mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
431 lines
15 KiB
TypeScript
431 lines
15 KiB
TypeScript
import {
|
|
createContainer,
|
|
DiContainer,
|
|
Injectable,
|
|
} from "@ogre-tools/injectable";
|
|
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
|
|
import { registerFeature } from "@k8slens/feature-core/src/register-feature";
|
|
import {
|
|
getMessageChannelListenerInjectable,
|
|
MessageChannel,
|
|
} from "../../actual/message/message-channel-listener-injection-token";
|
|
import { sendMessageToChannelInjectionToken } from "../../actual/message/message-to-channel-injection-token.no-coverage";
|
|
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
|
import { runInAction } from "mobx";
|
|
import {
|
|
getRequestChannelListenerInjectable,
|
|
RequestChannel,
|
|
} from "../../actual/request/request-channel-listener-injection-token";
|
|
import { requestFromChannelInjectionToken } from "../../actual/request/request-from-channel-injection-token.no-coverage";
|
|
import { getPromiseStatus } from "@k8slens/test-utils";
|
|
import { getMessageBridgeFake } from "./get-message-bridge-fake";
|
|
import { getMessageChannel } from "../../actual/message/get-message-channel";
|
|
import { getRequestChannel } from "../../actual/request/get-request-channel";
|
|
import { startApplicationInjectionToken } from "@k8slens/application";
|
|
import { messagingFeatureForUnitTesting } from "../feature";
|
|
|
|
[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach(
|
|
({ scenarioIsAsync }) =>
|
|
describe(`get-message-bridge-fake, given running as ${
|
|
scenarioIsAsync ? "async" : "sync"
|
|
}`, () => {
|
|
let messageBridgeFake: any;
|
|
|
|
beforeEach(() => {
|
|
messageBridgeFake = getMessageBridgeFake();
|
|
});
|
|
|
|
describe("given multiple DIs are involved", () => {
|
|
let someDi1: DiContainer;
|
|
let someDi2: DiContainer;
|
|
let someDiWithoutListeners: DiContainer;
|
|
|
|
beforeEach(async () => {
|
|
someDi1 = createContainer("some-di-1");
|
|
someDi2 = createContainer("some-di-2");
|
|
|
|
someDiWithoutListeners = createContainer("some-di-3");
|
|
|
|
registerMobX(someDi1);
|
|
registerMobX(someDi2);
|
|
registerMobX(someDiWithoutListeners);
|
|
|
|
runInAction(() => {
|
|
registerFeature(someDi1, messagingFeatureForUnitTesting);
|
|
registerFeature(someDi2, messagingFeatureForUnitTesting);
|
|
registerFeature(
|
|
someDiWithoutListeners,
|
|
messagingFeatureForUnitTesting
|
|
);
|
|
});
|
|
|
|
messageBridgeFake.involve(someDi1, someDi2, someDiWithoutListeners);
|
|
|
|
if (scenarioIsAsync) {
|
|
messageBridgeFake.setAsync(scenarioIsAsync);
|
|
}
|
|
|
|
await Promise.all([
|
|
someDi1.inject(startApplicationInjectionToken)(),
|
|
someDi2.inject(startApplicationInjectionToken)(),
|
|
someDiWithoutListeners.inject(startApplicationInjectionToken)(),
|
|
]);
|
|
});
|
|
|
|
describe("given there are message listeners", () => {
|
|
let someHandler1MockInDi1: jest.Mock;
|
|
let someHandler1MockInDi2: jest.Mock;
|
|
let someHandler2MockInDi2: jest.Mock;
|
|
let someListener1InDi2: Injectable<unknown, unknown>;
|
|
|
|
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<number>
|
|
>;
|
|
|
|
let someHandler1MockInDi2: AsyncFnMock<
|
|
(message: string) => Promise<number>
|
|
>;
|
|
|
|
let someListener1InDi2: Injectable<unknown, unknown>;
|
|
let actualPromise: Promise<number>;
|
|
|
|
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<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",
|
|
};
|