diff --git a/src/__tests__/pusher-js-mock.spec.ts b/src/__tests__/pusher-js-mock.spec.ts index aa3dc2d..4705dda 100644 --- a/src/__tests__/pusher-js-mock.spec.ts +++ b/src/__tests__/pusher-js-mock.spec.ts @@ -21,6 +21,10 @@ describe("PusherMock", () => { expect(pusherMock.channel("my-channel")).toBeDefined(); }); + it("is possible to access pusher client through the channel object", () => { + expect(pusherMock.channel("my-channel").pusher).toBeDefined(); + }); + it("adds new channel to channels object", () => { pusherMock.channel("my-channel"); expect(pusherMock.channels).toMatchObject({ "my-channel": {} }); diff --git a/src/__tests__/pusher-presence-channel-mock.spec.ts b/src/__tests__/pusher-presence-channel-mock.spec.ts index cbd8630..3698b08 100644 --- a/src/__tests__/pusher-presence-channel-mock.spec.ts +++ b/src/__tests__/pusher-presence-channel-mock.spec.ts @@ -1,4 +1,5 @@ import { PusherMock, PusherPresenceChannelMock } from "../"; +import { isPresenceChannel } from "../pusher-presence-channel-mock"; describe("PusherPresenceChannelMock", () => { let channelMock: PusherPresenceChannelMock; @@ -51,14 +52,28 @@ describe("Proxied PusherPresenceChannelMock", () => { client = createClient("my-id", {}); otherClient = createClient("your-id", {}); - proxiedChannelMock = client.subscribe(PRESENCE_CHANNEL); - otherProxiedChannelMock = otherClient.subscribe(PRESENCE_CHANNEL); + const channel = client.subscribe(PRESENCE_CHANNEL); + const otherChannel = otherClient.subscribe(PRESENCE_CHANNEL); + + if (!isPresenceChannel(channel) || !isPresenceChannel(otherChannel)) return; + + proxiedChannelMock = channel; + otherProxiedChannelMock = otherChannel; }); it("doesn't proxy class members it doesn't care about", () => { expect(proxiedChannelMock.subscribed).toBe(true); }); + it("me and myID values should be different when accessed from different channel instances", () => { + expect(proxiedChannelMock.members.myID).not.toBe( + otherProxiedChannelMock.members.myID + ); + expect(proxiedChannelMock.members.me).not.toBe( + otherProxiedChannelMock.members.me + ); + }); + it("add new members to the channel", () => { expect(proxiedChannelMock.members.count).toBe(2); expect(proxiedChannelMock.members.get("my-id")).toEqual({ diff --git a/src/proxy-channel.ts b/src/proxy-channel.ts new file mode 100644 index 0000000..025bf91 --- /dev/null +++ b/src/proxy-channel.ts @@ -0,0 +1,33 @@ +import { PusherChannelMock, PusherMock } from "."; + +/** + * Create the proxied channel + * @param {PusherChannelMock} channel the channel to be proxied + * @param {PusherMock} client the client we'll use to proxy the channel + * @returns {Proxy} the proxied channel + */ +export const proxyChannel = ( + channel: PusherChannelMock, + client: PusherMock +) => { + const handler = { + /** + * Proxies a channel and augments it with client specific information + * @param target The channel we're proxying + * @param key The attribute, property or method we're trapping + * @returns {mixed} the result of the trapped function + */ + get(target: PusherChannelMock, key: keyof PusherChannelMock) { + switch (key) { + case "IS_PROXY": + return true; + case "pusher": + return client; + default: + return target[key]; + } + } + }; + + return new Proxy(channel, handler); +}; diff --git a/src/proxy-presence-channel.ts b/src/proxy-presence-channel.ts index dc82621..cb3f9a6 100644 --- a/src/proxy-presence-channel.ts +++ b/src/proxy-presence-channel.ts @@ -14,12 +14,24 @@ export interface IProxiedCallback { * @returns {Members} The proxied members property on the channel */ const proxyMembers = (original: Members, client: PusherMock) => { - original.myID = client.id; - original.me = { - id: client.id, - info: client.info - }; - return original; + return new Proxy(original, { + get( + target: PusherPresenceChannelMock["members"], + key: keyof PusherPresenceChannelMock["members"] + ) { + switch (key) { + case "me": + return { + id: client.id, + info: client.info + }; + case "myID": + return client.id; + default: + return target[key]; + } + } + }); }; /** @@ -94,6 +106,8 @@ export const proxyPresenceChannel = ( /** Attach this client's info the member specific calls */ case "members": return proxyMembers(target.members, client); + case "pusher": + return client; /** Attach the owner of the callback so we can ignore it in future */ case "bind": return proxyBind(target, client); diff --git a/src/pusher-channel-mock.ts b/src/pusher-channel-mock.ts index b762640..b9dcf48 100644 --- a/src/pusher-channel-mock.ts +++ b/src/pusher-channel-mock.ts @@ -1,3 +1,5 @@ +import PusherMock from "./pusher-js-mock"; + /** Interface for all the callbacks each Pusher event could potentially have */ interface ICallbacks { [key: string]: Array<() => void>; @@ -8,6 +10,8 @@ class PusherChannelMock { public name: string; public callbacks: ICallbacks; public subscribed: boolean = true; + public IS_PROXY?: boolean; + public pusher?: PusherMock; /** Initialize PusherChannelMock with callbacks object. */ constructor(name: string = "public-channel") { diff --git a/src/pusher-js-mock-instance.ts b/src/pusher-js-mock-instance.ts index 27a36ce..cb62cf8 100644 --- a/src/pusher-js-mock-instance.ts +++ b/src/pusher-js-mock-instance.ts @@ -1,3 +1,4 @@ +import { proxyChannel } from "./proxy-channel"; import { proxyPresenceChannel } from "./proxy-presence-channel"; import PusherChannelMock from "./pusher-channel-mock"; import PusherMock from "./pusher-js-mock"; @@ -24,7 +25,7 @@ class PusherMockInstance { * @returns {PusherChannelMock} PusherChannelMock object that represents channel */ public channel(name: string, client: PusherMock = new PusherMock()) { - const presenceChannel = name.includes("presence-"); + const presenceChannel = name.startsWith("presence-"); if (!this.channels[name]) { this.channels[name] = presenceChannel ? new PusherPresenceChannelMock(name) @@ -33,7 +34,7 @@ class PusherMockInstance { return presenceChannel ? proxyPresenceChannel(this.channels[name], client) - : this.channels[name]; + : proxyChannel(this.channels[name], client); } /** diff --git a/src/pusher-js-mock.ts b/src/pusher-js-mock.ts index 835330c..8370220 100644 --- a/src/pusher-js-mock.ts +++ b/src/pusher-js-mock.ts @@ -2,6 +2,7 @@ import { PusherChannelMock } from "."; import { IProxiedCallback } from "./proxy-presence-channel"; import { emitConnectionEvents, emitDisconnectionEvents } from "./pusher-events"; import PusherMockInstance from "./pusher-js-mock-instance"; +import { isPresenceChannel } from "./pusher-presence-channel-mock"; export interface IPusherMockOptions { authorizer: ( @@ -49,9 +50,11 @@ class PusherMock { public subscribe(name: string) { const channel = PusherMockInstance.channel(name, this); - if (name.includes("presence-")) { + if (isPresenceChannel(channel)) { this.config?.authorizer - ? this.config.authorizer({} as any).authorize(channel, this.setAuthInfo) + ? this.config + .authorizer({} as any) + .authorize(channel as any, this.setAuthInfo) : this.setAuthInfo(false, { id: Math.random() .toString(36) @@ -69,8 +72,9 @@ class PusherMock { * @param {String} name - name of the channel. */ public unsubscribe(name: string) { + const channel = PusherMockInstance.channel(name, this); if (name in PusherMockInstance.channels) { - if (name.includes("presence-")) { + if (isPresenceChannel(channel)) { this.unsubscribePresence(name); } else { // public channel diff --git a/src/pusher-presence-channel-mock.ts b/src/pusher-presence-channel-mock.ts index ca5a4d8..7422537 100644 --- a/src/pusher-presence-channel-mock.ts +++ b/src/pusher-presence-channel-mock.ts @@ -21,4 +21,10 @@ class PusherPresenceChannelMock extends PusherChannelMock { } } +export function isPresenceChannel( + channel: PusherChannelMock +): channel is PusherPresenceChannelMock { + return channel.name.startsWith("presence-"); +} + export default PusherPresenceChannelMock;