import { createContainer, DiContainer, Injectable, } from "@ogre-tools/injectable"; import asyncFn, { AsyncFnMock } from "@async-fn/jest"; import { registerFeature } from "@k8slens/feature-core/src/register-feature"; import { getMessageChannelListenerInjectable, MessageChannel, } from "../../actual/message/message-channel-listener-injection-token"; import { sendMessageToChannelInjectionToken } from "../../actual/message/message-to-channel-injection-token.no-coverage"; import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; import { runInAction } from "mobx"; import { getRequestChannelListenerInjectable, RequestChannel, } from "../../actual/request/request-channel-listener-injection-token"; import { requestFromChannelInjectionToken } from "../../actual/request/request-from-channel-injection-token.no-coverage"; import { getPromiseStatus } from "@k8slens/test-utils"; import { getMessageBridgeFake } from "./get-message-bridge-fake"; import { getMessageChannel } from "../../actual/message/get-message-channel"; import { getRequestChannel } from "../../actual/request/get-request-channel"; import { startApplicationInjectionToken } from "@k8slens/application"; import { messagingFeatureForUnitTesting } from "../feature"; [{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach( ({ scenarioIsAsync }) => describe(`get-message-bridge-fake, given running as ${ scenarioIsAsync ? "async" : "sync" }`, () => { let messageBridgeFake: any; beforeEach(() => { messageBridgeFake = getMessageBridgeFake(); }); describe("given multiple DIs are involved", () => { let someDi1: DiContainer; let someDi2: DiContainer; let someDiWithoutListeners: DiContainer; beforeEach(async () => { someDi1 = createContainer("some-di-1"); someDi2 = createContainer("some-di-2"); someDiWithoutListeners = createContainer("some-di-3"); registerMobX(someDi1); registerMobX(someDi2); registerMobX(someDiWithoutListeners); runInAction(() => { registerFeature(someDi1, messagingFeatureForUnitTesting); registerFeature(someDi2, messagingFeatureForUnitTesting); registerFeature( someDiWithoutListeners, messagingFeatureForUnitTesting ); }); messageBridgeFake.involve(someDi1, someDi2, someDiWithoutListeners); if (scenarioIsAsync) { messageBridgeFake.setAsync(scenarioIsAsync); } await Promise.all([ someDi1.inject(startApplicationInjectionToken)(), someDi2.inject(startApplicationInjectionToken)(), someDiWithoutListeners.inject(startApplicationInjectionToken)(), ]); }); describe("given there are message listeners", () => { let someHandler1MockInDi1: jest.Mock; let someHandler1MockInDi2: jest.Mock; let someHandler2MockInDi2: jest.Mock; let someListener1InDi2: Injectable; beforeEach(() => { someHandler1MockInDi1 = jest.fn(); someHandler1MockInDi2 = jest.fn(); someHandler2MockInDi2 = jest.fn(); const someListener1InDi1 = getMessageChannelListenerInjectable({ id: "some-listener-in-di-1", channel: someMessageChannel, getHandler: () => someHandler1MockInDi1, }); someListener1InDi2 = getMessageChannelListenerInjectable({ id: "some-listener-in-di-2", channel: someMessageChannel, getHandler: () => someHandler1MockInDi2, }); const someListener2InDi2 = getMessageChannelListenerInjectable({ id: "some-listener-2-in-di-2", channel: someMessageChannel, getHandler: () => someHandler2MockInDi2, }); runInAction(() => { someDi1.register(someListener1InDi1); someDi2.register(someListener1InDi2); someDi2.register(someListener2InDi2); }); }); describe("given there is a listener in di-2 that responds to a message with a message", () => { beforeEach(() => { const someResponder = getMessageChannelListenerInjectable({ id: "some-responder-di-2", channel: someMessageChannel, getHandler: (di) => { const sendMessage = di.inject( sendMessageToChannelInjectionToken ); return (message) => { sendMessage( someMessageChannel, `some-response-to: ${message}` ); }; }, }); runInAction(() => { someDi2.register(someResponder); }); }); describe("given a message is sent in di-1", () => { beforeEach(() => { const sendMessageToChannelFromDi1 = someDi1.inject( sendMessageToChannelInjectionToken ); sendMessageToChannelFromDi1(someMessageChannel, "some-message"); }); describe( scenarioIsAsync ? "when all message steps are propagated using a wrapper" : "immediately", () => { let someWrapper: jest.Mock; beforeEach((done) => { someWrapper = jest.fn((propagation) => propagation()); if (scenarioIsAsync) { messageBridgeFake .messagePropagationRecursive(someWrapper) .then(done); } else { done(); } }); it("the response gets handled in di-1", () => { expect(someHandler1MockInDi1).toHaveBeenCalledWith( "some-response-to: some-message" ); }); scenarioIsAsync && it("the wrapper gets called with the both propagations", () => { expect(someWrapper).toHaveBeenCalledTimes(2); }); } ); describe( scenarioIsAsync ? "when all message steps are propagated not using a wrapper" : "immediately", () => { beforeEach((done) => { if (scenarioIsAsync) { messageBridgeFake .messagePropagationRecursive() .then(done); } else { done(); } }); it("the response gets handled in di-1", () => { expect(someHandler1MockInDi1).toHaveBeenCalledWith( "some-response-to: some-message" ); }); } ); }); }); describe("when sending message in a DI", () => { beforeEach(() => { const sendMessageToChannelFromDi1 = someDi1.inject( sendMessageToChannelInjectionToken ); sendMessageToChannelFromDi1(someMessageChannel, "some-message"); }); it("listener in sending DI does not handle the message", () => { expect(someHandler1MockInDi1).not.toHaveBeenCalled(); }); scenarioIsAsync && it("listeners in other than sending DIs do not handle the message yet", () => { expect(someHandler1MockInDi2).not.toHaveBeenCalled(); expect(someHandler2MockInDi2).not.toHaveBeenCalled(); }); describe( scenarioIsAsync ? "when messages are propagated" : "immediately", () => { beforeEach((done) => { if (scenarioIsAsync) { messageBridgeFake.messagePropagation().then(done); } else { done(); } }); it("listeners in other than sending DIs handle the message", () => { expect(someHandler1MockInDi2).toHaveBeenCalledWith( "some-message" ); expect(someHandler2MockInDi2).toHaveBeenCalledWith( "some-message" ); }); } ); scenarioIsAsync && describe("when messages are propagated using a wrapper, such as act() in react testing lib", () => { let someWrapper: jest.Mock; beforeEach(async () => { someWrapper = jest.fn((observation) => observation()); await messageBridgeFake.messagePropagation(someWrapper); }); it("the wrapper gets called with the related propagation", async () => { expect(someWrapper).toHaveBeenCalledTimes(1); }); it("listeners still handle the message", () => { expect(someHandler1MockInDi2).toHaveBeenCalledWith( "some-message" ); expect(someHandler2MockInDi2).toHaveBeenCalledWith( "some-message" ); }); }); }); it("given a listener is deregistered, when sending message, deregistered listener does not handle the message", () => { runInAction(() => { someDi2.deregister(someListener1InDi2); }); const sendMessageToChannelFromDi1 = someDi1.inject( sendMessageToChannelInjectionToken ); someHandler1MockInDi2.mockClear(); sendMessageToChannelFromDi1(someMessageChannel, "irrelevant"); expect(someHandler1MockInDi2).not.toHaveBeenCalled(); }); }); describe("given there are request listeners", () => { let someHandler1MockInDi1: AsyncFnMock< (message: string) => Promise >; let someHandler1MockInDi2: AsyncFnMock< (message: string) => Promise >; let someListener1InDi2: Injectable; let actualPromise: Promise; beforeEach(() => { someHandler1MockInDi1 = asyncFn(); someHandler1MockInDi2 = asyncFn(); const someListener1InDi1 = getRequestChannelListenerInjectable({ id: "some-request-listener-in-di-1", channel: someOtherRequestChannel, getHandler: () => someHandler1MockInDi1, }); someListener1InDi2 = getRequestChannelListenerInjectable({ id: "some-request-listener-in-di-2", channel: someRequestChannel, getHandler: () => someHandler1MockInDi2, }); runInAction(() => { someDi1.register(someListener1InDi1); someDi2.register(someListener1InDi2); }); }); describe("when requesting from a channel in a DI", () => { beforeEach(() => { const requestFromChannelFromDi1 = someDi1.inject( requestFromChannelInjectionToken ); actualPromise = requestFromChannelFromDi1( someRequestChannel, "some-request" ); }); it("listener in requesting DI does not handle the request", () => { expect(someHandler1MockInDi1).not.toHaveBeenCalled(); }); it("the listener in other than requesting DIs handle the request", () => { expect(someHandler1MockInDi2).toHaveBeenCalledWith( "some-request" ); }); it("does not resolve yet", async () => { const promiseStatus = await getPromiseStatus(actualPromise); expect(promiseStatus.fulfilled).toBe(false); }); it("when handle resolves, resolves with response", async () => { await someHandler1MockInDi2.resolve(42); const actual = await actualPromise; expect(actual).toBe(42); }); }); it("given a listener is deregistered, when requesting, deregistered listener does not handle the request", () => { runInAction(() => { someDi2.deregister(someListener1InDi2); }); const sendMessageToChannelFromDi1 = someDi1.inject( sendMessageToChannelInjectionToken ); someHandler1MockInDi2.mockClear(); sendMessageToChannelFromDi1(someMessageChannel, "irrelevant"); expect(someHandler1MockInDi2).not.toHaveBeenCalled(); }); it("given there are multiple listeners between different DIs for same channel, when requesting, throws", () => { const someConflictingListenerInjectable = getRequestChannelListenerInjectable({ id: "conflicting-listener", channel: someRequestChannel, getHandler: () => () => 84, }); runInAction(() => { someDi1.register(someConflictingListenerInjectable); }); const requestFromChannelFromDi2 = someDi2.inject( requestFromChannelInjectionToken ); return expect(() => requestFromChannelFromDi2(someRequestChannel, "irrelevant") ).rejects.toThrow( 'Tried to make a request but multiple listeners were discovered for channel "some-request-channel" in multiple DIs.' ); }); it("when requesting from channel without listener, throws", () => { const requestFromChannel = someDi1.inject( requestFromChannelInjectionToken ); return expect(() => requestFromChannel( someRequestChannelWithoutListeners, "irrelevant" ) ).rejects.toThrow( 'Tried to make a request but no listeners for channel "some-request-channel-without-listeners" was discovered in any DIs' ); }); }); }); }) ); type SomeMessageChannel = MessageChannel; type SomeRequestChannel = RequestChannel; const someMessageChannel: SomeMessageChannel = getMessageChannel( "some-message-channel" ); const someRequestChannel: SomeRequestChannel = getRequestChannel( "some-request-channel" ); const someOtherRequestChannel: SomeRequestChannel = { id: "some-other-request-channel", }; const someRequestChannelWithoutListeners: SomeRequestChannel = { id: "some-request-channel-without-listeners", };