From 88023e1f70b281d501bb37d18e90bd88ad2698ea Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Fri, 17 Mar 2023 09:33:30 +0200 Subject: [PATCH] Fixup Signed-off-by: Janne Savolainen --- .../messaging/agnostic/jest.config.js | 3 +- .../computed-channel.injectable.ts | 114 +-- .../computed-channel.test.tsx | 955 +++++++++--------- .../agnostic/src/features/actual/feature.ts | 4 +- .../listening-of-channels.injectable.ts | 106 +- ...essage-channel-listener-injection-token.ts | 2 +- .../actual/message/get-message-channel.ts | 4 +- ...essage-channel-listener-injection-token.ts | 13 +- ...-to-channel-injection-token.no-coverage.ts | 7 +- ...equest-channel-listener-injection-token.ts | 2 +- .../actual/request/get-request-channel.ts | 2 +- ...equest-channel-listener-injection-token.ts | 6 +- ...rom-channel-injection-token.no-coverage.ts | 9 +- .../src/features/unit-testing/feature.ts | 4 +- .../get-message-bridge-fake.test.ts | 746 +++++++------- .../get-message-bridge-fake.ts | 260 +++-- .../src/listening-of-messages.test.ts | 8 +- .../src/listening-of-requests.test.ts | 2 +- .../messaging/agnostic/tsconfig.json | 2 +- .../messaging/main/jest.config.js | 3 +- .../enlist-message-channel-listener.test.ts | 14 +- ...ist-request-channel-listener.injectable.ts | 14 +- .../enlist-request-channel-listener.test.ts | 19 +- .../messaging/main/src/feature.ts | 4 +- .../messaging/renderer/jest.config.js | 3 +- .../messaging/renderer/src/feature.ts | 4 +- .../enlist-message-channel-listener.test.ts | 14 +- .../request-from-channel.injectable.ts | 3 +- .../request-from-channel.test.ts | 5 +- .../message-to-channel.injectable.ts | 5 +- .../message-to-channel.test.ts | 4 +- 31 files changed, 1070 insertions(+), 1271 deletions(-) diff --git a/packages/technical-features/messaging/agnostic/jest.config.js b/packages/technical-features/messaging/agnostic/jest.config.js index 23be80353b..38d54ab7b6 100644 --- a/packages/technical-features/messaging/agnostic/jest.config.js +++ b/packages/technical-features/messaging/agnostic/jest.config.js @@ -1,2 +1 @@ -module.exports = - require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact; +module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact; diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.injectable.ts b/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.injectable.ts index d6d707e921..caaa410473 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.injectable.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.injectable.ts @@ -20,21 +20,18 @@ import { filter, groupBy, map, nth, toPairs } from "lodash/fp"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; import type { JsonPrimitive } from "type-fest"; -export type JsonifiableObject = - | { [Key in string]?: Jsonifiable } - | { toJSON: () => Jsonifiable }; +export type JsonifiableObject = { [Key in string]?: Jsonifiable } | { toJSON: () => Jsonifiable }; export type JsonifiableArray = readonly Jsonifiable[]; export type Jsonifiable = JsonPrimitive | JsonifiableObject | JsonifiableArray; export type ComputedChannelFactory = ( channel: MessageChannel, - pendingValue: T + pendingValue: T, ) => IComputedValue; -export const computedChannelInjectionToken = - getInjectionToken({ - id: "computed-channel-injection-token", - }); +export const computedChannelInjectionToken = getInjectionToken({ + id: "computed-channel-injection-token", +}); export type ChannelObserver = { channel: MessageChannel; @@ -51,6 +48,10 @@ export const computedChannelObserverInjectionToken = getInjectionToken< id: "computed-channel-observer", }); +export const computedChannelAdministrationChannel: MessageChannel = { + id: "computed-channel-administration-channel", +}; + const computedChannelInjectable = getInjectable({ id: "computed-channel", @@ -67,7 +68,7 @@ const computedChannelInjectable = getInjectable({ if (!contextIsReactive) { throw new Error( - `Tried to access value of computed channel "${channel.id}" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.` + `Tried to access value of computed channel "${channel.id}" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.`, ); } @@ -134,17 +135,17 @@ export const duplicateChannelObserverGuardInjectable = getInjectable({ groupBy((observer) => observer.channel.id), toPairs, filter(([, channelObservers]) => channelObservers.length > 1), - map(nth(0)) + map(nth(0)), ); if (duplicateObserverChannelIds.length) { throw new Error( `Tried to register duplicate channel observer for channels "${duplicateObserverChannelIds.join( - '", "' - )}"` + '", "', + )}"`, ); } - } + }, ); }, }; @@ -153,59 +154,48 @@ export const duplicateChannelObserverGuardInjectable = getInjectable({ injectionToken: onLoadOfApplicationInjectionToken, }); -export const computedChannelAdministrationChannel: MessageChannel = - { - id: "computed-channel-administration-channel", - }; +export const computedChannelAdministrationListenerInjectable = getMessageChannelListenerInjectable({ + id: "computed-channel-administration", + getHandler: (di) => { + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); -export const computedChannelAdministrationListenerInjectable = - getMessageChannelListenerInjectable({ - id: "computed-channel-administration", - getHandler: (di) => { - const sendMessageToChannel = di.inject( - sendMessageToChannelInjectionToken - ); + const disposersByChannelId = new Map void>(); - const disposersByChannelId = new Map void>(); + return (message) => { + if (message.status === "became-observed") { + const result = di + .injectMany(computedChannelObserverInjectionToken) + .find((channelObserver) => channelObserver.channel.id === message.channelId); - return (message) => { - if (message.status === "became-observed") { - const result = di - .injectMany(computedChannelObserverInjectionToken) - .find( - (channelObserver) => - channelObserver.channel.id === message.channelId - ); - - if (result === undefined) { - return; - } - - const disposer = reaction( - () => result.observer.get(), - (observed) => - sendMessageToChannel( - { - id: message.channelId, - }, - - observed - ), - { - fireImmediately: true, - } - ); - - disposersByChannelId.set(message.channelId, disposer); - } else { - const disposer = disposersByChannelId.get(message.channelId); - - disposer!(); + if (result === undefined) { + return; } - }; - }, - channel: computedChannelAdministrationChannel, - }); + const disposer = reaction( + () => result.observer.get(), + (observed) => + sendMessageToChannel( + { + id: message.channelId, + }, + + observed, + ), + { + fireImmediately: true, + }, + ); + + disposersByChannelId.set(message.channelId, disposer); + } else { + const disposer = disposersByChannelId.get(message.channelId); + + disposer?.(); + } + }; + }, + + channel: computedChannelAdministrationChannel, +}); export default computedChannelInjectable; diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.test.tsx b/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.test.tsx index 67cc675bfb..d9f9293fe1 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.test.tsx +++ b/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.test.tsx @@ -1,10 +1,6 @@ import React from "react"; import { act } from "@testing-library/react"; -import { - createContainer, - DiContainer, - getInjectable, -} from "@ogre-tools/injectable"; +import { createContainer, DiContainer, getInjectable } from "@ogre-tools/injectable"; import { getMessageBridgeFake, MessageBridgeFake, @@ -35,443 +31,280 @@ import { observer } from "mobx-react"; const testChannel: MessageChannel = { id: "some-channel-id" }; const testChannel2: MessageChannel = { id: "some-other-channel-id" }; -[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach( - ({ scenarioIsAsync }) => - describe(`computed-channel, given running message bridge fake as ${ - scenarioIsAsync ? "async" : "sync" - }`, () => { - describe("given multiple dis and a message channel and a channel observer and application has started", () => { - let di1: DiContainer; - let di2: DiContainer; - let latestAdminMessage: ComputedChannelAdminMessage | undefined; - let latestValueMessage: string | undefined; - let messageBridgeFake: MessageBridgeFake; +const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue }) => ( +
{someComputed.get()}
+)); - beforeEach(async () => { - latestAdminMessage = undefined; - latestValueMessage = undefined; +[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach(({ scenarioIsAsync }) => + describe(`computed-channel, given running message bridge fake as ${ + scenarioIsAsync ? "async" : "sync" + }`, () => { + describe("given multiple dis and a message channel and a channel observer and application has started", () => { + let di1: DiContainer; + let di2: DiContainer; + let latestAdminMessage: ComputedChannelAdminMessage | undefined; + let latestValueMessage: string | undefined; + let messageBridgeFake: MessageBridgeFake; - di1 = createContainer("some-container-1"); - di2 = createContainer("some-container-2"); - registerMobX(di1); - registerMobX(di2); + beforeEach(async () => { + latestAdminMessage = undefined; + latestValueMessage = undefined; - const administrationChannelTestListenerInjectable = - getMessageChannelListenerInjectable({ - id: "administration-channel-test-listener", - channel: computedChannelAdministrationChannel, + di1 = createContainer("some-container-1"); + di2 = createContainer("some-container-2"); + registerMobX(di1); + registerMobX(di2); - getHandler: () => (adminMessage) => { - latestAdminMessage = adminMessage; - }, - }); + const administrationChannelTestListenerInjectable = getMessageChannelListenerInjectable({ + id: "administration-channel-test-listener", + channel: computedChannelAdministrationChannel, - const channelValueTestListenerInjectable = - getMessageChannelListenerInjectable({ - id: "test-channel-value-listener", - channel: testChannel, - - getHandler: () => (message) => { - latestValueMessage = message; - }, - }); - - runInAction(() => { - registerFeature(di1, messagingFeatureForUnitTesting); - registerFeature(di2, messagingFeatureForUnitTesting); - - di1.register(channelValueTestListenerInjectable); - di2.register(administrationChannelTestListenerInjectable); - }); - - messageBridgeFake = getMessageBridgeFake(); - messageBridgeFake.setAsync(scenarioIsAsync); - messageBridgeFake.involve(di1, di2); - - await Promise.all([ - di1.inject(startApplicationInjectionToken)(), - di2.inject(startApplicationInjectionToken)(), - ]); + getHandler: () => (adminMessage) => { + latestAdminMessage = adminMessage; + }, }); - describe("given a channel observer and matching computed channel for the channel in di-2", () => { - let someObservable: IObservableValue; - let computedTestChannel: IComputedValue; + const channelValueTestListenerInjectable = getMessageChannelListenerInjectable({ + id: "test-channel-value-listener", + channel: testChannel, + + getHandler: () => (message) => { + latestValueMessage = message; + }, + }); + + runInAction(() => { + registerFeature(di1, messagingFeatureForUnitTesting); + registerFeature(di2, messagingFeatureForUnitTesting); + + di1.register(channelValueTestListenerInjectable); + di2.register(administrationChannelTestListenerInjectable); + }); + + messageBridgeFake = getMessageBridgeFake(); + messageBridgeFake.setAsync(scenarioIsAsync); + messageBridgeFake.involve(di1, di2); + + await Promise.all([ + di1.inject(startApplicationInjectionToken)(), + di2.inject(startApplicationInjectionToken)(), + ]); + }); + + describe("given a channel observer and matching computed channel for the channel in di-2", () => { + let someObservable: IObservableValue; + let computedTestChannel: IComputedValue; + + beforeEach(() => { + someObservable = observable.box("some-initial-value"); + + const channelObserverInjectable = getInjectable({ + id: "some-channel-observer", + + instantiate: () => ({ + channel: testChannel, + observer: computed(() => someObservable.get()), + }), + + injectionToken: computedChannelObserverInjectionToken, + }); + + runInAction(() => { + di2.register(channelObserverInjectable); + }); + + const computedChannel = di1.inject(computedChannelInjectionToken); + + computedTestChannel = computedChannel(testChannel, "some-pending-value"); + }); + + it("there is no admin message yet", () => { + expect(latestAdminMessage).toBeUndefined(); + }); + + describe("when observing the computed value in a component in di-1", () => { + let rendered: any; beforeEach(() => { - someObservable = observable.box("some-initial-value"); + const render = renderFor(di2); - const channelObserverInjectable = getInjectable({ - id: "some-channel-observer", - - instantiate: () => ({ - channel: testChannel, - observer: computed(() => someObservable.get()), - }), - - injectionToken: computedChannelObserverInjectionToken, - }); - - runInAction(() => { - di2.register(channelObserverInjectable); - }); - - const computedChannel = di1.inject(computedChannelInjectionToken); - - computedTestChannel = computedChannel( - testChannel, - "some-pending-value" - ); + rendered = render(); }); - it("there is no admin message yet", () => { - expect(latestAdminMessage).toBeUndefined(); - }); + const scenarioName = scenarioIsAsync ? "when all messages are propagated" : "immediately"; - describe("when observing the computed value in a component in di-1", () => { - let rendered: any; - - beforeEach(() => { - const render = renderFor(di2); - - rendered = render( - - ); - }); - - describe( - scenarioIsAsync - ? "when all messages are propagated" - : "immediately", - () => { - beforeEach((done) => { - if (scenarioIsAsync) { - messageBridgeFake - .messagePropagationRecursive(act) - .then(done); - } else { - done(); - } - }); - - it("renders", () => { - expect(rendered.container).toHaveTextContent( - "some-initial-value" - ); - }); + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + beforeEach((done) => { + if (scenarioIsAsync) { + messageBridgeFake.messagePropagationRecursive(act).then(done); + } else { + done(); } + }); + + it("renders", () => { + expect(rendered.container).toHaveTextContent("some-initial-value"); + }); + }); + }); + + describe("when observing the computed channel in di-1", () => { + let observedValue: string | undefined; + let stopObserving: () => void; + + beforeEach(() => { + observedValue = undefined; + + stopObserving = reaction( + () => computedTestChannel.get(), + (value) => { + observedValue = value; + }, + + { + fireImmediately: true, + }, ); }); - describe("when observing the computed channel in di-1", () => { - let observedValue: string | undefined; - let stopObserving: () => void; - - beforeEach(() => { - observedValue = undefined; - - stopObserving = reaction( - () => computedTestChannel.get(), - (value) => { - observedValue = value; - }, - - { - fireImmediately: true, - } - ); + scenarioIsAsync && + it("computed test channel value is observed as the pending value", () => { + expect(observedValue).toBe("some-pending-value"); }); - scenarioIsAsync && - it("computed test channel value is observed as the pending value", () => { - expect(observedValue).toBe("some-pending-value"); + const scenarioName = scenarioIsAsync + ? "when admin messages are propagated" + : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + beforeEach((done) => { + if (scenarioIsAsync) { + void messageBridgeFake.messagePropagation().then(done); + } else { + done(); + } + }); + + it("administration-message to start observing gets listened", () => { + expect(latestAdminMessage).toEqual({ + channelId: "some-channel-id", + status: "became-observed", + }); + }); + + const scenarioName = scenarioIsAsync + ? "when returning value-messages propagate" + : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + beforeEach((done) => { + if (scenarioIsAsync) { + void messageBridgeFake.messagePropagation().then(done); + } else { + done(); + } }); - describe( - scenarioIsAsync - ? "when admin messages are propagated" - : "immediately", - () => { - beforeEach((done) => { - if (scenarioIsAsync) { - messageBridgeFake.messagePropagation().then(done); - } else { - done(); - } - }); + it("the computed channel value in di-1 matches the value in di-2", () => { + expect(observedValue).toBe("some-initial-value"); + }); - it("administration-message to start observing gets listened", () => { - expect(latestAdminMessage).toEqual({ - channelId: "some-channel-id", - status: "became-observed", + it("the value gets listened in di-1", () => { + expect(latestValueMessage).toBe("some-initial-value"); + }); + + describe("when the observed value changes", () => { + beforeEach(async () => { + latestValueMessage = undefined; + + runInAction(() => { + someObservable.set("some-new-value"); }); }); - describe( - scenarioIsAsync - ? "when returning value-messages propagate" - : "immediately", - () => { - beforeEach((done) => { - if (scenarioIsAsync) { - messageBridgeFake.messagePropagation().then(done); - } else { - done(); - } + const scenarioName = scenarioIsAsync + ? "when value-messages propagate" + : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + beforeEach((done) => { + if (scenarioIsAsync) { + void messageBridgeFake.messagePropagation().then(done); + } else { + done(); + } + }); + + it("the computed channel value in di-1 changes", () => { + expect(observedValue).toBe("some-new-value"); + }); + + it("the new value gets listened in di-1", () => { + expect(latestValueMessage).toBe("some-new-value"); + }); + }); + }); + + describe("when stopping observation for the channel in di-1", () => { + beforeEach(async () => { + latestValueMessage = undefined; + + stopObserving(); + }); + + const scenarioName = scenarioIsAsync + ? "when admin-messages propagate" + : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + beforeEach((done) => { + if (scenarioIsAsync) { + void messageBridgeFake.messagePropagation().then(done); + } else { + done(); + } + }); + + it("messages administration channel to stop observing", () => { + expect(latestAdminMessage).toEqual({ + channelId: "some-channel-id", + status: "became-unobserved", }); + }); - it("the computed channel value in di-1 matches the value in di-2", () => { - expect(observedValue).toBe("some-initial-value"); - }); + it("no value gets listened in di-1 anymore", () => { + expect(latestValueMessage).toBeUndefined(); + }); - it("the value gets listened in di-1", () => { - expect(latestValueMessage).toBe("some-initial-value"); - }); - - describe("when the observed value changes", () => { - beforeEach(async () => { - latestValueMessage = undefined; - - runInAction(() => { - someObservable.set("some-new-value"); - }); - }); - - describe( - scenarioIsAsync - ? "when value-messages propagate" - : "immediately", - () => { - beforeEach((done) => { - if (scenarioIsAsync) { - messageBridgeFake.messagePropagation().then(done); - } else { - done(); - } - }); - - it("the computed channel value in di-1 changes", () => { - expect(observedValue).toBe("some-new-value"); - }); - - it("the new value gets listened in di-1", () => { - expect(latestValueMessage).toBe("some-new-value"); - }); - } - ); - }); - - describe("when stopping observation for the channel in di-1", () => { - beforeEach(async () => { - latestValueMessage = undefined; - - stopObserving(); - }); - - describe( - scenarioIsAsync - ? "when admin-messages propagate" - : "immediately", - () => { - beforeEach((done) => { - if (scenarioIsAsync) { - messageBridgeFake.messagePropagation().then(done); - } else { - done(); - } - }); - - it("messages administration channel to stop observing", () => { - expect(latestAdminMessage).toEqual({ - channelId: "some-channel-id", - status: "became-unobserved", - }); - }); - - it("no value gets listened in di-1 anymore", () => { - expect(latestValueMessage).toBeUndefined(); - }); - - describe("when the observed value changes", () => { - beforeEach(async () => { - latestValueMessage = undefined; - - runInAction(() => { - someObservable.set("some-new-value-2"); - }); - }); - - it("when accessing the computed value outside of reactive context, throws", () => { - expect(() => { - computedTestChannel.get(); - }).toThrow( - 'Tried to access value of computed channel "some-channel-id" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.' - ); - }); - - it("no value gets listened in di-1 anymore", () => { - expect(latestValueMessage).toBeUndefined(); - }); - - describe("when observing the computed channel again", () => { - beforeEach(() => { - observedValue = undefined; - - reaction( - () => computedTestChannel.get(), - (value) => { - observedValue = value; - }, - - { - fireImmediately: true, - } - ); - }); - - scenarioIsAsync && - it("computed test channel value is observed as the pending value again", () => { - expect(observedValue).toBe( - "some-pending-value" - ); - }); - - describe( - scenarioIsAsync - ? "when admin messages propagate" - : "immediately", - () => { - beforeEach((done) => { - if (scenarioIsAsync) { - latestAdminMessage = undefined; - - messageBridgeFake - .messagePropagation() - .then(done); - } else { - done(); - } - }); - - it("administration-message to start observing gets listened again", () => { - expect(latestAdminMessage).toEqual({ - channelId: "some-channel-id", - status: "became-observed", - }); - }); - - scenarioIsAsync && - it("computed test channel value is still observed as the pending value", () => { - expect(observedValue).toBe( - "some-pending-value" - ); - }); - - describe( - scenarioIsAsync - ? "when value-messages propagate back" - : "immediately", - () => { - beforeEach((done) => { - if (scenarioIsAsync) { - latestValueMessage = undefined; - - messageBridgeFake - .messagePropagation() - .then(done); - } else { - done(); - } - }); - - it("the computed channel value changes", () => { - expect(observedValue).toBe( - "some-new-value-2" - ); - }); - - it("the current value gets listened", () => { - expect(latestValueMessage).toBe( - "some-new-value-2" - ); - }); - } - ); - } - ); - }); - }); - } - ); - - it("when accessing the computed value outside of reactive context, throws", () => { - expect(() => { - computedTestChannel.get(); - }).toThrow( - 'Tried to access value of computed channel "some-channel-id" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.' - ); - }); - }); - - it("given observation of unrelated computed channel is stopped, observation of other computed channel still works", async () => { - const someOtherObservable = observable.box(""); - - const channelObserver2Injectable = getInjectable({ - id: "some-channel-observer-2", - - instantiate: () => ({ - channel: testChannel2, - observer: computed(() => someOtherObservable.get()), - }), - - injectionToken: computedChannelObserverInjectionToken, - }); + describe("when the observed value changes", () => { + beforeEach(async () => { + latestValueMessage = undefined; runInAction(() => { - di2.register(channelObserver2Injectable); + someObservable.set("some-new-value-2"); }); + }); - const computedChannel = di1.inject( - computedChannelInjectionToken + it("when accessing the computed value outside of reactive context, throws", () => { + expect(() => { + computedTestChannel.get(); + }).toThrow( + 'Tried to access value of computed channel "some-channel-id" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.', ); + }); - computedTestChannel = computedChannel( - testChannel2, - "some-pending-value" - ); - - reaction( - () => computedTestChannel.get(), - (value) => { - observedValue = value; - }, - - { - fireImmediately: true, - } - ); - - scenarioIsAsync && - (await messageBridgeFake.messagePropagation()); - - stopObserving(); - - scenarioIsAsync && - (await messageBridgeFake.messagePropagation()); - - runInAction(() => { - someOtherObservable.set("some-value"); - }); - - scenarioIsAsync && - (await messageBridgeFake.messagePropagation()); - - expect(observedValue).toBe("some-value"); + it("no value gets listened in di-1 anymore", () => { + expect(latestValueMessage).toBeUndefined(); }); describe("when observing the computed channel again", () => { beforeEach(() => { - latestAdminMessage = undefined; + observedValue = undefined; reaction( () => computedTestChannel.get(), @@ -481,134 +314,256 @@ const testChannel2: MessageChannel = { id: "some-other-channel-id" }; { fireImmediately: true, - } + }, ); }); - it("doesn't send second administration message", () => { - expect(latestAdminMessage).toBeUndefined(); - }); + scenarioIsAsync && + it("computed test channel value is observed as the pending value again", () => { + expect(observedValue).toBe("some-pending-value"); + }); - it("when one of the observations stops, doesn't send administration message to stop observing", async () => { - latestAdminMessage = undefined; + const scenarioName = scenarioIsAsync + ? "when admin messages propagate" + : "immediately"; - stopObserving(); + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + beforeEach((done) => { + if (scenarioIsAsync) { + latestAdminMessage = undefined; - expect(latestAdminMessage).toBeUndefined(); - }); - }); + void messageBridgeFake.messagePropagation().then(done); + } else { + done(); + } + }); - describe("when accessing the computed value outside of reactive context", () => { - let nonReactiveValue: string; + it("administration-message to start observing gets listened again", () => { + expect(latestAdminMessage).toEqual({ + channelId: "some-channel-id", + status: "became-observed", + }); + }); - beforeEach(() => { - latestValueMessage = undefined; - latestAdminMessage = undefined; + scenarioIsAsync && + it("computed test channel value is still observed as the pending value", () => { + expect(observedValue).toBe("some-pending-value"); + }); - nonReactiveValue = computedTestChannel.get(); - }); + const scenarioTitle = scenarioIsAsync + ? "when value-messages propagate back" + : "immediately"; - it("the non reactive value is what ever happens to be the current value from di-2", () => { - expect(nonReactiveValue).toBe("some-initial-value"); - }); - - describe( - scenarioIsAsync - ? "when messages would be propagated" - : "immediately", - () => { + // eslint-disable-next-line jest/valid-title + describe(scenarioTitle, () => { beforeEach((done) => { if (scenarioIsAsync) { - messageBridgeFake.messagePropagation().then(done); + latestValueMessage = undefined; + + void messageBridgeFake.messagePropagation().then(done); } else { done(); } }); - it("does not send new value message", () => { - expect(latestValueMessage).toBeUndefined(); + it("the computed channel value changes", () => { + expect(observedValue).toBe("some-new-value-2"); }); - it("does not send new admin message", () => { - expect(latestAdminMessage).toBeUndefined(); + it("the current value gets listened", () => { + expect(latestValueMessage).toBe("some-new-value-2"); }); - } - ); + }); + }); }); - } - ); - } - ); - }); + }); + }); - it("when accessing the computed value outside of reactive context, throws", () => { - expect(() => { - computedTestChannel.get(); - }).toThrow( - 'Tried to access value of computed channel "some-channel-id" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.' - ); - }); - - it("given duplicate channel observer for the channel is registered, when the computed channel is observer, throws", () => { - const duplicateChannelObserverInjectable = getInjectable({ - id: "some-duplicate-channel-observer", - - instantiate: () => ({ - channel: testChannel, - observer: computed(() => "irrelevant"), - }), - - injectionToken: computedChannelObserverInjectionToken, - }); - - expect(() => { - runWithThrownMobxReactions(() => { - runInAction(() => { - di2.register(duplicateChannelObserverInjectable); + it("when accessing the computed value outside of reactive context, throws", () => { + expect(() => { + computedTestChannel.get(); + }).toThrow( + 'Tried to access value of computed channel "some-channel-id" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.', + ); }); }); - }).toThrow( - 'Tried to register duplicate channel observer for channels "some-channel-id"' - ); + + it("given observation of unrelated computed channel is stopped, observation of other computed channel still works", async () => { + const someOtherObservable = observable.box(""); + + const channelObserver2Injectable = getInjectable({ + id: "some-channel-observer-2", + + instantiate: () => ({ + channel: testChannel2, + observer: computed(() => someOtherObservable.get()), + }), + + injectionToken: computedChannelObserverInjectionToken, + }); + + runInAction(() => { + di2.register(channelObserver2Injectable); + }); + + const computedChannel = di1.inject(computedChannelInjectionToken); + + computedTestChannel = computedChannel(testChannel2, "some-pending-value"); + + reaction( + () => computedTestChannel.get(), + (value) => { + observedValue = value; + }, + + { + fireImmediately: true, + }, + ); + + scenarioIsAsync && (await messageBridgeFake.messagePropagation()); + + stopObserving(); + + scenarioIsAsync && (await messageBridgeFake.messagePropagation()); + + runInAction(() => { + someOtherObservable.set("some-value"); + }); + + scenarioIsAsync && (await messageBridgeFake.messagePropagation()); + + expect(observedValue).toBe("some-value"); + }); + + describe("when observing the computed channel again", () => { + beforeEach(() => { + latestAdminMessage = undefined; + + reaction( + () => computedTestChannel.get(), + (value) => { + observedValue = value; + }, + + { + fireImmediately: true, + }, + ); + }); + + it("doesn't send second administration message", () => { + expect(latestAdminMessage).toBeUndefined(); + }); + + it("when one of the observations stops, doesn't send administration message to stop observing", async () => { + latestAdminMessage = undefined; + + stopObserving(); + + expect(latestAdminMessage).toBeUndefined(); + }); + }); + + describe("when accessing the computed value outside of reactive context", () => { + let nonReactiveValue: string; + + beforeEach(() => { + latestValueMessage = undefined; + latestAdminMessage = undefined; + + nonReactiveValue = computedTestChannel.get(); + }); + + it("the non reactive value is what ever happens to be the current value from di-2", () => { + expect(nonReactiveValue).toBe("some-initial-value"); + }); + + const scenarioName = scenarioIsAsync + ? "when messages would be propagated" + : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + beforeEach((done) => { + if (scenarioIsAsync) { + void messageBridgeFake.messagePropagation().then(done); + } else { + done(); + } + }); + + it("does not send new value message", () => { + expect(latestValueMessage).toBeUndefined(); + }); + + it("does not send new admin message", () => { + expect(latestAdminMessage).toBeUndefined(); + }); + }); + }); + }); }); }); - describe("given no channel observer but still a computed channel", () => { - let computedTestChannel: IComputedValue; + it("when accessing the computed value outside of reactive context, throws", () => { + expect(() => { + computedTestChannel.get(); + }).toThrow( + 'Tried to access value of computed channel "some-channel-id" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.', + ); + }); - beforeEach(() => { - const computedChannel = di1.inject(computedChannelInjectionToken); + it("given duplicate channel observer for the channel is registered, when the computed channel is observer, throws", () => { + const duplicateChannelObserverInjectable = getInjectable({ + id: "some-duplicate-channel-observer", - computedTestChannel = computedChannel( - testChannel, - "some-pending-value" - ); + instantiate: () => ({ + channel: testChannel, + observer: computed(() => "irrelevant"), + }), + + injectionToken: computedChannelObserverInjectionToken, }); - it("when the computed channel is observed, observes as undefined", () => { - let observedValue = "some-value-to-never-be-seen-in-unit-test"; - - reaction( - () => computedTestChannel.get(), - - (value) => { - observedValue = value; - }, - - { - fireImmediately: true, - } - ); - - expect(observedValue).toBe("some-pending-value"); - }); + expect(() => { + runWithThrownMobxReactions(() => { + runInAction(() => { + di2.register(duplicateChannelObserverInjectable); + }); + }); + }).toThrow('Tried to register duplicate channel observer for channels "some-channel-id"'); }); }); - }) -); -const TestComponent = observer( - ({ someComputed }: { someComputed: IComputedValue }) => ( -
{someComputed.get()}
- ) + describe("given no channel observer but still a computed channel", () => { + let computedTestChannel: IComputedValue; + + beforeEach(() => { + const computedChannel = di1.inject(computedChannelInjectionToken); + + computedTestChannel = computedChannel(testChannel, "some-pending-value"); + }); + + it("when the computed channel is observed, observes as undefined", () => { + let observedValue = "some-value-to-never-be-seen-in-unit-test"; + + reaction( + () => computedTestChannel.get(), + + (value) => { + observedValue = value; + }, + + { + fireImmediately: true, + }, + ); + + expect(observedValue).toBe("some-pending-value"); + }); + }); + }); + }), ); diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/feature.ts b/packages/technical-features/messaging/agnostic/src/features/actual/feature.ts index e520892217..5f302e1bcd 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/feature.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/feature.ts @@ -12,9 +12,7 @@ export const messagingFeature = getFeature({ di, targetModule: module, - getRequireContexts: () => [ - require.context("./", true, /\.injectable\.(ts|tsx)$/), - ], + getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)], }); }, }); diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts index b5da24466a..c28d6cbc5a 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts @@ -1,10 +1,7 @@ import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; import { enlistMessageChannelListenerInjectionToken } from "../message/enlist-message-channel-listener-injection-token"; -import { - getStartableStoppable, - StartableStoppable, -} from "@k8slens/startable-stoppable"; +import { getStartableStoppable, StartableStoppable } from "@k8slens/startable-stoppable"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; import { IComputedValue, reaction } from "mobx"; @@ -15,62 +12,14 @@ import { enlistRequestChannelListenerInjectionToken } from "../request/enlist-re import type { Channel } from "../channel.no-coverage"; export type ListeningOfChannels = StartableStoppable; -export const listeningOfChannelsInjectionToken = - getInjectionToken({ - id: "listening-of-channels-injection-token", - }); - -const listeningOfChannelsInjectable = getInjectable({ - id: "listening-of-channels", - - instantiate: (di) => { - const enlistMessageChannelListener = di.inject( - enlistMessageChannelListenerInjectionToken - ); - - const enlistRequestChannelListener = di.inject( - enlistRequestChannelListenerInjectionToken - ); - - const computedInjectMany = di.inject(computedInjectManyInjectable); - - const messageChannelListeners = computedInjectMany( - messageChannelListenerInjectionToken - ); - - const requestChannelListeners = computedInjectMany( - requestChannelListenerInjectionToken - ); - - return getStartableStoppable("listening-of-channels", () => { - const stopListeningOfMessageChannels = listening( - messageChannelListeners, - enlistMessageChannelListener, - (x) => x.id - ); - - const stopListeningOfRequestChannels = listening( - requestChannelListeners, - enlistRequestChannelListener, - (x) => x.channel.id - ); - - return () => { - stopListeningOfMessageChannels(); - stopListeningOfRequestChannels(); - }; - }); - }, - - injectionToken: listeningOfChannelsInjectionToken, +export const listeningOfChannelsInjectionToken = getInjectionToken({ + id: "listening-of-channels-injection-token", }); -export default listeningOfChannelsInjectable; - const listening = }>( channelListeners: IComputedValue, enlistChannelListener: (listener: T) => () => void, - getId: (listener: T) => string + getId: (listener: T) => string, ) => { const listenerDisposers = new Map void>(); @@ -78,11 +27,11 @@ const listening = }>( () => channelListeners.get(), (newValues, oldValues = []) => { const addedListeners = newValues.filter( - (newValue) => !oldValues.some((oldValue) => oldValue.id === newValue.id) + (newValue) => !oldValues.some((oldValue) => oldValue.id === newValue.id), ); const removedListeners = oldValues.filter( - (oldValue) => !newValues.some((newValue) => newValue.id === oldValue.id) + (oldValue) => !newValues.some((newValue) => newValue.id === oldValue.id), ); addedListeners.forEach((listener) => { @@ -90,7 +39,7 @@ const listening = }>( if (listenerDisposers.has(id)) { throw new Error( - `Tried to add listener for channel "${listener.channel.id}" but listener already exists.` + `Tried to add listener for channel "${listener.channel.id}" but listener already exists.`, ); } @@ -108,7 +57,7 @@ const listening = }>( }); }, - { fireImmediately: true } + { fireImmediately: true }, ); return () => { @@ -116,3 +65,42 @@ const listening = }>( listenerDisposers.forEach((dispose) => dispose()); }; }; + +const listeningOfChannelsInjectable = getInjectable({ + id: "listening-of-channels", + + instantiate: (di) => { + const enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken); + + const enlistRequestChannelListener = di.inject(enlistRequestChannelListenerInjectionToken); + + const computedInjectMany = di.inject(computedInjectManyInjectable); + + const messageChannelListeners = computedInjectMany(messageChannelListenerInjectionToken); + + const requestChannelListeners = computedInjectMany(requestChannelListenerInjectionToken); + + return getStartableStoppable("listening-of-channels", () => { + const stopListeningOfMessageChannels = listening( + messageChannelListeners, + enlistMessageChannelListener, + (x) => x.id, + ); + + const stopListeningOfRequestChannels = listening( + requestChannelListeners, + enlistRequestChannelListener, + (x) => x.channel.id, + ); + + return () => { + stopListeningOfMessageChannels(); + stopListeningOfRequestChannels(); + }; + }); + }, + + injectionToken: listeningOfChannelsInjectionToken, +}); + +export default listeningOfChannelsInjectable; diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts index 9169dab74d..9ec6f8b93a 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts @@ -6,7 +6,7 @@ import type { } from "./message-channel-listener-injection-token"; export type EnlistMessageChannelListener = ( - listener: MessageChannelListener> + listener: MessageChannelListener>, ) => () => void; export const enlistMessageChannelListenerInjectionToken = diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/message/get-message-channel.ts b/packages/technical-features/messaging/agnostic/src/features/actual/message/get-message-channel.ts index aee2413d07..076a7af464 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/message/get-message-channel.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/message/get-message-channel.ts @@ -1,7 +1,5 @@ import type { MessageChannel } from "./message-channel-listener-injection-token"; -export const getMessageChannel = ( - id: string -): MessageChannel => ({ +export const getMessageChannel = (id: string): MessageChannel => ({ id, }); diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts index 9df97a75a6..6343d281a7 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts @@ -6,9 +6,7 @@ export interface MessageChannel { _messageSignature?: Message; } -export type MessageChannelHandler = Channel extends MessageChannel< - infer Message -> +export type MessageChannelHandler = Channel extends MessageChannel ? (message: Message) => void : never; @@ -24,10 +22,7 @@ export const messageChannelListenerInjectionToken = getInjectionToken< id: "message-channel-listener", }); -export interface GetMessageChannelListenerInfo< - Channel extends MessageChannel, - Message -> { +export interface GetMessageChannelListenerInfo, Message> { id: string; channel: Channel; getHandler: (di: DiContainerForInjection) => MessageChannelHandler; @@ -36,9 +31,9 @@ export interface GetMessageChannelListenerInfo< export const getMessageChannelListenerInjectable = < Channel extends MessageChannel, - Message + Message, >( - info: GetMessageChannelListenerInfo + info: GetMessageChannelListenerInfo, ) => getInjectable({ id: `${info.channel.id}-message-listener-${info.id}`, diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/message/message-to-channel-injection-token.no-coverage.ts b/packages/technical-features/messaging/agnostic/src/features/actual/message/message-to-channel-injection-token.no-coverage.ts index 84a0478e69..9cb5df4986 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/message/message-to-channel-injection-token.no-coverage.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/message/message-to-channel-injection-token.no-coverage.ts @@ -6,7 +6,6 @@ export interface SendMessageToChannel { (channel: MessageChannel, message: Message): void; } -export const sendMessageToChannelInjectionToken = - getInjectionToken({ - id: "send-message-to-message-channel", - }); +export const sendMessageToChannelInjectionToken = getInjectionToken({ + id: "send-message-to-message-channel", +}); diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts index 420305341b..cdb3ac97d5 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts @@ -6,7 +6,7 @@ import type { } from "./request-channel-listener-injection-token"; export type EnlistRequestChannelListener = ( - listener: RequestChannelListener> + listener: RequestChannelListener>, ) => () => void; export const enlistRequestChannelListenerInjectionToken = diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/request/get-request-channel.ts b/packages/technical-features/messaging/agnostic/src/features/actual/request/get-request-channel.ts index 90bf947a99..c0ee40bcf4 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/request/get-request-channel.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/request/get-request-channel.ts @@ -1,7 +1,7 @@ import type { RequestChannel } from "./request-channel-listener-injection-token"; export const getRequestChannel = ( - id: string + id: string, ): RequestChannel => ({ id, }); diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts index 095c1bb713..2ec76ff546 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts @@ -29,7 +29,7 @@ export const requestChannelListenerInjectionToken = getInjectionToken< export interface GetRequestChannelListenerInjectableInfo< Channel extends RequestChannel, Request, - Response + Response, > { id: string; channel: Channel; @@ -39,9 +39,9 @@ export interface GetRequestChannelListenerInjectableInfo< export const getRequestChannelListenerInjectable = < Channel extends RequestChannel, Request, - Response + Response, >( - info: GetRequestChannelListenerInjectableInfo + info: GetRequestChannelListenerInjectableInfo, ) => getInjectable({ id: `${info.channel.id}-request-listener-${info.id}`, diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/request/request-from-channel-injection-token.no-coverage.ts b/packages/technical-features/messaging/agnostic/src/features/actual/request/request-from-channel-injection-token.no-coverage.ts index 665af0369e..194091b588 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/request/request-from-channel-injection-token.no-coverage.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/request/request-from-channel-injection-token.no-coverage.ts @@ -4,7 +4,7 @@ import type { RequestChannel } from "./request-channel-listener-injection-token" export interface RequestFromChannel { ( channel: RequestChannel, - request: Request + request: Request, ): Promise; (channel: RequestChannel): Promise; } @@ -16,7 +16,6 @@ export type ChannelRequester = Channel extends RequestChannel< ? (req: Request) => Promise> : never; -export const requestFromChannelInjectionToken = - getInjectionToken({ - id: "request-from-request-channel", - }); +export const requestFromChannelInjectionToken = getInjectionToken({ + id: "request-from-request-channel", +}); diff --git a/packages/technical-features/messaging/agnostic/src/features/unit-testing/feature.ts b/packages/technical-features/messaging/agnostic/src/features/unit-testing/feature.ts index f8a0774fed..ca6ddd2b2e 100644 --- a/packages/technical-features/messaging/agnostic/src/features/unit-testing/feature.ts +++ b/packages/technical-features/messaging/agnostic/src/features/unit-testing/feature.ts @@ -12,9 +12,7 @@ export const messagingFeatureForUnitTesting = getFeature({ di, targetModule: module, - getRequireContexts: () => [ - require.context("./", true, /\.injectable\.(ts|tsx)$/), - ], + getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)], }); }, }); diff --git a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts index afd9557e1c..b5fc7b7b88 100644 --- a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts +++ b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts @@ -1,8 +1,4 @@ -import { - createContainer, - DiContainer, - Injectable, -} from "@ogre-tools/injectable"; +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 { @@ -24,407 +20,357 @@ 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 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", }; + +[{ 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"); + }); + + const scenarioTitle = scenarioIsAsync + ? "when all message steps are propagated using a wrapper" + : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioTitle, () => { + 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); + }); + }); + + const scenarioName: string = scenarioIsAsync + ? "when all message steps are propagated not using a wrapper" + : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + 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(); + }); + + const scenarioName = scenarioIsAsync ? "when messages are propagated" : "immediately"; + + // eslint-disable-next-line jest/valid-title + describe(scenarioName, () => { + 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', + ); + }); + }); + }); + }), +); diff --git a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts index e674966214..2927e43faf 100644 --- a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts +++ b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts @@ -20,6 +20,126 @@ export type MessageBridgeFake = { setAsync: (value: boolean) => void; }; +const overrideMessaging = ({ + di, + messageListenersByDi, + messagePropagationBuffer, + getAsyncModeStatus, +}: { + di: DiContainer; + + messageListenersByDi: Map>>>; + + messagePropagationBuffer: Set<{ resolve: () => Promise }>; + + getAsyncModeStatus: () => boolean; +}) => { + const messageHandlersByChannel = new Map>>(); + + messageListenersByDi.set(di, messageHandlersByChannel); + + di.override(sendMessageToChannelInjectionToken, () => (channel, message) => { + const allOtherDis = [...messageListenersByDi.keys()].filter((x) => x !== di); + + allOtherDis.forEach((otherDi) => { + const listeners = messageListenersByDi.get(otherDi); + + const handlersForChannel = listeners?.get(channel.id); + + if (!handlersForChannel) { + return; + } + + if (getAsyncModeStatus()) { + const resolvableHandlePromise = asyncFn(); + + resolvableHandlePromise().then(() => { + handlersForChannel.forEach((handler) => handler(message)); + }); + + messagePropagationBuffer.add(resolvableHandlePromise); + } else { + handlersForChannel.forEach((handler) => handler(message)); + } + }); + }); + + di.override(enlistMessageChannelListenerInjectionToken, () => (listener) => { + if (!messageHandlersByChannel.has(listener.channel.id)) { + messageHandlersByChannel.set(listener.channel.id, new Set()); + } + + const handlerSet = messageHandlersByChannel.get(listener.channel.id); + + handlerSet?.add(listener.handler); + + return () => { + handlerSet?.delete(listener.handler); + }; + }); +}; + +const overrideRequesting = ({ + di, + requestListenersByDi, +}: { + di: DiContainer; + + requestListenersByDi: Map>>>; +}) => { + const requestHandlersByChannel = new Map>>(); + + requestListenersByDi.set(di, requestHandlersByChannel); + + di.override( + requestFromChannelInjectionToken, + () => + (async (channel, request) => + pipeline( + [...requestListenersByDi.values()], + map((listenersByChannel) => listenersByChannel?.get(channel.id)), + filter((x) => !!x), + + (channelSpecificListeners) => { + if (channelSpecificListeners.length === 0) { + throw new Error( + `Tried to make a request but no listeners for channel "${channel.id}" was discovered in any DIs`, + ); + } + + if (channelSpecificListeners.length > 1) { + throw new Error( + `Tried to make a request but multiple listeners were discovered for channel "${channel.id}" in multiple DIs.`, + ); + } + + const listeners = channelSpecificListeners[0]; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const [handler] = listeners!; + + return handler; + }, + + async (handler) => handler(request), + )) as RequestFromChannel, + ); + + di.override(enlistRequestChannelListenerInjectionToken, () => (listener) => { + if (!requestHandlersByChannel.has(listener.channel.id)) { + requestHandlersByChannel.set(listener.channel.id, new Set()); + } + + const handlerSet = requestHandlersByChannel.get(listener.channel.id); + + handlerSet?.add(listener.handler); + + return () => { + handlerSet?.delete(listener.handler); + }; + }); +}; + export const getMessageBridgeFake = (): MessageBridgeFake => { const messageListenersByDi = new Map< DiContainer, @@ -33,16 +153,15 @@ export const getMessageBridgeFake = (): MessageBridgeFake => { const messagePropagationBuffer = new Set void>>(); - const messagePropagation = async ( - wrapper: (callback: any) => any = (callback) => callback() - ) => { + const messagePropagation = async (wrapper: (callback: any) => any = (callback) => callback()) => { const oldMessages = [...messagePropagationBuffer.values()]; + messagePropagationBuffer.clear(); await Promise.all(oldMessages.map((x) => wrapper(x.resolve))); }; const messagePropagationRecursive = async ( - wrapper: (callback: any) => any = (callback) => callback() + wrapper: (callback: any) => any = (callback) => callback(), ) => { while (messagePropagationBuffer.size) { await messagePropagation(wrapper); @@ -75,136 +194,3 @@ export const getMessageBridgeFake = (): MessageBridgeFake => { }, }; }; - -const overrideMessaging = ({ - di, - messageListenersByDi, - messagePropagationBuffer, - getAsyncModeStatus, -}: { - di: DiContainer; - - messageListenersByDi: Map< - DiContainer, - Map>> - >; - - messagePropagationBuffer: Set<{ resolve: () => Promise }>; - - getAsyncModeStatus: () => boolean; -}) => { - const messageHandlersByChannel = new Map< - string, - Set> - >(); - - messageListenersByDi.set(di, messageHandlersByChannel); - - di.override(sendMessageToChannelInjectionToken, () => (channel, message) => { - const allOtherDis = [...messageListenersByDi.keys()].filter( - (x) => x !== di - ); - - allOtherDis.forEach((otherDi) => { - const listeners = messageListenersByDi.get(otherDi); - - const handlersForChannel = listeners!.get(channel.id); - - if (!handlersForChannel) { - return; - } - - if (getAsyncModeStatus()) { - const resolvableHandlePromise = asyncFn(); - - resolvableHandlePromise().then(() => { - handlersForChannel.forEach((handler) => handler(message)); - }); - - messagePropagationBuffer.add(resolvableHandlePromise); - } else { - handlersForChannel.forEach((handler) => handler(message)); - } - }); - }); - - di.override(enlistMessageChannelListenerInjectionToken, () => (listener) => { - if (!messageHandlersByChannel.has(listener.channel.id)) { - messageHandlersByChannel.set(listener.channel.id, new Set()); - } - - const handlerSet = messageHandlersByChannel.get(listener.channel.id); - - handlerSet!.add(listener.handler); - - return () => { - handlerSet!.delete(listener.handler); - }; - }); -}; - -const overrideRequesting = ({ - di, - requestListenersByDi, -}: { - di: DiContainer; - - requestListenersByDi: Map< - DiContainer, - Map>> - >; -}) => { - const requestHandlersByChannel = new Map< - string, - Set> - >(); - - requestListenersByDi.set(di, requestHandlersByChannel); - - di.override( - requestFromChannelInjectionToken, - () => - (async (channel, request) => - await pipeline( - [...requestListenersByDi.values()], - map((listenersByChannel) => listenersByChannel!.get(channel.id)), - filter((x) => !!x), - - (channelSpecificListeners) => { - if (channelSpecificListeners.length === 0) { - throw new Error( - `Tried to make a request but no listeners for channel "${channel.id}" was discovered in any DIs` - ); - } - - if (channelSpecificListeners.length > 1) { - throw new Error( - `Tried to make a request but multiple listeners were discovered for channel "${channel.id}" in multiple DIs.` - ); - } - - const listeners = channelSpecificListeners[0]; - - const [handler] = listeners!; - - return handler; - }, - - async (handler) => await handler(request) - )) as RequestFromChannel - ); - - di.override(enlistRequestChannelListenerInjectionToken, () => (listener) => { - if (!requestHandlersByChannel.has(listener.channel.id)) { - requestHandlersByChannel.set(listener.channel.id, new Set()); - } - - const handlerSet = requestHandlersByChannel.get(listener.channel.id); - - handlerSet!.add(listener.handler); - - return () => { - handlerSet!.delete(listener.handler); - }; - }); -}; diff --git a/packages/technical-features/messaging/agnostic/src/listening-of-messages.test.ts b/packages/technical-features/messaging/agnostic/src/listening-of-messages.test.ts index 67837263aa..8dc25fa41e 100644 --- a/packages/technical-features/messaging/agnostic/src/listening-of-messages.test.ts +++ b/packages/technical-features/messaging/agnostic/src/listening-of-messages.test.ts @@ -1,8 +1,4 @@ -import { - createContainer, - DiContainer, - Injectable, -} from "@ogre-tools/injectable"; +import { createContainer, DiContainer, Injectable } from "@ogre-tools/injectable"; import { registerFeature } from "@k8slens/feature-core"; import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; @@ -86,7 +82,7 @@ describe("listening-of-messages", () => { await startApplication(); }); - it("it enlists a listener for the channel", () => { + it("enlists a listener for the channel", () => { expect(enlistMessageChannelListenerMock).toHaveBeenCalledWith({ id: "some-channel-id-message-listener-some-listener", channel: someChannel, diff --git a/packages/technical-features/messaging/agnostic/src/listening-of-requests.test.ts b/packages/technical-features/messaging/agnostic/src/listening-of-requests.test.ts index 1df95849c4..e708c983c8 100644 --- a/packages/technical-features/messaging/agnostic/src/listening-of-requests.test.ts +++ b/packages/technical-features/messaging/agnostic/src/listening-of-requests.test.ts @@ -90,7 +90,7 @@ describe("listening-of-requests", () => { await startApplication(); }); - it("it enlists a listener for the channel", () => { + it("enlists a listener for the channel", () => { expect(enlistRequestChannelListenerMock).toHaveBeenCalledWith({ id: "some-channel-id-request-listener-some-listener", channel: someChannel, diff --git a/packages/technical-features/messaging/agnostic/tsconfig.json b/packages/technical-features/messaging/agnostic/tsconfig.json index 1819203dc1..ec29a8f75f 100644 --- a/packages/technical-features/messaging/agnostic/tsconfig.json +++ b/packages/technical-features/messaging/agnostic/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "@k8slens/typescript/config/base.json", - "include": ["**/*.ts"] + "include": ["**/*.ts", "**/*.tsx"] } diff --git a/packages/technical-features/messaging/main/jest.config.js b/packages/technical-features/messaging/main/jest.config.js index 6d3d6ff231..c6074967eb 100644 --- a/packages/technical-features/messaging/main/jest.config.js +++ b/packages/technical-features/messaging/main/jest.config.js @@ -1,2 +1 @@ -module.exports = - require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode; +module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode; diff --git a/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts b/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts index 382be4ee6d..5b4f31be37 100644 --- a/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts +++ b/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts @@ -29,9 +29,7 @@ describe("enlist message channel listener in main", () => { di.override(ipcMainInjectable, () => ipcMainStub); - enlistMessageChannelListener = di.inject( - enlistMessageChannelListenerInjectionToken - ); + enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken); }); describe("when called", () => { @@ -53,10 +51,7 @@ describe("enlist message channel listener in main", () => { }); it("registers the listener", () => { - expect(onMock).toHaveBeenCalledWith( - "some-channel-id", - expect.any(Function) - ); + expect(onMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function)); }); it("does not de-register the listener yet", () => { @@ -75,10 +70,7 @@ describe("enlist message channel listener in main", () => { it("when disposing the listener, de-registers the listener", () => { disposer(); - expect(offMock).toHaveBeenCalledWith( - "some-channel-id", - expect.any(Function) - ); + expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function)); }); }); diff --git a/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.injectable.ts b/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.injectable.ts index a4a16cadc4..f46976a3f1 100644 --- a/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.injectable.ts +++ b/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.injectable.ts @@ -1,16 +1,11 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { IpcMainInvokeEvent } from "electron"; import ipcMainInjectable from "../ipc-main/ipc-main.injectable"; -import type { - RequestChannel, - RequestChannelListener, -} from "@k8slens/messaging"; +import type { RequestChannel, RequestChannelListener } from "@k8slens/messaging"; import { enlistRequestChannelListenerInjectionToken } from "@k8slens/messaging"; -export type EnlistRequestChannelListener = < - TChannel extends RequestChannel ->( - listener: RequestChannelListener +export type EnlistRequestChannelListener = >( + listener: RequestChannelListener, ) => () => void; const enlistRequestChannelListenerInjectable = getInjectable({ @@ -20,8 +15,7 @@ const enlistRequestChannelListenerInjectable = getInjectable({ const ipcMain = di.inject(ipcMainInjectable); return ({ channel, handler }) => { - const nativeHandleCallback = (_: IpcMainInvokeEvent, request: unknown) => - handler(request); + const nativeHandleCallback = (_: IpcMainInvokeEvent, request: unknown) => handler(request); ipcMain.handle(channel.id, nativeHandleCallback); diff --git a/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.test.ts b/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.test.ts index 432df6d130..68672f4e95 100644 --- a/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.test.ts +++ b/packages/technical-features/messaging/main/src/channel-listeners/enlist-request-channel-listener.test.ts @@ -37,9 +37,7 @@ describe("enlist request channel listener in main", () => { di.override(ipcMainInjectable, () => ipcMainStub); - enlistRequestChannelListener = di.inject( - enlistRequestChannelListenerInjectable - ); + enlistRequestChannelListener = di.inject(enlistRequestChannelListenerInjectable); }); describe("when called", () => { @@ -61,10 +59,7 @@ describe("enlist request channel listener in main", () => { }); it("registers the listener", () => { - expect(handleMock).toHaveBeenCalledWith( - "some-channel-id", - expect.any(Function) - ); + expect(handleMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function)); }); it("does not de-register the listener yet", () => { @@ -75,10 +70,7 @@ describe("enlist request channel listener in main", () => { let actualPromise: Promise; beforeEach(() => { - actualPromise = handleMock.mock.calls[0][1]( - {} as IpcMainInvokeEvent, - "some-request" - ); + actualPromise = handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, "some-request"); }); it("calls the handler with the request", () => { @@ -105,10 +97,7 @@ describe("enlist request channel listener in main", () => { it("when disposing the listener, de-registers the listener", () => { disposer(); - expect(offMock).toHaveBeenCalledWith( - "some-channel-id", - expect.any(Function) - ); + expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function)); }); }); diff --git a/packages/technical-features/messaging/main/src/feature.ts b/packages/technical-features/messaging/main/src/feature.ts index 925f9630ba..cf2ea25c84 100644 --- a/packages/technical-features/messaging/main/src/feature.ts +++ b/packages/technical-features/messaging/main/src/feature.ts @@ -9,9 +9,7 @@ export const messagingFeatureForMain = getFeature({ di, targetModule: module, - getRequireContexts: () => [ - require.context("./", true, /\.injectable\.(ts|tsx)$/), - ], + getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)], }); }, }); diff --git a/packages/technical-features/messaging/renderer/jest.config.js b/packages/technical-features/messaging/renderer/jest.config.js index 6d3d6ff231..c6074967eb 100644 --- a/packages/technical-features/messaging/renderer/jest.config.js +++ b/packages/technical-features/messaging/renderer/jest.config.js @@ -1,2 +1 @@ -module.exports = - require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode; +module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode; diff --git a/packages/technical-features/messaging/renderer/src/feature.ts b/packages/technical-features/messaging/renderer/src/feature.ts index 9cec963ebc..2799132348 100644 --- a/packages/technical-features/messaging/renderer/src/feature.ts +++ b/packages/technical-features/messaging/renderer/src/feature.ts @@ -9,9 +9,7 @@ export const messagingFeatureForRenderer = getFeature({ di, targetModule: module, - getRequireContexts: () => [ - require.context("./", true, /\.injectable\.(ts|tsx)$/), - ], + getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)], }); }, }); diff --git a/packages/technical-features/messaging/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts b/packages/technical-features/messaging/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts index abfc2b6fe9..dc89c7b3e7 100644 --- a/packages/technical-features/messaging/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts +++ b/packages/technical-features/messaging/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts @@ -29,9 +29,7 @@ describe("enlist message channel listener in renderer", () => { di.override(ipcRendererInjectable, () => ipcRendererStub); - enlistMessageChannelListener = di.inject( - enlistMessageChannelListenerInjectionToken - ); + enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken); }); describe("when called", () => { @@ -53,10 +51,7 @@ describe("enlist message channel listener in renderer", () => { }); it("registers the listener", () => { - expect(onMock).toHaveBeenCalledWith( - "some-channel-id", - expect.any(Function) - ); + expect(onMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function)); }); it("does not de-register the listener yet", () => { @@ -75,10 +70,7 @@ describe("enlist message channel listener in renderer", () => { it("when disposing the listener, de-registers the listener", () => { disposer(); - expect(offMock).toHaveBeenCalledWith( - "some-channel-id", - expect.any(Function) - ); + expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function)); }); }); diff --git a/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.injectable.ts b/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.injectable.ts index 7d46c0681c..032042198a 100644 --- a/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.injectable.ts +++ b/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.injectable.ts @@ -9,8 +9,7 @@ const requestFromChannelInjectable = getInjectable({ instantiate: (di) => { const invokeIpc = di.inject(invokeIpcInjectable); - return ((channel, request) => - invokeIpc(channel.id, request)) as RequestFromChannel; + return ((channel, request) => invokeIpc(channel.id, request)) as RequestFromChannel; }, injectionToken: requestFromChannelInjectionToken, diff --git a/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.test.ts b/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.test.ts index db5dcadff0..930040f117 100644 --- a/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.test.ts +++ b/packages/technical-features/messaging/renderer/src/requesting-of-requests/request-from-channel.test.ts @@ -34,10 +34,7 @@ describe("request-from-channel", () => { }); it("invokes ipcRenderer of Electron", () => { - expect(invokeIpcMock).toHaveBeenCalledWith( - "some-channel-id", - "some-request-payload" - ); + expect(invokeIpcMock).toHaveBeenCalledWith("some-channel-id", "some-request-payload"); }); it("when invoke resolves with response, resolves with said response", async () => { diff --git a/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.injectable.ts b/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.injectable.ts index 7ba5d51631..469a1548ab 100644 --- a/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.injectable.ts +++ b/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.injectable.ts @@ -1,9 +1,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import sendToIpcInjectable from "./send-to-ipc.injectable"; -import { - SendMessageToChannel, - sendMessageToChannelInjectionToken, -} from "@k8slens/messaging"; +import { SendMessageToChannel, sendMessageToChannelInjectionToken } from "@k8slens/messaging"; const messageToChannelInjectable = getInjectable({ id: "message-to-channel", diff --git a/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.test.ts b/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.test.ts index 7a73e68a57..07989cf16e 100644 --- a/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.test.ts +++ b/packages/technical-features/messaging/renderer/src/sending-of-messages/message-to-channel.test.ts @@ -22,9 +22,7 @@ describe("message-from-channel", () => { describe("when called", () => { beforeEach(() => { - const sendMessageToChannel = di.inject( - sendMessageToChannelInjectionToken - ); + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); const someChannel: MessageChannel = { id: "some-channel-id",