Skip to content

Commit

Permalink
✅ code coverage + more robust channels
Browse files Browse the repository at this point in the history
  • Loading branch information
Harley Alexander committed Aug 20, 2019
1 parent 0de31c1 commit 161be56
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 38 deletions.
90 changes: 76 additions & 14 deletions src/__tests__/useChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,95 @@ import React from "react";
import { renderHook } from "@testing-library/react-hooks";
import { PusherProvider } from "../PusherProvider";
import { useChannel } from "../useChannel";
import { cleanup } from "@testing-library/react";

beforeEach(() => {
cleanup();
jest.resetAllMocks();
});

jest.mock("pusher-js", () => {
const { PusherMock } = require("../mocks.ts");
return {
__esModule: true,
default: jest.fn(() => new PusherMock())
};
const { PusherMock } = require("pusher-js-mock");
// monkey patch missing function
PusherMock.prototype.disconnect = () => {};
return PusherMock;
});

const config = {
clientKey: "client-key",
cluster: "ap4",
children: "Test"
};

test("should fill default options", () => {
const wrapper = ({ children }: any) => (
<PusherProvider {...config}>{children}</PusherProvider>
);
const { result, unmount } = renderHook(() => useChannel("my-channel"), {
wrapper
});

const { channel } = result.current;
expect(Object.keys(channel.callbacks)).toHaveLength(0);

unmount();
});

test("should subscribe to channel and listen to events", async () => {
test("should subscribe to channel and emit events", async () => {
const onEvent = jest.fn();
const wrapper = ({ children }: any) => (
<PusherProvider clientKey="client-key" cluster="ap4">
{children}
</PusherProvider>
<PusherProvider {...config}>{children}</PusherProvider>
);
const { result, unmount } = renderHook(
() => useChannel("my-channel", "my-event", onEvent),
const { result, unmount, rerender } = renderHook(
() => useChannel("my-channel", "my-event", onEvent, [], { skip: false }),
{ wrapper }
);

expect(result.current!.bind).toHaveBeenCalledTimes(1);
rerender();

const { channel } = result.current;

result.current!.emit("my-event", "test");
channel.emit("my-event", "test");
expect(channel.callbacks["my-event"]).toBeTruthy();
expect(onEvent).toHaveBeenCalledTimes(1);
expect(onEvent).toHaveBeenCalledWith("test");

unmount();
expect(result.current!.unbind).toHaveBeenCalledTimes(1);
});

test("should subscribe to channel as prop changes", () => {
const onEvent = jest.fn();
const wrapper = ({ children }: any) => (
<PusherProvider {...config}>{children}</PusherProvider>
);
const { result, unmount, rerender } = renderHook(
([a = "my-channel", b = "my-event", c = onEvent]: any) =>
useChannel(a, b, c),
{ wrapper }
);

rerender(["your-channel", "some-event", onEvent]);
const { channel } = result.current;

// simulate event
channel.emit("some-event", "test");

expect(onEvent).toHaveBeenCalledTimes(1);
expect(onEvent).toHaveBeenCalledWith("test");
unmount();
});

test("should skip channel subscription if option is passed", () => {
const wrapper = ({ children }: any) => (
<PusherProvider {...config}>{children}</PusherProvider>
);
const { result, unmount } = renderHook(
() => useChannel("a", "b", () => {}, [], { skip: true }),
{ wrapper }
);

const { channel } = result.current;
expect(channel).toBeUndefined();

unmount();
});
61 changes: 37 additions & 24 deletions src/useChannel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";

import { Channel, EventCallback } from "pusher-js";
import { EventCallback } from "pusher-js";
import invariant from "invariant";

import { usePusher } from "./usePusher";
Expand All @@ -26,8 +26,6 @@ export function useChannel(
) {
// errors for missing arguments
invariant(channelName, "channelName required to subscribe to a channel");
invariant(eventName, "eventName required to bind to an event");
invariant(onEvent, "onEvent required to callback on event");

// initialise defaults
const defaultOptions = { skip: false };
Expand All @@ -39,35 +37,50 @@ export function useChannel(

// hook setup
const { client } = usePusher();
const callback = useCallback<EventCallback>(eventHandler, deps);
const [channel, setChannel] = useState<Channel | undefined>();
const [channel, setChannel] = useState<any>();

/**
* Channel subscription
*/
useEffect(() => {
if (client && !hookOptions.skip && channelName) {
if (client.current && !hookOptions.skip) {
// subscribe to the channel
const pusherChannel = client.subscribe(channelName);
// if there's an eventName, bind to it.
eventName && pusherChannel.bind(eventName, callback);

// store the ref for cleanup
const pusherChannel = client.current.subscribe(channelName);
setChannel(pusherChannel);
}
}, [client, callback, hookOptions.skip, eventName, channelName]);
}, [client.current, hookOptions.skip]);

// cleanup on unmount
useEffect(
() => () => {
if (client && channel) {
client.unsubscribe(channelName);
}
const previousChannelName = useRef<string | undefined>();
useEffect(() => {
if (previousChannelName.current === channelName) return;
const clientRef = client.current;
return () => {
clientRef.unsubscribe(channelName);
};
});
// track channelName for unsubscription.
useEffect(() => {
previousChannelName.current = channelName;
});

/**
* Event binding
*/
const callback = useCallback<EventCallback>(eventHandler, deps);
useEffect(() => {
if (channel && eventName && !hookOptions.skip) {
channel.bind(eventName, callback);
}
}, [channel, eventName, hookOptions.skip, callback]);

// when the callback changes, unbind the old one.
useEffect(() => {
return () => {
if (client && channel && eventName) {
channel.unbind(eventName, callback);
}
},
[client, channel]
);
};
}, [client, channel, callback, eventName]);

// return channel instance back for unsafe things like channel.trigger()
return channel;
return { channel };
}

0 comments on commit 161be56

Please sign in to comment.