mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fixup
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
c9f16beb54
commit
88023e1f70
@ -1,2 +1 @@
|
|||||||
module.exports =
|
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
|
||||||
require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
|
|
||||||
|
|||||||
@ -20,19 +20,16 @@ import { filter, groupBy, map, nth, toPairs } from "lodash/fp";
|
|||||||
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||||
import type { JsonPrimitive } from "type-fest";
|
import type { JsonPrimitive } from "type-fest";
|
||||||
|
|
||||||
export type JsonifiableObject =
|
export type JsonifiableObject = { [Key in string]?: Jsonifiable } | { toJSON: () => Jsonifiable };
|
||||||
| { [Key in string]?: Jsonifiable }
|
|
||||||
| { toJSON: () => Jsonifiable };
|
|
||||||
export type JsonifiableArray = readonly Jsonifiable[];
|
export type JsonifiableArray = readonly Jsonifiable[];
|
||||||
export type Jsonifiable = JsonPrimitive | JsonifiableObject | JsonifiableArray;
|
export type Jsonifiable = JsonPrimitive | JsonifiableObject | JsonifiableArray;
|
||||||
|
|
||||||
export type ComputedChannelFactory = <T>(
|
export type ComputedChannelFactory = <T>(
|
||||||
channel: MessageChannel<T>,
|
channel: MessageChannel<T>,
|
||||||
pendingValue: T
|
pendingValue: T,
|
||||||
) => IComputedValue<T>;
|
) => IComputedValue<T>;
|
||||||
|
|
||||||
export const computedChannelInjectionToken =
|
export const computedChannelInjectionToken = getInjectionToken<ComputedChannelFactory>({
|
||||||
getInjectionToken<ComputedChannelFactory>({
|
|
||||||
id: "computed-channel-injection-token",
|
id: "computed-channel-injection-token",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,6 +48,10 @@ export const computedChannelObserverInjectionToken = getInjectionToken<
|
|||||||
id: "computed-channel-observer",
|
id: "computed-channel-observer",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const computedChannelAdministrationChannel: MessageChannel<ComputedChannelAdminMessage> = {
|
||||||
|
id: "computed-channel-administration-channel",
|
||||||
|
};
|
||||||
|
|
||||||
const computedChannelInjectable = getInjectable({
|
const computedChannelInjectable = getInjectable({
|
||||||
id: "computed-channel",
|
id: "computed-channel",
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ const computedChannelInjectable = getInjectable({
|
|||||||
|
|
||||||
if (!contextIsReactive) {
|
if (!contextIsReactive) {
|
||||||
throw new Error(
|
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),
|
groupBy((observer) => observer.channel.id),
|
||||||
toPairs,
|
toPairs,
|
||||||
filter(([, channelObservers]) => channelObservers.length > 1),
|
filter(([, channelObservers]) => channelObservers.length > 1),
|
||||||
map(nth(0))
|
map(nth(0)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (duplicateObserverChannelIds.length) {
|
if (duplicateObserverChannelIds.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Tried to register duplicate channel observer for channels "${duplicateObserverChannelIds.join(
|
`Tried to register duplicate channel observer for channels "${duplicateObserverChannelIds.join(
|
||||||
'", "'
|
'", "',
|
||||||
)}"`
|
)}"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -153,18 +154,10 @@ export const duplicateChannelObserverGuardInjectable = getInjectable({
|
|||||||
injectionToken: onLoadOfApplicationInjectionToken,
|
injectionToken: onLoadOfApplicationInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const computedChannelAdministrationChannel: MessageChannel<ComputedChannelAdminMessage> =
|
export const computedChannelAdministrationListenerInjectable = getMessageChannelListenerInjectable({
|
||||||
{
|
|
||||||
id: "computed-channel-administration-channel",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const computedChannelAdministrationListenerInjectable =
|
|
||||||
getMessageChannelListenerInjectable({
|
|
||||||
id: "computed-channel-administration",
|
id: "computed-channel-administration",
|
||||||
getHandler: (di) => {
|
getHandler: (di) => {
|
||||||
const sendMessageToChannel = di.inject(
|
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
sendMessageToChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
const disposersByChannelId = new Map<string, () => void>();
|
const disposersByChannelId = new Map<string, () => void>();
|
||||||
|
|
||||||
@ -172,10 +165,7 @@ export const computedChannelAdministrationListenerInjectable =
|
|||||||
if (message.status === "became-observed") {
|
if (message.status === "became-observed") {
|
||||||
const result = di
|
const result = di
|
||||||
.injectMany(computedChannelObserverInjectionToken)
|
.injectMany(computedChannelObserverInjectionToken)
|
||||||
.find(
|
.find((channelObserver) => channelObserver.channel.id === message.channelId);
|
||||||
(channelObserver) =>
|
|
||||||
channelObserver.channel.id === message.channelId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -189,18 +179,18 @@ export const computedChannelAdministrationListenerInjectable =
|
|||||||
id: message.channelId,
|
id: message.channelId,
|
||||||
},
|
},
|
||||||
|
|
||||||
observed
|
observed,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
disposersByChannelId.set(message.channelId, disposer);
|
disposersByChannelId.set(message.channelId, disposer);
|
||||||
} else {
|
} else {
|
||||||
const disposer = disposersByChannelId.get(message.channelId);
|
const disposer = disposersByChannelId.get(message.channelId);
|
||||||
|
|
||||||
disposer!();
|
disposer?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { act } from "@testing-library/react";
|
import { act } from "@testing-library/react";
|
||||||
import {
|
import { createContainer, DiContainer, getInjectable } from "@ogre-tools/injectable";
|
||||||
createContainer,
|
|
||||||
DiContainer,
|
|
||||||
getInjectable,
|
|
||||||
} from "@ogre-tools/injectable";
|
|
||||||
import {
|
import {
|
||||||
getMessageBridgeFake,
|
getMessageBridgeFake,
|
||||||
MessageBridgeFake,
|
MessageBridgeFake,
|
||||||
@ -35,8 +31,11 @@ import { observer } from "mobx-react";
|
|||||||
const testChannel: MessageChannel<string> = { id: "some-channel-id" };
|
const testChannel: MessageChannel<string> = { id: "some-channel-id" };
|
||||||
const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
||||||
|
|
||||||
[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach(
|
const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue<string> }) => (
|
||||||
({ scenarioIsAsync }) =>
|
<div>{someComputed.get()}</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach(({ scenarioIsAsync }) =>
|
||||||
describe(`computed-channel, given running message bridge fake as ${
|
describe(`computed-channel, given running message bridge fake as ${
|
||||||
scenarioIsAsync ? "async" : "sync"
|
scenarioIsAsync ? "async" : "sync"
|
||||||
}`, () => {
|
}`, () => {
|
||||||
@ -56,8 +55,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
registerMobX(di1);
|
registerMobX(di1);
|
||||||
registerMobX(di2);
|
registerMobX(di2);
|
||||||
|
|
||||||
const administrationChannelTestListenerInjectable =
|
const administrationChannelTestListenerInjectable = getMessageChannelListenerInjectable({
|
||||||
getMessageChannelListenerInjectable({
|
|
||||||
id: "administration-channel-test-listener",
|
id: "administration-channel-test-listener",
|
||||||
channel: computedChannelAdministrationChannel,
|
channel: computedChannelAdministrationChannel,
|
||||||
|
|
||||||
@ -66,8 +64,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const channelValueTestListenerInjectable =
|
const channelValueTestListenerInjectable = getMessageChannelListenerInjectable({
|
||||||
getMessageChannelListenerInjectable({
|
|
||||||
id: "test-channel-value-listener",
|
id: "test-channel-value-listener",
|
||||||
channel: testChannel,
|
channel: testChannel,
|
||||||
|
|
||||||
@ -118,10 +115,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
|
|
||||||
const computedChannel = di1.inject(computedChannelInjectionToken);
|
const computedChannel = di1.inject(computedChannelInjectionToken);
|
||||||
|
|
||||||
computedTestChannel = computedChannel(
|
computedTestChannel = computedChannel(testChannel, "some-pending-value");
|
||||||
testChannel,
|
|
||||||
"some-pending-value"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("there is no admin message yet", () => {
|
it("there is no admin message yet", () => {
|
||||||
@ -134,33 +128,25 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const render = renderFor(di2);
|
const render = renderFor(di2);
|
||||||
|
|
||||||
rendered = render(
|
rendered = render(<TestComponent someComputed={computedTestChannel} />);
|
||||||
<TestComponent someComputed={computedTestChannel} />
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync ? "when all messages are propagated" : "immediately";
|
||||||
scenarioIsAsync
|
|
||||||
? "when all messages are propagated"
|
// eslint-disable-next-line jest/valid-title
|
||||||
: "immediately",
|
describe(scenarioName, () => {
|
||||||
() => {
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake
|
messageBridgeFake.messagePropagationRecursive(act).then(done);
|
||||||
.messagePropagationRecursive(act)
|
|
||||||
.then(done);
|
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
expect(rendered.container).toHaveTextContent(
|
expect(rendered.container).toHaveTextContent("some-initial-value");
|
||||||
"some-initial-value"
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when observing the computed channel in di-1", () => {
|
describe("when observing the computed channel in di-1", () => {
|
||||||
@ -178,7 +164,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
|
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -187,14 +173,15 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
expect(observedValue).toBe("some-pending-value");
|
expect(observedValue).toBe("some-pending-value");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when admin messages are propagated"
|
? "when admin messages are propagated"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake.messagePropagation().then(done);
|
void messageBridgeFake.messagePropagation().then(done);
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -207,14 +194,15 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when returning value-messages propagate"
|
? "when returning value-messages propagate"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake.messagePropagation().then(done);
|
void messageBridgeFake.messagePropagation().then(done);
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -237,14 +225,15 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when value-messages propagate"
|
? "when value-messages propagate"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake.messagePropagation().then(done);
|
void messageBridgeFake.messagePropagation().then(done);
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -257,8 +246,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
it("the new value gets listened in di-1", () => {
|
it("the new value gets listened in di-1", () => {
|
||||||
expect(latestValueMessage).toBe("some-new-value");
|
expect(latestValueMessage).toBe("some-new-value");
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when stopping observation for the channel in di-1", () => {
|
describe("when stopping observation for the channel in di-1", () => {
|
||||||
@ -268,14 +256,15 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
stopObserving();
|
stopObserving();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when admin-messages propagate"
|
? "when admin-messages propagate"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake.messagePropagation().then(done);
|
void messageBridgeFake.messagePropagation().then(done);
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -305,7 +294,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
expect(() => {
|
expect(() => {
|
||||||
computedTestChannel.get();
|
computedTestChannel.get();
|
||||||
}).toThrow(
|
}).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.'
|
'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.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -325,29 +314,26 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
|
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
scenarioIsAsync &&
|
scenarioIsAsync &&
|
||||||
it("computed test channel value is observed as the pending value again", () => {
|
it("computed test channel value is observed as the pending value again", () => {
|
||||||
expect(observedValue).toBe(
|
expect(observedValue).toBe("some-pending-value");
|
||||||
"some-pending-value"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when admin messages propagate"
|
? "when admin messages propagate"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
latestAdminMessage = undefined;
|
latestAdminMessage = undefined;
|
||||||
|
|
||||||
messageBridgeFake
|
void messageBridgeFake.messagePropagation().then(done);
|
||||||
.messagePropagation()
|
|
||||||
.then(done);
|
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -362,53 +348,43 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
|
|
||||||
scenarioIsAsync &&
|
scenarioIsAsync &&
|
||||||
it("computed test channel value is still observed as the pending value", () => {
|
it("computed test channel value is still observed as the pending value", () => {
|
||||||
expect(observedValue).toBe(
|
expect(observedValue).toBe("some-pending-value");
|
||||||
"some-pending-value"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioTitle = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when value-messages propagate back"
|
? "when value-messages propagate back"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioTitle, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
latestValueMessage = undefined;
|
latestValueMessage = undefined;
|
||||||
|
|
||||||
messageBridgeFake
|
void messageBridgeFake.messagePropagation().then(done);
|
||||||
.messagePropagation()
|
|
||||||
.then(done);
|
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("the computed channel value changes", () => {
|
it("the computed channel value changes", () => {
|
||||||
expect(observedValue).toBe(
|
expect(observedValue).toBe("some-new-value-2");
|
||||||
"some-new-value-2"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("the current value gets listened", () => {
|
it("the current value gets listened", () => {
|
||||||
expect(latestValueMessage).toBe(
|
expect(latestValueMessage).toBe("some-new-value-2");
|
||||||
"some-new-value-2"
|
});
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
it("when accessing the computed value outside of reactive context, throws", () => {
|
it("when accessing the computed value outside of reactive context, throws", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
computedTestChannel.get();
|
computedTestChannel.get();
|
||||||
}).toThrow(
|
}).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.'
|
'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.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -431,14 +407,9 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
di2.register(channelObserver2Injectable);
|
di2.register(channelObserver2Injectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
const computedChannel = di1.inject(
|
const computedChannel = di1.inject(computedChannelInjectionToken);
|
||||||
computedChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
computedTestChannel = computedChannel(
|
computedTestChannel = computedChannel(testChannel2, "some-pending-value");
|
||||||
testChannel2,
|
|
||||||
"some-pending-value"
|
|
||||||
);
|
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
() => computedTestChannel.get(),
|
() => computedTestChannel.get(),
|
||||||
@ -448,23 +419,20 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
|
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
scenarioIsAsync &&
|
scenarioIsAsync && (await messageBridgeFake.messagePropagation());
|
||||||
(await messageBridgeFake.messagePropagation());
|
|
||||||
|
|
||||||
stopObserving();
|
stopObserving();
|
||||||
|
|
||||||
scenarioIsAsync &&
|
scenarioIsAsync && (await messageBridgeFake.messagePropagation());
|
||||||
(await messageBridgeFake.messagePropagation());
|
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
someOtherObservable.set("some-value");
|
someOtherObservable.set("some-value");
|
||||||
});
|
});
|
||||||
|
|
||||||
scenarioIsAsync &&
|
scenarioIsAsync && (await messageBridgeFake.messagePropagation());
|
||||||
(await messageBridgeFake.messagePropagation());
|
|
||||||
|
|
||||||
expect(observedValue).toBe("some-value");
|
expect(observedValue).toBe("some-value");
|
||||||
});
|
});
|
||||||
@ -481,7 +449,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
|
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -512,14 +480,15 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
expect(nonReactiveValue).toBe("some-initial-value");
|
expect(nonReactiveValue).toBe("some-initial-value");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when messages would be propagated"
|
? "when messages would be propagated"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake.messagePropagation().then(done);
|
void messageBridgeFake.messagePropagation().then(done);
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -532,20 +501,17 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
it("does not send new admin message", () => {
|
it("does not send new admin message", () => {
|
||||||
expect(latestAdminMessage).toBeUndefined();
|
expect(latestAdminMessage).toBeUndefined();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when accessing the computed value outside of reactive context, throws", () => {
|
it("when accessing the computed value outside of reactive context, throws", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
computedTestChannel.get();
|
computedTestChannel.get();
|
||||||
}).toThrow(
|
}).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.'
|
'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.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -567,9 +533,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
di2.register(duplicateChannelObserverInjectable);
|
di2.register(duplicateChannelObserverInjectable);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).toThrow(
|
}).toThrow('Tried to register duplicate channel observer for channels "some-channel-id"');
|
||||||
'Tried to register duplicate channel observer for channels "some-channel-id"'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -579,10 +543,7 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const computedChannel = di1.inject(computedChannelInjectionToken);
|
const computedChannel = di1.inject(computedChannelInjectionToken);
|
||||||
|
|
||||||
computedTestChannel = computedChannel(
|
computedTestChannel = computedChannel(testChannel, "some-pending-value");
|
||||||
testChannel,
|
|
||||||
"some-pending-value"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when the computed channel is observed, observes as undefined", () => {
|
it("when the computed channel is observed, observes as undefined", () => {
|
||||||
@ -597,18 +558,12 @@ const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
|||||||
|
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(observedValue).toBe("some-pending-value");
|
expect(observedValue).toBe("some-pending-value");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
}),
|
||||||
);
|
|
||||||
|
|
||||||
const TestComponent = observer(
|
|
||||||
({ someComputed }: { someComputed: IComputedValue<string> }) => (
|
|
||||||
<div>{someComputed.get()}</div>
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,9 +12,7 @@ export const messagingFeature = getFeature({
|
|||||||
di,
|
di,
|
||||||
targetModule: module,
|
targetModule: module,
|
||||||
|
|
||||||
getRequireContexts: () => [
|
getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
|
||||||
require.context("./", true, /\.injectable\.(ts|tsx)$/),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
|
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../message/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "../message/enlist-message-channel-listener-injection-token";
|
||||||
|
|
||||||
import {
|
import { getStartableStoppable, StartableStoppable } from "@k8slens/startable-stoppable";
|
||||||
getStartableStoppable,
|
|
||||||
StartableStoppable,
|
|
||||||
} from "@k8slens/startable-stoppable";
|
|
||||||
|
|
||||||
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||||
import { IComputedValue, reaction } from "mobx";
|
import { IComputedValue, reaction } from "mobx";
|
||||||
@ -15,62 +12,14 @@ import { enlistRequestChannelListenerInjectionToken } from "../request/enlist-re
|
|||||||
import type { Channel } from "../channel.no-coverage";
|
import type { Channel } from "../channel.no-coverage";
|
||||||
|
|
||||||
export type ListeningOfChannels = StartableStoppable;
|
export type ListeningOfChannels = StartableStoppable;
|
||||||
export const listeningOfChannelsInjectionToken =
|
export const listeningOfChannelsInjectionToken = getInjectionToken<ListeningOfChannels>({
|
||||||
getInjectionToken<ListeningOfChannels>({
|
|
||||||
id: "listening-of-channels-injection-token",
|
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 default listeningOfChannelsInjectable;
|
|
||||||
|
|
||||||
const listening = <T extends { id: string; channel: Channel<unknown> }>(
|
const listening = <T extends { id: string; channel: Channel<unknown> }>(
|
||||||
channelListeners: IComputedValue<T[]>,
|
channelListeners: IComputedValue<T[]>,
|
||||||
enlistChannelListener: (listener: T) => () => void,
|
enlistChannelListener: (listener: T) => () => void,
|
||||||
getId: (listener: T) => string
|
getId: (listener: T) => string,
|
||||||
) => {
|
) => {
|
||||||
const listenerDisposers = new Map<string, () => void>();
|
const listenerDisposers = new Map<string, () => void>();
|
||||||
|
|
||||||
@ -78,11 +27,11 @@ const listening = <T extends { id: string; channel: Channel<unknown> }>(
|
|||||||
() => channelListeners.get(),
|
() => channelListeners.get(),
|
||||||
(newValues, oldValues = []) => {
|
(newValues, oldValues = []) => {
|
||||||
const addedListeners = newValues.filter(
|
const addedListeners = newValues.filter(
|
||||||
(newValue) => !oldValues.some((oldValue) => oldValue.id === newValue.id)
|
(newValue) => !oldValues.some((oldValue) => oldValue.id === newValue.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
const removedListeners = oldValues.filter(
|
const removedListeners = oldValues.filter(
|
||||||
(oldValue) => !newValues.some((newValue) => newValue.id === oldValue.id)
|
(oldValue) => !newValues.some((newValue) => newValue.id === oldValue.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
addedListeners.forEach((listener) => {
|
addedListeners.forEach((listener) => {
|
||||||
@ -90,7 +39,7 @@ const listening = <T extends { id: string; channel: Channel<unknown> }>(
|
|||||||
|
|
||||||
if (listenerDisposers.has(id)) {
|
if (listenerDisposers.has(id)) {
|
||||||
throw new Error(
|
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 = <T extends { id: string; channel: Channel<unknown> }>(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
{ fireImmediately: true }
|
{ fireImmediately: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -116,3 +65,42 @@ const listening = <T extends { id: string; channel: Channel<unknown> }>(
|
|||||||
listenerDisposers.forEach((dispose) => dispose());
|
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;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type {
|
|||||||
} from "./message-channel-listener-injection-token";
|
} from "./message-channel-listener-injection-token";
|
||||||
|
|
||||||
export type EnlistMessageChannelListener = (
|
export type EnlistMessageChannelListener = (
|
||||||
listener: MessageChannelListener<MessageChannel<unknown>>
|
listener: MessageChannelListener<MessageChannel<unknown>>,
|
||||||
) => () => void;
|
) => () => void;
|
||||||
|
|
||||||
export const enlistMessageChannelListenerInjectionToken =
|
export const enlistMessageChannelListenerInjectionToken =
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import type { MessageChannel } from "./message-channel-listener-injection-token";
|
import type { MessageChannel } from "./message-channel-listener-injection-token";
|
||||||
|
|
||||||
export const getMessageChannel = <Request>(
|
export const getMessageChannel = <Request>(id: string): MessageChannel<Request> => ({
|
||||||
id: string
|
|
||||||
): MessageChannel<Request> => ({
|
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,9 +6,7 @@ export interface MessageChannel<Message> {
|
|||||||
_messageSignature?: Message;
|
_messageSignature?: Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageChannelHandler<Channel> = Channel extends MessageChannel<
|
export type MessageChannelHandler<Channel> = Channel extends MessageChannel<infer Message>
|
||||||
infer Message
|
|
||||||
>
|
|
||||||
? (message: Message) => void
|
? (message: Message) => void
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
@ -24,10 +22,7 @@ export const messageChannelListenerInjectionToken = getInjectionToken<
|
|||||||
id: "message-channel-listener",
|
id: "message-channel-listener",
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface GetMessageChannelListenerInfo<
|
export interface GetMessageChannelListenerInfo<Channel extends MessageChannel<Message>, Message> {
|
||||||
Channel extends MessageChannel<Message>,
|
|
||||||
Message
|
|
||||||
> {
|
|
||||||
id: string;
|
id: string;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
getHandler: (di: DiContainerForInjection) => MessageChannelHandler<Channel>;
|
getHandler: (di: DiContainerForInjection) => MessageChannelHandler<Channel>;
|
||||||
@ -36,9 +31,9 @@ export interface GetMessageChannelListenerInfo<
|
|||||||
|
|
||||||
export const getMessageChannelListenerInjectable = <
|
export const getMessageChannelListenerInjectable = <
|
||||||
Channel extends MessageChannel<Message>,
|
Channel extends MessageChannel<Message>,
|
||||||
Message
|
Message,
|
||||||
>(
|
>(
|
||||||
info: GetMessageChannelListenerInfo<Channel, Message>
|
info: GetMessageChannelListenerInfo<Channel, Message>,
|
||||||
) =>
|
) =>
|
||||||
getInjectable({
|
getInjectable({
|
||||||
id: `${info.channel.id}-message-listener-${info.id}`,
|
id: `${info.channel.id}-message-listener-${info.id}`,
|
||||||
|
|||||||
@ -6,7 +6,6 @@ export interface SendMessageToChannel {
|
|||||||
<Message>(channel: MessageChannel<Message>, message: Message): void;
|
<Message>(channel: MessageChannel<Message>, message: Message): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendMessageToChannelInjectionToken =
|
export const sendMessageToChannelInjectionToken = getInjectionToken<SendMessageToChannel>({
|
||||||
getInjectionToken<SendMessageToChannel>({
|
|
||||||
id: "send-message-to-message-channel",
|
id: "send-message-to-message-channel",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type {
|
|||||||
} from "./request-channel-listener-injection-token";
|
} from "./request-channel-listener-injection-token";
|
||||||
|
|
||||||
export type EnlistRequestChannelListener = (
|
export type EnlistRequestChannelListener = (
|
||||||
listener: RequestChannelListener<RequestChannel<unknown, unknown>>
|
listener: RequestChannelListener<RequestChannel<unknown, unknown>>,
|
||||||
) => () => void;
|
) => () => void;
|
||||||
|
|
||||||
export const enlistRequestChannelListenerInjectionToken =
|
export const enlistRequestChannelListenerInjectionToken =
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { RequestChannel } from "./request-channel-listener-injection-token";
|
import type { RequestChannel } from "./request-channel-listener-injection-token";
|
||||||
|
|
||||||
export const getRequestChannel = <Request, Response>(
|
export const getRequestChannel = <Request, Response>(
|
||||||
id: string
|
id: string,
|
||||||
): RequestChannel<Request, Response> => ({
|
): RequestChannel<Request, Response> => ({
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export const requestChannelListenerInjectionToken = getInjectionToken<
|
|||||||
export interface GetRequestChannelListenerInjectableInfo<
|
export interface GetRequestChannelListenerInjectableInfo<
|
||||||
Channel extends RequestChannel<Request, Response>,
|
Channel extends RequestChannel<Request, Response>,
|
||||||
Request,
|
Request,
|
||||||
Response
|
Response,
|
||||||
> {
|
> {
|
||||||
id: string;
|
id: string;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
@ -39,9 +39,9 @@ export interface GetRequestChannelListenerInjectableInfo<
|
|||||||
export const getRequestChannelListenerInjectable = <
|
export const getRequestChannelListenerInjectable = <
|
||||||
Channel extends RequestChannel<Request, Response>,
|
Channel extends RequestChannel<Request, Response>,
|
||||||
Request,
|
Request,
|
||||||
Response
|
Response,
|
||||||
>(
|
>(
|
||||||
info: GetRequestChannelListenerInjectableInfo<Channel, Request, Response>
|
info: GetRequestChannelListenerInjectableInfo<Channel, Request, Response>,
|
||||||
) =>
|
) =>
|
||||||
getInjectable({
|
getInjectable({
|
||||||
id: `${info.channel.id}-request-listener-${info.id}`,
|
id: `${info.channel.id}-request-listener-${info.id}`,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { RequestChannel } from "./request-channel-listener-injection-token"
|
|||||||
export interface RequestFromChannel {
|
export interface RequestFromChannel {
|
||||||
<Request, Response>(
|
<Request, Response>(
|
||||||
channel: RequestChannel<Request, Response>,
|
channel: RequestChannel<Request, Response>,
|
||||||
request: Request
|
request: Request,
|
||||||
): Promise<Response>;
|
): Promise<Response>;
|
||||||
<Response>(channel: RequestChannel<void, Response>): Promise<Response>;
|
<Response>(channel: RequestChannel<void, Response>): Promise<Response>;
|
||||||
}
|
}
|
||||||
@ -16,7 +16,6 @@ export type ChannelRequester<Channel> = Channel extends RequestChannel<
|
|||||||
? (req: Request) => Promise<Awaited<Response>>
|
? (req: Request) => Promise<Awaited<Response>>
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export const requestFromChannelInjectionToken =
|
export const requestFromChannelInjectionToken = getInjectionToken<RequestFromChannel>({
|
||||||
getInjectionToken<RequestFromChannel>({
|
|
||||||
id: "request-from-request-channel",
|
id: "request-from-request-channel",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,9 +12,7 @@ export const messagingFeatureForUnitTesting = getFeature({
|
|||||||
di,
|
di,
|
||||||
targetModule: module,
|
targetModule: module,
|
||||||
|
|
||||||
getRequireContexts: () => [
|
getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
|
||||||
require.context("./", true, /\.injectable\.(ts|tsx)$/),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { createContainer, DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||||
createContainer,
|
|
||||||
DiContainer,
|
|
||||||
Injectable,
|
|
||||||
} from "@ogre-tools/injectable";
|
|
||||||
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
|
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
|
||||||
import { registerFeature } from "@k8slens/feature-core/src/register-feature";
|
import { registerFeature } from "@k8slens/feature-core/src/register-feature";
|
||||||
import {
|
import {
|
||||||
@ -24,8 +20,19 @@ import { getRequestChannel } from "../../actual/request/get-request-channel";
|
|||||||
import { startApplicationInjectionToken } from "@k8slens/application";
|
import { startApplicationInjectionToken } from "@k8slens/application";
|
||||||
import { messagingFeatureForUnitTesting } from "../feature";
|
import { messagingFeatureForUnitTesting } from "../feature";
|
||||||
|
|
||||||
[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach(
|
type SomeMessageChannel = MessageChannel<string>;
|
||||||
({ scenarioIsAsync }) =>
|
type SomeRequestChannel = RequestChannel<string, number>;
|
||||||
|
|
||||||
|
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 ${
|
describe(`get-message-bridge-fake, given running as ${
|
||||||
scenarioIsAsync ? "async" : "sync"
|
scenarioIsAsync ? "async" : "sync"
|
||||||
}`, () => {
|
}`, () => {
|
||||||
@ -53,10 +60,7 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
registerFeature(someDi1, messagingFeatureForUnitTesting);
|
registerFeature(someDi1, messagingFeatureForUnitTesting);
|
||||||
registerFeature(someDi2, messagingFeatureForUnitTesting);
|
registerFeature(someDi2, messagingFeatureForUnitTesting);
|
||||||
registerFeature(
|
registerFeature(someDiWithoutListeners, messagingFeatureForUnitTesting);
|
||||||
someDiWithoutListeners,
|
|
||||||
messagingFeatureForUnitTesting
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
messageBridgeFake.involve(someDi1, someDi2, someDiWithoutListeners);
|
messageBridgeFake.involve(someDi1, someDi2, someDiWithoutListeners);
|
||||||
@ -115,15 +119,10 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
channel: someMessageChannel,
|
channel: someMessageChannel,
|
||||||
|
|
||||||
getHandler: (di) => {
|
getHandler: (di) => {
|
||||||
const sendMessage = di.inject(
|
const sendMessage = di.inject(sendMessageToChannelInjectionToken);
|
||||||
sendMessageToChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
return (message) => {
|
return (message) => {
|
||||||
sendMessage(
|
sendMessage(someMessageChannel, `some-response-to: ${message}`);
|
||||||
someMessageChannel,
|
|
||||||
`some-response-to: ${message}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -136,26 +135,25 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
describe("given a message is sent in di-1", () => {
|
describe("given a message is sent in di-1", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const sendMessageToChannelFromDi1 = someDi1.inject(
|
const sendMessageToChannelFromDi1 = someDi1.inject(
|
||||||
sendMessageToChannelInjectionToken
|
sendMessageToChannelInjectionToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendMessageToChannelFromDi1(someMessageChannel, "some-message");
|
sendMessageToChannelFromDi1(someMessageChannel, "some-message");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioTitle = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when all message steps are propagated using a wrapper"
|
? "when all message steps are propagated using a wrapper"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioTitle, () => {
|
||||||
let someWrapper: jest.Mock;
|
let someWrapper: jest.Mock;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
someWrapper = jest.fn((propagation) => propagation());
|
someWrapper = jest.fn((propagation) => propagation());
|
||||||
|
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake
|
messageBridgeFake.messagePropagationRecursive(someWrapper).then(done);
|
||||||
.messagePropagationRecursive(someWrapper)
|
|
||||||
.then(done);
|
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -163,7 +161,7 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
|
|
||||||
it("the response gets handled in di-1", () => {
|
it("the response gets handled in di-1", () => {
|
||||||
expect(someHandler1MockInDi1).toHaveBeenCalledWith(
|
expect(someHandler1MockInDi1).toHaveBeenCalledWith(
|
||||||
"some-response-to: some-message"
|
"some-response-to: some-message",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -171,19 +169,17 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
it("the wrapper gets called with the both propagations", () => {
|
it("the wrapper gets called with the both propagations", () => {
|
||||||
expect(someWrapper).toHaveBeenCalledTimes(2);
|
expect(someWrapper).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
describe(
|
const scenarioName: string = scenarioIsAsync
|
||||||
scenarioIsAsync
|
|
||||||
? "when all message steps are propagated not using a wrapper"
|
? "when all message steps are propagated not using a wrapper"
|
||||||
: "immediately",
|
: "immediately";
|
||||||
() => {
|
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake
|
messageBridgeFake.messagePropagationRecursive().then(done);
|
||||||
.messagePropagationRecursive()
|
|
||||||
.then(done);
|
|
||||||
} else {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -191,19 +187,16 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
|
|
||||||
it("the response gets handled in di-1", () => {
|
it("the response gets handled in di-1", () => {
|
||||||
expect(someHandler1MockInDi1).toHaveBeenCalledWith(
|
expect(someHandler1MockInDi1).toHaveBeenCalledWith(
|
||||||
"some-response-to: some-message"
|
"some-response-to: some-message",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when sending message in a DI", () => {
|
describe("when sending message in a DI", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const sendMessageToChannelFromDi1 = someDi1.inject(
|
const sendMessageToChannelFromDi1 = someDi1.inject(sendMessageToChannelInjectionToken);
|
||||||
sendMessageToChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
sendMessageToChannelFromDi1(someMessageChannel, "some-message");
|
sendMessageToChannelFromDi1(someMessageChannel, "some-message");
|
||||||
});
|
});
|
||||||
@ -218,9 +211,10 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
expect(someHandler2MockInDi2).not.toHaveBeenCalled();
|
expect(someHandler2MockInDi2).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(
|
const scenarioName = scenarioIsAsync ? "when messages are propagated" : "immediately";
|
||||||
scenarioIsAsync ? "when messages are propagated" : "immediately",
|
|
||||||
() => {
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
describe(scenarioName, () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
if (scenarioIsAsync) {
|
if (scenarioIsAsync) {
|
||||||
messageBridgeFake.messagePropagation().then(done);
|
messageBridgeFake.messagePropagation().then(done);
|
||||||
@ -230,16 +224,11 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("listeners in other than sending DIs handle the message", () => {
|
it("listeners in other than sending DIs handle the message", () => {
|
||||||
expect(someHandler1MockInDi2).toHaveBeenCalledWith(
|
expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message");
|
||||||
"some-message"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(someHandler2MockInDi2).toHaveBeenCalledWith(
|
expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message");
|
||||||
"some-message"
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
scenarioIsAsync &&
|
scenarioIsAsync &&
|
||||||
describe("when messages are propagated using a wrapper, such as act() in react testing lib", () => {
|
describe("when messages are propagated using a wrapper, such as act() in react testing lib", () => {
|
||||||
@ -256,13 +245,9 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("listeners still handle the message", () => {
|
it("listeners still handle the message", () => {
|
||||||
expect(someHandler1MockInDi2).toHaveBeenCalledWith(
|
expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message");
|
||||||
"some-message"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(someHandler2MockInDi2).toHaveBeenCalledWith(
|
expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message");
|
||||||
"some-message"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -272,9 +257,7 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
someDi2.deregister(someListener1InDi2);
|
someDi2.deregister(someListener1InDi2);
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendMessageToChannelFromDi1 = someDi1.inject(
|
const sendMessageToChannelFromDi1 = someDi1.inject(sendMessageToChannelInjectionToken);
|
||||||
sendMessageToChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
someHandler1MockInDi2.mockClear();
|
someHandler1MockInDi2.mockClear();
|
||||||
|
|
||||||
@ -285,13 +268,9 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("given there are request listeners", () => {
|
describe("given there are request listeners", () => {
|
||||||
let someHandler1MockInDi1: AsyncFnMock<
|
let someHandler1MockInDi1: AsyncFnMock<(message: string) => Promise<number>>;
|
||||||
(message: string) => Promise<number>
|
|
||||||
>;
|
|
||||||
|
|
||||||
let someHandler1MockInDi2: AsyncFnMock<
|
let someHandler1MockInDi2: AsyncFnMock<(message: string) => Promise<number>>;
|
||||||
(message: string) => Promise<number>
|
|
||||||
>;
|
|
||||||
|
|
||||||
let someListener1InDi2: Injectable<unknown, unknown>;
|
let someListener1InDi2: Injectable<unknown, unknown>;
|
||||||
let actualPromise: Promise<number>;
|
let actualPromise: Promise<number>;
|
||||||
@ -320,14 +299,9 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
|
|
||||||
describe("when requesting from a channel in a DI", () => {
|
describe("when requesting from a channel in a DI", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const requestFromChannelFromDi1 = someDi1.inject(
|
const requestFromChannelFromDi1 = someDi1.inject(requestFromChannelInjectionToken);
|
||||||
requestFromChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
actualPromise = requestFromChannelFromDi1(
|
actualPromise = requestFromChannelFromDi1(someRequestChannel, "some-request");
|
||||||
someRequestChannel,
|
|
||||||
"some-request"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("listener in requesting DI does not handle the request", () => {
|
it("listener in requesting DI does not handle the request", () => {
|
||||||
@ -335,9 +309,7 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("the listener in other than requesting DIs handle the request", () => {
|
it("the listener in other than requesting DIs handle the request", () => {
|
||||||
expect(someHandler1MockInDi2).toHaveBeenCalledWith(
|
expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-request");
|
||||||
"some-request"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not resolve yet", async () => {
|
it("does not resolve yet", async () => {
|
||||||
@ -360,9 +332,7 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
someDi2.deregister(someListener1InDi2);
|
someDi2.deregister(someListener1InDi2);
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendMessageToChannelFromDi1 = someDi1.inject(
|
const sendMessageToChannelFromDi1 = someDi1.inject(sendMessageToChannelInjectionToken);
|
||||||
sendMessageToChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
someHandler1MockInDi2.mockClear();
|
someHandler1MockInDi2.mockClear();
|
||||||
|
|
||||||
@ -372,8 +342,7 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("given there are multiple listeners between different DIs for same channel, when requesting, throws", () => {
|
it("given there are multiple listeners between different DIs for same channel, when requesting, throws", () => {
|
||||||
const someConflictingListenerInjectable =
|
const someConflictingListenerInjectable = getRequestChannelListenerInjectable({
|
||||||
getRequestChannelListenerInjectable({
|
|
||||||
id: "conflicting-listener",
|
id: "conflicting-listener",
|
||||||
channel: someRequestChannel,
|
channel: someRequestChannel,
|
||||||
getHandler: () => () => 84,
|
getHandler: () => () => 84,
|
||||||
@ -383,48 +352,25 @@ import { messagingFeatureForUnitTesting } from "../feature";
|
|||||||
someDi1.register(someConflictingListenerInjectable);
|
someDi1.register(someConflictingListenerInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestFromChannelFromDi2 = someDi2.inject(
|
const requestFromChannelFromDi2 = someDi2.inject(requestFromChannelInjectionToken);
|
||||||
requestFromChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
return expect(() =>
|
return expect(() =>
|
||||||
requestFromChannelFromDi2(someRequestChannel, "irrelevant")
|
requestFromChannelFromDi2(someRequestChannel, "irrelevant"),
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
'Tried to make a request but multiple listeners were discovered for channel "some-request-channel" in multiple DIs.'
|
'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", () => {
|
it("when requesting from channel without listener, throws", () => {
|
||||||
const requestFromChannel = someDi1.inject(
|
const requestFromChannel = someDi1.inject(requestFromChannelInjectionToken);
|
||||||
requestFromChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
return expect(() =>
|
return expect(() =>
|
||||||
requestFromChannel(
|
requestFromChannel(someRequestChannelWithoutListeners, "irrelevant"),
|
||||||
someRequestChannelWithoutListeners,
|
|
||||||
"irrelevant"
|
|
||||||
)
|
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
'Tried to make a request but no listeners for channel "some-request-channel-without-listeners" was discovered in any DIs'
|
'Tried to make a request but no listeners for channel "some-request-channel-without-listeners" was discovered in any DIs',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
type SomeMessageChannel = MessageChannel<string>;
|
|
||||||
type SomeRequestChannel = RequestChannel<string, number>;
|
|
||||||
|
|
||||||
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",
|
|
||||||
};
|
|
||||||
|
|||||||
@ -20,6 +20,126 @@ export type MessageBridgeFake = {
|
|||||||
setAsync: (value: boolean) => void;
|
setAsync: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const overrideMessaging = ({
|
||||||
|
di,
|
||||||
|
messageListenersByDi,
|
||||||
|
messagePropagationBuffer,
|
||||||
|
getAsyncModeStatus,
|
||||||
|
}: {
|
||||||
|
di: DiContainer;
|
||||||
|
|
||||||
|
messageListenersByDi: Map<DiContainer, Map<string, Set<MessageChannelHandler<Channel>>>>;
|
||||||
|
|
||||||
|
messagePropagationBuffer: Set<{ resolve: () => Promise<void> }>;
|
||||||
|
|
||||||
|
getAsyncModeStatus: () => boolean;
|
||||||
|
}) => {
|
||||||
|
const messageHandlersByChannel = new Map<string, Set<MessageChannelHandler<Channel>>>();
|
||||||
|
|
||||||
|
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<string, Set<MessageChannelHandler<Channel>>>>;
|
||||||
|
}) => {
|
||||||
|
const requestHandlersByChannel = new Map<string, Set<RequestChannelHandler<Channel>>>();
|
||||||
|
|
||||||
|
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 => {
|
export const getMessageBridgeFake = (): MessageBridgeFake => {
|
||||||
const messageListenersByDi = new Map<
|
const messageListenersByDi = new Map<
|
||||||
DiContainer,
|
DiContainer,
|
||||||
@ -33,16 +153,15 @@ export const getMessageBridgeFake = (): MessageBridgeFake => {
|
|||||||
|
|
||||||
const messagePropagationBuffer = new Set<AsyncFnMock<() => void>>();
|
const messagePropagationBuffer = new Set<AsyncFnMock<() => void>>();
|
||||||
|
|
||||||
const messagePropagation = async (
|
const messagePropagation = async (wrapper: (callback: any) => any = (callback) => callback()) => {
|
||||||
wrapper: (callback: any) => any = (callback) => callback()
|
|
||||||
) => {
|
|
||||||
const oldMessages = [...messagePropagationBuffer.values()];
|
const oldMessages = [...messagePropagationBuffer.values()];
|
||||||
|
|
||||||
messagePropagationBuffer.clear();
|
messagePropagationBuffer.clear();
|
||||||
await Promise.all(oldMessages.map((x) => wrapper(x.resolve)));
|
await Promise.all(oldMessages.map((x) => wrapper(x.resolve)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const messagePropagationRecursive = async (
|
const messagePropagationRecursive = async (
|
||||||
wrapper: (callback: any) => any = (callback) => callback()
|
wrapper: (callback: any) => any = (callback) => callback(),
|
||||||
) => {
|
) => {
|
||||||
while (messagePropagationBuffer.size) {
|
while (messagePropagationBuffer.size) {
|
||||||
await messagePropagation(wrapper);
|
await messagePropagation(wrapper);
|
||||||
@ -75,136 +194,3 @@ export const getMessageBridgeFake = (): MessageBridgeFake => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const overrideMessaging = ({
|
|
||||||
di,
|
|
||||||
messageListenersByDi,
|
|
||||||
messagePropagationBuffer,
|
|
||||||
getAsyncModeStatus,
|
|
||||||
}: {
|
|
||||||
di: DiContainer;
|
|
||||||
|
|
||||||
messageListenersByDi: Map<
|
|
||||||
DiContainer,
|
|
||||||
Map<string, Set<MessageChannelHandler<Channel>>>
|
|
||||||
>;
|
|
||||||
|
|
||||||
messagePropagationBuffer: Set<{ resolve: () => Promise<void> }>;
|
|
||||||
|
|
||||||
getAsyncModeStatus: () => boolean;
|
|
||||||
}) => {
|
|
||||||
const messageHandlersByChannel = new Map<
|
|
||||||
string,
|
|
||||||
Set<MessageChannelHandler<Channel>>
|
|
||||||
>();
|
|
||||||
|
|
||||||
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<string, Set<MessageChannelHandler<Channel>>>
|
|
||||||
>;
|
|
||||||
}) => {
|
|
||||||
const requestHandlersByChannel = new Map<
|
|
||||||
string,
|
|
||||||
Set<RequestChannelHandler<Channel>>
|
|
||||||
>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { createContainer, DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||||
createContainer,
|
|
||||||
DiContainer,
|
|
||||||
Injectable,
|
|
||||||
} from "@ogre-tools/injectable";
|
|
||||||
|
|
||||||
import { registerFeature } from "@k8slens/feature-core";
|
import { registerFeature } from "@k8slens/feature-core";
|
||||||
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||||
@ -86,7 +82,7 @@ describe("listening-of-messages", () => {
|
|||||||
await startApplication();
|
await startApplication();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("it enlists a listener for the channel", () => {
|
it("enlists a listener for the channel", () => {
|
||||||
expect(enlistMessageChannelListenerMock).toHaveBeenCalledWith({
|
expect(enlistMessageChannelListenerMock).toHaveBeenCalledWith({
|
||||||
id: "some-channel-id-message-listener-some-listener",
|
id: "some-channel-id-message-listener-some-listener",
|
||||||
channel: someChannel,
|
channel: someChannel,
|
||||||
|
|||||||
@ -90,7 +90,7 @@ describe("listening-of-requests", () => {
|
|||||||
await startApplication();
|
await startApplication();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("it enlists a listener for the channel", () => {
|
it("enlists a listener for the channel", () => {
|
||||||
expect(enlistRequestChannelListenerMock).toHaveBeenCalledWith({
|
expect(enlistRequestChannelListenerMock).toHaveBeenCalledWith({
|
||||||
id: "some-channel-id-request-listener-some-listener",
|
id: "some-channel-id-request-listener-some-listener",
|
||||||
channel: someChannel,
|
channel: someChannel,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "@k8slens/typescript/config/base.json",
|
"extends": "@k8slens/typescript/config/base.json",
|
||||||
"include": ["**/*.ts"]
|
"include": ["**/*.ts", "**/*.tsx"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
module.exports =
|
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode;
|
||||||
require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode;
|
|
||||||
|
|||||||
@ -29,9 +29,7 @@ describe("enlist message channel listener in main", () => {
|
|||||||
|
|
||||||
di.override(ipcMainInjectable, () => ipcMainStub);
|
di.override(ipcMainInjectable, () => ipcMainStub);
|
||||||
|
|
||||||
enlistMessageChannelListener = di.inject(
|
enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken);
|
||||||
enlistMessageChannelListenerInjectionToken
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when called", () => {
|
describe("when called", () => {
|
||||||
@ -53,10 +51,7 @@ describe("enlist message channel listener in main", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("registers the listener", () => {
|
it("registers the listener", () => {
|
||||||
expect(onMock).toHaveBeenCalledWith(
|
expect(onMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
|
||||||
"some-channel-id",
|
|
||||||
expect.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not de-register the listener yet", () => {
|
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", () => {
|
it("when disposing the listener, de-registers the listener", () => {
|
||||||
disposer();
|
disposer();
|
||||||
|
|
||||||
expect(offMock).toHaveBeenCalledWith(
|
expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
|
||||||
"some-channel-id",
|
|
||||||
expect.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { IpcMainInvokeEvent } from "electron";
|
import type { IpcMainInvokeEvent } from "electron";
|
||||||
import ipcMainInjectable from "../ipc-main/ipc-main.injectable";
|
import ipcMainInjectable from "../ipc-main/ipc-main.injectable";
|
||||||
import type {
|
import type { RequestChannel, RequestChannelListener } from "@k8slens/messaging";
|
||||||
RequestChannel,
|
|
||||||
RequestChannelListener,
|
|
||||||
} from "@k8slens/messaging";
|
|
||||||
import { enlistRequestChannelListenerInjectionToken } from "@k8slens/messaging";
|
import { enlistRequestChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type EnlistRequestChannelListener = <
|
export type EnlistRequestChannelListener = <TChannel extends RequestChannel<unknown, unknown>>(
|
||||||
TChannel extends RequestChannel<unknown, unknown>
|
listener: RequestChannelListener<TChannel>,
|
||||||
>(
|
|
||||||
listener: RequestChannelListener<TChannel>
|
|
||||||
) => () => void;
|
) => () => void;
|
||||||
|
|
||||||
const enlistRequestChannelListenerInjectable = getInjectable({
|
const enlistRequestChannelListenerInjectable = getInjectable({
|
||||||
@ -20,8 +15,7 @@ const enlistRequestChannelListenerInjectable = getInjectable({
|
|||||||
const ipcMain = di.inject(ipcMainInjectable);
|
const ipcMain = di.inject(ipcMainInjectable);
|
||||||
|
|
||||||
return ({ channel, handler }) => {
|
return ({ channel, handler }) => {
|
||||||
const nativeHandleCallback = (_: IpcMainInvokeEvent, request: unknown) =>
|
const nativeHandleCallback = (_: IpcMainInvokeEvent, request: unknown) => handler(request);
|
||||||
handler(request);
|
|
||||||
|
|
||||||
ipcMain.handle(channel.id, nativeHandleCallback);
|
ipcMain.handle(channel.id, nativeHandleCallback);
|
||||||
|
|
||||||
|
|||||||
@ -37,9 +37,7 @@ describe("enlist request channel listener in main", () => {
|
|||||||
|
|
||||||
di.override(ipcMainInjectable, () => ipcMainStub);
|
di.override(ipcMainInjectable, () => ipcMainStub);
|
||||||
|
|
||||||
enlistRequestChannelListener = di.inject(
|
enlistRequestChannelListener = di.inject(enlistRequestChannelListenerInjectable);
|
||||||
enlistRequestChannelListenerInjectable
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when called", () => {
|
describe("when called", () => {
|
||||||
@ -61,10 +59,7 @@ describe("enlist request channel listener in main", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("registers the listener", () => {
|
it("registers the listener", () => {
|
||||||
expect(handleMock).toHaveBeenCalledWith(
|
expect(handleMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
|
||||||
"some-channel-id",
|
|
||||||
expect.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not de-register the listener yet", () => {
|
it("does not de-register the listener yet", () => {
|
||||||
@ -75,10 +70,7 @@ describe("enlist request channel listener in main", () => {
|
|||||||
let actualPromise: Promise<any>;
|
let actualPromise: Promise<any>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
actualPromise = handleMock.mock.calls[0][1](
|
actualPromise = handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, "some-request");
|
||||||
{} as IpcMainInvokeEvent,
|
|
||||||
"some-request"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls the handler with the 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", () => {
|
it("when disposing the listener, de-registers the listener", () => {
|
||||||
disposer();
|
disposer();
|
||||||
|
|
||||||
expect(offMock).toHaveBeenCalledWith(
|
expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
|
||||||
"some-channel-id",
|
|
||||||
expect.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,7 @@ export const messagingFeatureForMain = getFeature({
|
|||||||
di,
|
di,
|
||||||
targetModule: module,
|
targetModule: module,
|
||||||
|
|
||||||
getRequireContexts: () => [
|
getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
|
||||||
require.context("./", true, /\.injectable\.(ts|tsx)$/),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
module.exports =
|
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode;
|
||||||
require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode;
|
|
||||||
|
|||||||
@ -9,9 +9,7 @@ export const messagingFeatureForRenderer = getFeature({
|
|||||||
di,
|
di,
|
||||||
targetModule: module,
|
targetModule: module,
|
||||||
|
|
||||||
getRequireContexts: () => [
|
getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
|
||||||
require.context("./", true, /\.injectable\.(ts|tsx)$/),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -29,9 +29,7 @@ describe("enlist message channel listener in renderer", () => {
|
|||||||
|
|
||||||
di.override(ipcRendererInjectable, () => ipcRendererStub);
|
di.override(ipcRendererInjectable, () => ipcRendererStub);
|
||||||
|
|
||||||
enlistMessageChannelListener = di.inject(
|
enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken);
|
||||||
enlistMessageChannelListenerInjectionToken
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when called", () => {
|
describe("when called", () => {
|
||||||
@ -53,10 +51,7 @@ describe("enlist message channel listener in renderer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("registers the listener", () => {
|
it("registers the listener", () => {
|
||||||
expect(onMock).toHaveBeenCalledWith(
|
expect(onMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
|
||||||
"some-channel-id",
|
|
||||||
expect.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not de-register the listener yet", () => {
|
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", () => {
|
it("when disposing the listener, de-registers the listener", () => {
|
||||||
disposer();
|
disposer();
|
||||||
|
|
||||||
expect(offMock).toHaveBeenCalledWith(
|
expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
|
||||||
"some-channel-id",
|
|
||||||
expect.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,7 @@ const requestFromChannelInjectable = getInjectable({
|
|||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const invokeIpc = di.inject(invokeIpcInjectable);
|
const invokeIpc = di.inject(invokeIpcInjectable);
|
||||||
|
|
||||||
return ((channel, request) =>
|
return ((channel, request) => invokeIpc(channel.id, request)) as RequestFromChannel;
|
||||||
invokeIpc(channel.id, request)) as RequestFromChannel;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
injectionToken: requestFromChannelInjectionToken,
|
injectionToken: requestFromChannelInjectionToken,
|
||||||
|
|||||||
@ -34,10 +34,7 @@ describe("request-from-channel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("invokes ipcRenderer of Electron", () => {
|
it("invokes ipcRenderer of Electron", () => {
|
||||||
expect(invokeIpcMock).toHaveBeenCalledWith(
|
expect(invokeIpcMock).toHaveBeenCalledWith("some-channel-id", "some-request-payload");
|
||||||
"some-channel-id",
|
|
||||||
"some-request-payload"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when invoke resolves with response, resolves with said response", async () => {
|
it("when invoke resolves with response, resolves with said response", async () => {
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import sendToIpcInjectable from "./send-to-ipc.injectable";
|
import sendToIpcInjectable from "./send-to-ipc.injectable";
|
||||||
import {
|
import { SendMessageToChannel, sendMessageToChannelInjectionToken } from "@k8slens/messaging";
|
||||||
SendMessageToChannel,
|
|
||||||
sendMessageToChannelInjectionToken,
|
|
||||||
} from "@k8slens/messaging";
|
|
||||||
|
|
||||||
const messageToChannelInjectable = getInjectable({
|
const messageToChannelInjectable = getInjectable({
|
||||||
id: "message-to-channel",
|
id: "message-to-channel",
|
||||||
|
|||||||
@ -22,9 +22,7 @@ describe("message-from-channel", () => {
|
|||||||
|
|
||||||
describe("when called", () => {
|
describe("when called", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const sendMessageToChannel = di.inject(
|
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
sendMessageToChannelInjectionToken
|
|
||||||
);
|
|
||||||
|
|
||||||
const someChannel: MessageChannel<number> = {
|
const someChannel: MessageChannel<number> = {
|
||||||
id: "some-channel-id",
|
id: "some-channel-id",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user