mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: Fix lint for non @k8slens/core packages
- Update eslint config to be tighter in control Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
fcef072859
commit
6184c2f03b
@ -18,7 +18,7 @@ import platformInjectable from "./platform.injectable";
|
||||
|
||||
describe("keyboard-shortcuts", () => {
|
||||
let di: DiContainer;
|
||||
let invokeMock: jest.Mock;
|
||||
let invokeMock: jest.MockedFunction<(val?: string) => void>;
|
||||
let rendered: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
"react-app",
|
||||
"react-app/jest",
|
||||
"airbnb-typescript",
|
||||
@ -15,14 +16,16 @@ module.exports = {
|
||||
"xss",
|
||||
"no-unsanitized"
|
||||
],
|
||||
ignorePatterns: [
|
||||
"dist/*"
|
||||
],
|
||||
ignorePatterns: ["**/dist/**"],
|
||||
rules: {
|
||||
"react/react-in-jsx-scope": 0,
|
||||
"security/detect-object-injection": "off",
|
||||
"security/detect-non-literal-fs-filename": "off"
|
||||
},
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
@ -121,6 +124,9 @@ module.exports = {
|
||||
"template-curly-spacing": "error",
|
||||
"keyword-spacing": "off",
|
||||
|
||||
// jest rules
|
||||
"jest/valid-title": "off",
|
||||
|
||||
// testing-library
|
||||
"testing-library/no-node-access": "off",
|
||||
"testing-library/no-container": "off",
|
||||
@ -132,11 +138,9 @@ module.exports = {
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-useless-constructor": "off",
|
||||
"@typescript-eslint/comma-dangle": "off",
|
||||
"@typescript-eslint/no-shadow": "off",
|
||||
@ -148,15 +152,25 @@ module.exports = {
|
||||
allowTemplateLiterals: true,
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-unused-expressions": [
|
||||
"@typescript-eslint/no-unused-expressions": "error",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/keyword-spacing": "error",
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": [
|
||||
"error",
|
||||
{
|
||||
allowShortCircuit: true,
|
||||
allowNumber: true,
|
||||
allowBoolean: true,
|
||||
allowNullish: true,
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/keyword-spacing": ["error"],
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/no-use-before-define": [
|
||||
"error",
|
||||
{
|
||||
functions: false,
|
||||
classes: false,
|
||||
}
|
||||
],
|
||||
|
||||
// React specific rules
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
|
||||
@ -21,11 +21,10 @@ export const observableHistoryInjectable = getInjectable({
|
||||
|
||||
instantiate: (di) => {
|
||||
const history = di.inject(historyInjectable);
|
||||
const navigation = createObservableHistory(history, {
|
||||
|
||||
return createObservableHistory(history, {
|
||||
searchParams: searchParamsOptions,
|
||||
});
|
||||
|
||||
return navigation;
|
||||
},
|
||||
injectionToken: observableHistoryInjectionToken,
|
||||
});
|
||||
|
||||
@ -98,14 +98,15 @@ describe("register-feature", () => {
|
||||
});
|
||||
|
||||
it("given di-container and registered Features with injectables forming a cycle, when an injectable is injected, throws with namespaced error about cycle", () => {
|
||||
const someInjectable: Injectable<any> = getInjectable({
|
||||
const someInjectable: Injectable<unknown> = getInjectable({
|
||||
id: "some-injectable-1",
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
instantiate: (di) => di.inject(someInjectable2),
|
||||
});
|
||||
|
||||
const someInjectable2: Injectable<any> = getInjectable({
|
||||
const someInjectable2: Injectable<unknown> = getInjectable({
|
||||
id: "some-injectable-2",
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
instantiate: (di) => di.inject(someInjectable),
|
||||
});
|
||||
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "../message/enlist-message-channel-listener-injection-token";
|
||||
|
||||
import { getStartableStoppable, StartableStoppable } from "@k8slens/startable-stoppable";
|
||||
|
||||
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import { IComputedValue, reaction } from "mobx";
|
||||
|
||||
import {
|
||||
MessageChannel,
|
||||
MessageChannelListener,
|
||||
messageChannelListenerInjectionToken,
|
||||
} from "../message/message-channel-listener-injection-token";
|
||||
import {
|
||||
RequestChannel,
|
||||
RequestChannelListener,
|
||||
requestChannelListenerInjectionToken,
|
||||
} from "../request/request-channel-listener-injection-token";
|
||||
import { enlistRequestChannelListenerInjectionToken } from "../request/enlist-request-channel-listener-injection-token";
|
||||
import type { Disposer } from "@k8slens/utilities";
|
||||
|
||||
export type ListeningOfChannels = StartableStoppable;
|
||||
export const listeningOfChannelsInjectionToken = getInjectionToken<ListeningOfChannels>({
|
||||
id: "listening-of-channels-injection-token",
|
||||
});
|
||||
|
||||
const listening = <T extends { id: string; channel: MessageChannel<any> | RequestChannel<any, any> }>(
|
||||
channelListeners: IComputedValue<T[]>,
|
||||
enlistChannelListener: (listener: T) => () => void,
|
||||
getId: (listener: T) => string,
|
||||
) => {
|
||||
function messageListening<Message>(
|
||||
channelListeners: IComputedValue<MessageChannelListener<MessageChannel<Message>>[]>,
|
||||
enlistChannelListener: (listener: MessageChannelListener<MessageChannel<Message>>) => Disposer,
|
||||
): Disposer {
|
||||
const listenerDisposers = new Map<string, () => void>();
|
||||
|
||||
const reactionDisposer = reaction(
|
||||
@ -38,23 +38,20 @@ const listening = <T extends { id: string; channel: MessageChannel<any> | Reques
|
||||
);
|
||||
|
||||
addedListeners.forEach((listener) => {
|
||||
const id = getId(listener);
|
||||
|
||||
if (listenerDisposers.has(id)) {
|
||||
throw new Error(`Tried to add listener for channel "${listener.channel.id}" but listener already exists.`);
|
||||
if (listenerDisposers.has(listener.id)) {
|
||||
throw new Error(
|
||||
`Tried to add listener "${listener.id}" for channel "${listener.channel.id}" but listener with a same ID already exists.`,
|
||||
);
|
||||
}
|
||||
|
||||
const disposer = enlistChannelListener(listener);
|
||||
|
||||
listenerDisposers.set(id, disposer);
|
||||
listenerDisposers.set(listener.id, disposer);
|
||||
});
|
||||
|
||||
removedListeners.forEach((listener) => {
|
||||
const dispose = listenerDisposers.get(getId(listener));
|
||||
|
||||
dispose?.();
|
||||
|
||||
listenerDisposers.delete(getId(listener));
|
||||
listenerDisposers.get(listener.id)?.();
|
||||
listenerDisposers.delete(listener.id);
|
||||
});
|
||||
},
|
||||
|
||||
@ -65,34 +62,69 @@ const listening = <T extends { id: string; channel: MessageChannel<any> | Reques
|
||||
reactionDisposer();
|
||||
listenerDisposers.forEach((dispose) => dispose());
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function requestListening<Request, Response>(
|
||||
channelListeners: IComputedValue<RequestChannelListener<RequestChannel<Request, Response>>[]>,
|
||||
enlistChannelListener: (listener: RequestChannelListener<RequestChannel<Request, Response>>) => Disposer,
|
||||
): Disposer {
|
||||
const listenerDisposers = new Map<string, () => void>();
|
||||
|
||||
const reactionDisposer = reaction(
|
||||
() => channelListeners.get(),
|
||||
(newValues, oldValues = []) => {
|
||||
const addedListeners = newValues.filter(
|
||||
(newValue) => !oldValues.some((oldValue) => oldValue.channel.id === newValue.channel.id),
|
||||
);
|
||||
|
||||
const removedListeners = oldValues.filter(
|
||||
(oldValue) => !newValues.some((newValue) => newValue.channel.id === oldValue.channel.id),
|
||||
);
|
||||
|
||||
addedListeners.forEach((listener) => {
|
||||
if (listenerDisposers.has(listener.channel.id)) {
|
||||
throw new Error(
|
||||
`Tried to add request listener for channel "${listener.channel.id}" but listener already exists.`,
|
||||
);
|
||||
}
|
||||
|
||||
const disposer = enlistChannelListener(listener);
|
||||
|
||||
listenerDisposers.set(listener.channel.id, disposer);
|
||||
});
|
||||
|
||||
removedListeners.forEach((listener) => {
|
||||
const dispose = listenerDisposers.get(listener.channel.id);
|
||||
|
||||
dispose?.();
|
||||
|
||||
listenerDisposers.delete(listener.channel.id);
|
||||
});
|
||||
},
|
||||
|
||||
{ fireImmediately: true },
|
||||
);
|
||||
|
||||
return () => {
|
||||
reactionDisposer();
|
||||
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,
|
||||
);
|
||||
const stopListeningOfMessageChannels = messageListening(messageChannelListeners, enlistMessageChannelListener);
|
||||
const stopListeningOfRequestChannels = requestListening(requestChannelListeners, enlistRequestChannelListener);
|
||||
|
||||
return () => {
|
||||
stopListeningOfMessageChannels();
|
||||
|
||||
@ -9,8 +9,8 @@ const startListeningOfChannelsInjectable = getInjectable({
|
||||
const listeningOfChannels = di.inject(listeningOfChannelsInjectionToken);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await listeningOfChannels.start();
|
||||
run: () => {
|
||||
listeningOfChannels.start();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@ -12,7 +12,6 @@ export type RequestChannelHandler<Channel> = Channel extends RequestChannel<infe
|
||||
: never;
|
||||
|
||||
export interface RequestChannelListener<Channel> {
|
||||
id: string;
|
||||
channel: Channel;
|
||||
handler: RequestChannelHandler<Channel>;
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
ComputedChannelAdminMessage,
|
||||
} from "./computed-channel-administration-channel.injectable";
|
||||
import { computedChannelFeature } from "../feature";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
|
||||
const testChannel: MessageChannel<string> = { id: "some-channel-id" };
|
||||
const testChannel2: MessageChannel<string> = { id: "some-other-channel-id" };
|
||||
@ -110,7 +111,7 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
});
|
||||
|
||||
describe("when observing the computed value in a component in di-1", () => {
|
||||
let rendered: any;
|
||||
let rendered: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
const render = renderFor(di2);
|
||||
@ -118,13 +119,10 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
rendered = render(<TestComponent someComputed={computedTestChannel} />);
|
||||
});
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when all messages are propagated" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
describe(scenarioIsAsync ? "when all messages are propagated" : "immediately", () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.messagePropagationRecursive(act).then(done);
|
||||
void messageBridgeFake.messagePropagationRecursive(act).then(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@ -155,15 +153,13 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
);
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
if (scenarioIsAsync) {
|
||||
it("computed test channel value is observed as the pending value", () => {
|
||||
expect(observedValue).toBe("some-pending-value");
|
||||
});
|
||||
}
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when admin messages are propagated" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
describe(scenarioIsAsync ? "when admin messages are propagated" : "immediately", () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
void messageBridgeFake.messagePropagation().then(done);
|
||||
@ -179,10 +175,7 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
});
|
||||
});
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when returning value-messages propagate" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
describe(scenarioIsAsync ? "when returning value-messages propagate" : "immediately", () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
void messageBridgeFake.messagePropagation().then(done);
|
||||
@ -208,10 +201,7 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
});
|
||||
});
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when value-messages propagate" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
describe(scenarioIsAsync ? "when value-messages propagate" : "immediately", () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
void messageBridgeFake.messagePropagation().then(done);
|
||||
@ -237,10 +227,7 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
stopObserving();
|
||||
});
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when admin-messages propagate" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
describe(scenarioIsAsync ? "when admin-messages propagate" : "immediately", () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
void messageBridgeFake.messagePropagation().then(done);
|
||||
@ -289,15 +276,13 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
);
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
if (scenarioIsAsync) {
|
||||
it("computed test channel value is observed as the pending value again", () => {
|
||||
expect(observedValue).toBe("some-pending-value");
|
||||
});
|
||||
}
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when admin messages propagate" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
describe(scenarioIsAsync ? "when admin messages propagate" : "immediately", () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
latestAdminMessage = undefined;
|
||||
@ -315,14 +300,14 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
});
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
if (scenarioIsAsync) {
|
||||
it("computed test channel value is still observed as the pending value", () => {
|
||||
expect(observedValue).toBe("some-pending-value");
|
||||
});
|
||||
}
|
||||
|
||||
const scenarioTitle = scenarioIsAsync ? "when value-messages propagate back" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioTitle, () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
@ -381,17 +366,23 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
},
|
||||
);
|
||||
|
||||
scenarioIsAsync && (await messageBridgeFake.messagePropagation());
|
||||
if (scenarioIsAsync) {
|
||||
await messageBridgeFake.messagePropagation();
|
||||
}
|
||||
|
||||
stopObserving();
|
||||
|
||||
scenarioIsAsync && (await messageBridgeFake.messagePropagation());
|
||||
if (scenarioIsAsync) {
|
||||
await messageBridgeFake.messagePropagation();
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
someOtherObservable.set("some-value");
|
||||
});
|
||||
|
||||
scenarioIsAsync && (await messageBridgeFake.messagePropagation());
|
||||
if (scenarioIsAsync) {
|
||||
await messageBridgeFake.messagePropagation();
|
||||
}
|
||||
|
||||
expect(observedValue).toBe("some-value");
|
||||
});
|
||||
@ -439,10 +430,7 @@ const TestComponent = observer(({ someComputed }: { someComputed: IComputedValue
|
||||
expect(nonReactiveValue).toBe("some-initial-value");
|
||||
});
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when messages would be propagated" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
describe(scenarioIsAsync ? "when messages would be propagated" : "immediately", () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
void messageBridgeFake.messagePropagation().then(done);
|
||||
|
||||
@ -1,42 +1,38 @@
|
||||
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { iter } from "@k8slens/utilities";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import { filter, groupBy, nth, map, toPairs } from "lodash/fp";
|
||||
import { reaction } from "mobx";
|
||||
import { computedChannelObserverInjectionToken } from "./computed-channel.injectable";
|
||||
|
||||
export const duplicateChannelObserverGuardInjectable = getInjectable({
|
||||
id: "duplicate-channel-observer-guard",
|
||||
|
||||
instantiate: (di) => {
|
||||
const computedInjectMany = di.inject(computedInjectManyInjectable);
|
||||
instantiate: (di) => ({
|
||||
run: () => {
|
||||
const computedInjectMany = di.inject(computedInjectManyInjectable);
|
||||
const computedObservers = computedInjectMany(computedChannelObserverInjectionToken);
|
||||
|
||||
return {
|
||||
run: () => {
|
||||
reaction(
|
||||
() => computedInjectMany(computedChannelObserverInjectionToken).get(),
|
||||
(observers) => {
|
||||
const duplicateObserverChannelIds = pipeline(
|
||||
observers,
|
||||
groupBy((observer) => observer.channel.id),
|
||||
toPairs,
|
||||
filter(([, channelObservers]) => channelObservers.length > 1),
|
||||
map(nth(0)),
|
||||
);
|
||||
reaction(
|
||||
() => computedObservers.get(),
|
||||
(observers) => {
|
||||
const observersByChannelId = iter
|
||||
.chain(observers.values())
|
||||
.map((observer) => [observer.channel.id, observer] as const)
|
||||
.groupIntoMap();
|
||||
const duplicateIds = iter
|
||||
.chain(observersByChannelId.entries())
|
||||
.filter(([, channelObservers]) => channelObservers.length > 1)
|
||||
.map(([id]) => id)
|
||||
.toArray();
|
||||
|
||||
if (duplicateObserverChannelIds.length) {
|
||||
throw new Error(
|
||||
`Tried to register duplicate channel observer for channels "${duplicateObserverChannelIds.join(
|
||||
'", "',
|
||||
)}"`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
if (duplicateIds.length) {
|
||||
throw new Error(`Tried to register duplicate channel observer for channels "${duplicateIds.join('", "')}"`);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
}),
|
||||
|
||||
injectionToken: onLoadOfApplicationInjectionToken,
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IpcMainEvent } from "electron";
|
||||
import ipcMainInjectable from "../ipc-main/ipc-main.injectable";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||
import { enlistMessageChannelListenerInjectionToken, MessageChannel, MessageChannelListener } from "@k8slens/messaging";
|
||||
|
||||
const enlistMessageChannelListenerInjectable = getInjectable({
|
||||
id: "enlist-message-channel-listener",
|
||||
@ -9,8 +9,8 @@ const enlistMessageChannelListenerInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const ipcMain = di.inject(ipcMainInjectable);
|
||||
|
||||
return ({ channel, handler }) => {
|
||||
const nativeOnCallback = (nativeEvent: IpcMainEvent, message: any) => {
|
||||
return <T>({ channel, handler }: MessageChannelListener<MessageChannel<T>>) => {
|
||||
const nativeOnCallback = (nativeEvent: IpcMainEvent, message: T) => {
|
||||
handler(message, { frameId: nativeEvent.frameId, processId: nativeEvent.processId });
|
||||
};
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@ describe("enlist message channel listener in main", () => {
|
||||
|
||||
describe("when message arrives", () => {
|
||||
beforeEach(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, "some-message");
|
||||
});
|
||||
|
||||
@ -72,18 +73,21 @@ describe("enlist message channel listener in main", () => {
|
||||
});
|
||||
|
||||
it("given number as message, when message arrives, calls the handler with the message", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, 42);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(42, { frameId: 42, processId: 84 });
|
||||
});
|
||||
|
||||
it("given boolean as message, when message arrives, calls the handler with the message", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, true);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(true, { frameId: 42, processId: 84 });
|
||||
});
|
||||
|
||||
it("given object as message, when message arrives, calls the handler with the message", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, { some: "object" });
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith({ some: "object" }, { frameId: 42, processId: 84 });
|
||||
|
||||
@ -48,7 +48,6 @@ describe("enlist request channel listener in main", () => {
|
||||
handlerMock = asyncFn();
|
||||
|
||||
disposer = enlistRequestChannelListener({
|
||||
id: "some-listener",
|
||||
channel: testRequestChannel,
|
||||
handler: handlerMock,
|
||||
});
|
||||
@ -67,9 +66,11 @@ describe("enlist request channel listener in main", () => {
|
||||
});
|
||||
|
||||
describe("when request arrives", () => {
|
||||
let actualPromise: Promise<any>;
|
||||
let actualPromise: Promise<unknown>;
|
||||
|
||||
beforeEach(() => {
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
actualPromise = handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, "some-request");
|
||||
});
|
||||
|
||||
@ -127,18 +128,21 @@ describe("enlist request channel listener in main", () => {
|
||||
});
|
||||
|
||||
it("given number as request, when request arrives, calls the handler with the request", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, 42);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(42);
|
||||
});
|
||||
|
||||
it("given boolean as request, when request arrives, calls the handler with the request", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, true);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("given object as request, when request arrives, calls the handler with the request", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, { some: "object" });
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith({ some: "object" });
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* c8 ignore start */
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { RequestChannel, RequestFromChannel, requestFromChannelInjectionToken } from "@k8slens/messaging";
|
||||
|
||||
@ -6,12 +5,10 @@ const requestFromChannelInjectable = getInjectable({
|
||||
id: "request-from-channel",
|
||||
|
||||
instantiate: () =>
|
||||
((channel: RequestChannel<any, any>) => {
|
||||
throw new Error(`Tried to request from channel "${channel.id}" but requesting in "main" it's not supported yet`);
|
||||
}) as unknown as RequestFromChannel,
|
||||
|
||||
(async <Request, Response>(channel: RequestChannel<Request, Response>) => {
|
||||
throw new Error(`Tried to request from channel "${channel.id}" from main, which is not supported.`);
|
||||
}) as RequestFromChannel,
|
||||
injectionToken: requestFromChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default requestFromChannelInjectable;
|
||||
/* c8 ignore stop */
|
||||
|
||||
@ -10,8 +10,10 @@ const allowCommunicationListenerInjectable = getMessageChannelListenerInjectable
|
||||
getHandler: (di) => {
|
||||
const frameIds = di.inject(frameIdsInjectable);
|
||||
|
||||
return (_, { frameId, processId }) => {
|
||||
frameIds.add({ frameId, processId });
|
||||
return (_, info) => {
|
||||
if (info) {
|
||||
frameIds.add(info);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -17,7 +17,7 @@ describe("send-message-to-channel", () => {
|
||||
registerFeature(di, messagingFeatureForMain);
|
||||
});
|
||||
|
||||
it("given no web contents, when sending a message, does not do anything", () => {
|
||||
it("given no web contents, when sending a message, does not do an unknown thing", () => {
|
||||
di.override(getWebContentsInjectable, () => () => []);
|
||||
|
||||
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||
@ -26,8 +26,8 @@ describe("send-message-to-channel", () => {
|
||||
});
|
||||
|
||||
describe("given web content that is alive", () => {
|
||||
let sendToFrameMock: jest.Mock;
|
||||
let sendMessageMock: jest.Mock;
|
||||
let sendToFrameMock: jest.MockedFunction<(...args: unknown[]) => void>;
|
||||
let sendMessageMock: jest.MockedFunction<(...args: unknown[]) => void>;
|
||||
|
||||
beforeEach(() => {
|
||||
sendToFrameMock = jest.fn();
|
||||
@ -35,14 +35,14 @@ describe("send-message-to-channel", () => {
|
||||
|
||||
di.override(getWebContentsInjectable, () => () => [
|
||||
{
|
||||
send: (...args: any[]) => sendMessageMock("first", ...args),
|
||||
sendToFrame: (...args: any[]) => sendToFrameMock("first", ...args),
|
||||
send: (...args: unknown[]) => sendMessageMock("first", ...args),
|
||||
sendToFrame: (...args: unknown[]) => sendToFrameMock("first", ...args),
|
||||
isDestroyed: () => false,
|
||||
isCrashed: () => false,
|
||||
} as unknown as WebContents,
|
||||
{
|
||||
send: (...args: any[]) => sendMessageMock("second", ...args),
|
||||
sendToFrame: (...args: any[]) => sendToFrameMock("second", ...args),
|
||||
send: (...args: unknown[]) => sendMessageMock("second", ...args),
|
||||
sendToFrame: (...args: unknown[]) => sendToFrameMock("second", ...args),
|
||||
isDestroyed: () => false,
|
||||
isCrashed: () => false,
|
||||
} as unknown as WebContents,
|
||||
|
||||
@ -1,18 +1,15 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { SendMessageToChannel, sendMessageToChannelInjectionToken } from "@k8slens/messaging";
|
||||
import getWebContentsInjectable from "./get-web-contents.injectable";
|
||||
import { flatMap, reject } from "lodash/fp";
|
||||
import type { WebContents } from "electron";
|
||||
import frameIdsInjectable from "./frameIds.injectable";
|
||||
|
||||
const isDestroyed = (webContent: WebContents) => webContent.isDestroyed();
|
||||
const isCrashed = (webContent: WebContents) => webContent.isCrashed();
|
||||
|
||||
const forEach =
|
||||
<T>(predicate: (item: T) => void) =>
|
||||
(items: T[]) =>
|
||||
items.forEach(predicate);
|
||||
const not =
|
||||
<T>(fn: (val: T) => boolean) =>
|
||||
(val: T) =>
|
||||
!fn(val);
|
||||
|
||||
const sendMessageToChannelInjectable = getInjectable({
|
||||
id: "send-message-to-channel",
|
||||
@ -22,23 +19,19 @@ const sendMessageToChannelInjectable = getInjectable({
|
||||
const frameIds = di.inject(frameIdsInjectable);
|
||||
|
||||
return ((channel, message) => {
|
||||
pipeline(
|
||||
getWebContents(),
|
||||
reject(isDestroyed),
|
||||
reject(isCrashed),
|
||||
getWebContents()
|
||||
.filter(not(isDestroyed))
|
||||
.filter(not(isCrashed))
|
||||
.flatMap((webContent) => [
|
||||
(channelId: string, ...args: unknown[]) => webContent.send(channelId, ...args),
|
||||
|
||||
flatMap((webContent) => [
|
||||
(channelId: string, ...args: any[]) => webContent.send(channelId, ...args),
|
||||
|
||||
...[...frameIds].map(({ frameId, processId }) => (channelId: string, ...args: any[]) => {
|
||||
...[...frameIds].map(({ frameId, processId }) => (channelId: string, ...args: unknown[]) => {
|
||||
webContent.sendToFrame([processId, frameId], channelId, ...args);
|
||||
}),
|
||||
]),
|
||||
|
||||
forEach((send) => {
|
||||
])
|
||||
.forEach((send) => {
|
||||
send(channel.id, message);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}) as SendMessageToChannel;
|
||||
},
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import ipcRendererInjectable from "../ipc/ipc-renderer.injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IpcRendererEvent } from "electron";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||
import { enlistMessageChannelListenerInjectionToken, MessageChannelListener, MessageChannel } from "@k8slens/messaging";
|
||||
|
||||
const enlistMessageChannelListenerInjectable = getInjectable({
|
||||
id: "enlist-message-channel-listener-for-renderer",
|
||||
@ -9,8 +9,8 @@ const enlistMessageChannelListenerInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return ({ channel, handler }) => {
|
||||
const nativeCallback = (_: IpcRendererEvent, message: any) => {
|
||||
return <T>({ channel, handler }: MessageChannelListener<MessageChannel<T>>) => {
|
||||
const nativeCallback = (_: IpcRendererEvent, message: T) => {
|
||||
handler(message);
|
||||
};
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@ describe("enlist message channel listener in renderer", () => {
|
||||
|
||||
describe("when message arrives", () => {
|
||||
beforeEach(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, "some-message");
|
||||
});
|
||||
|
||||
@ -72,18 +73,21 @@ describe("enlist message channel listener in renderer", () => {
|
||||
});
|
||||
|
||||
it("given number as message, when message arrives, calls the handler with the message", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, 42);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(42);
|
||||
});
|
||||
|
||||
it("given boolean as message, when message arrives, calls the handler with the message", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, true);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("given object as message, when message arrives, calls the handler with the message", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, { some: "object" });
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith({ some: "object" });
|
||||
|
||||
@ -4,7 +4,11 @@ import ipcRendererInjectable from "../ipc/ipc-renderer.injectable";
|
||||
const invokeIpcInjectable = getInjectable({
|
||||
id: "invoke-ipc",
|
||||
|
||||
instantiate: (di) => di.inject(ipcRendererInjectable).invoke,
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return (channel: string, ...args: unknown[]) => ipcRenderer.invoke(channel, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
export default invokeIpcInjectable;
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import { createContainer, DiContainer } from "@ogre-tools/injectable";
|
||||
import { registerFeature } from "@k8slens/feature-core";
|
||||
import { messagingFeatureForRenderer } from "../feature";
|
||||
import { ipcRenderer } from "electron";
|
||||
import invokeIpcInjectable from "./invoke-ipc.injectable";
|
||||
|
||||
describe("ipc-renderer", () => {
|
||||
let di: DiContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
di = createContainer("irrelevant");
|
||||
|
||||
registerFeature(di, messagingFeatureForRenderer);
|
||||
});
|
||||
|
||||
it("is IPC-renderer invoke of Electron", () => {
|
||||
const actual = di.inject(invokeIpcInjectable);
|
||||
|
||||
expect(actual).toBe(ipcRenderer.invoke);
|
||||
});
|
||||
});
|
||||
@ -4,7 +4,11 @@ import ipcRendererInjectable from "../ipc/ipc-renderer.injectable";
|
||||
const sendToIpcInjectable = getInjectable({
|
||||
id: "send-to-ipc",
|
||||
|
||||
instantiate: (di) => di.inject(ipcRendererInjectable).send,
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return (channel: string, ...args: unknown[]) => ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
});
|
||||
|
||||
export default sendToIpcInjectable;
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import { createContainer, DiContainer } from "@ogre-tools/injectable";
|
||||
import { registerFeature } from "@k8slens/feature-core";
|
||||
import { messagingFeatureForRenderer } from "../feature";
|
||||
import { ipcRenderer } from "electron";
|
||||
import sendToIpcInjectable from "./send-to-ipc.injectable";
|
||||
|
||||
describe("ipc-renderer", () => {
|
||||
let di: DiContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
di = createContainer("irrelevant");
|
||||
|
||||
registerFeature(di, messagingFeatureForRenderer);
|
||||
});
|
||||
|
||||
it("is IPC-renderer send of Electron", () => {
|
||||
const actual = di.inject(sendToIpcInjectable);
|
||||
|
||||
expect(actual).toBe(ipcRenderer.send);
|
||||
});
|
||||
});
|
||||
@ -16,7 +16,7 @@ import {
|
||||
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import { runInAction } from "mobx";
|
||||
import { getPromiseStatus } from "@k8slens/test-utils";
|
||||
import { getMessageBridgeFake } from "./get-message-bridge-fake";
|
||||
import { getMessageBridgeFake, MessageBridgeFake } from "./get-message-bridge-fake";
|
||||
import { startApplicationInjectionToken } from "@k8slens/application";
|
||||
|
||||
type SomeMessageChannel = MessageChannel<string>;
|
||||
@ -33,7 +33,7 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = {
|
||||
|
||||
[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach(({ scenarioIsAsync }) =>
|
||||
describe(`get-message-bridge-fake, given running as ${scenarioIsAsync ? "async" : "sync"}`, () => {
|
||||
let messageBridgeFake: any;
|
||||
let messageBridgeFake: MessageBridgeFake;
|
||||
|
||||
beforeEach(() => {
|
||||
messageBridgeFake = getMessageBridgeFake();
|
||||
@ -142,15 +142,14 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = {
|
||||
? "when all message steps are propagated using a wrapper"
|
||||
: "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioTitle, () => {
|
||||
let someWrapper: jest.Mock;
|
||||
let someWrapper: jest.MockedFunction<() => Promise<void>>;
|
||||
|
||||
beforeEach((done) => {
|
||||
someWrapper = jest.fn((propagation) => propagation());
|
||||
someWrapper = jest.fn().mockImplementation((propagation: () => Promise<void>) => propagation());
|
||||
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.messagePropagationRecursive(someWrapper).then(done);
|
||||
void messageBridgeFake.messagePropagationRecursive(someWrapper).then(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@ -163,21 +162,21 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = {
|
||||
});
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
if (scenarioIsAsync) {
|
||||
it("the wrapper gets called with the both propagations", () => {
|
||||
expect(someWrapper).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const scenarioName: string = scenarioIsAsync
|
||||
? "when all message steps are propagated not using a wrapper"
|
||||
: "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.messagePropagationRecursive().then(done);
|
||||
void messageBridgeFake.messagePropagationRecursive().then(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@ -204,19 +203,19 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = {
|
||||
expect(someHandler1MockInDi1).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
if (scenarioIsAsync) {
|
||||
it("listeners in other than sending DIs do not handle the message yet", () => {
|
||||
expect(someHandler1MockInDi2).not.toHaveBeenCalled();
|
||||
expect(someHandler2MockInDi2).not.toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when messages are propagated" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.messagePropagation().then(done);
|
||||
void messageBridgeFake.messagePropagation().then(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@ -235,12 +234,12 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = {
|
||||
});
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
if (scenarioIsAsync) {
|
||||
describe("when messages are propagated using a wrapper, such as act() in react testing lib", () => {
|
||||
let someWrapper: jest.Mock;
|
||||
let someWrapper: jest.MockedFunction<() => Promise<void>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
someWrapper = jest.fn((observation) => observation());
|
||||
someWrapper = jest.fn().mockImplementation((propagation: () => Promise<void>) => propagation());
|
||||
|
||||
await messageBridgeFake.messagePropagation(someWrapper);
|
||||
});
|
||||
@ -261,6 +260,7 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = {
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("given a listener is deregistered, when sending message, deregistered listener does not handle the message", () => {
|
||||
|
||||
@ -5,6 +5,7 @@ import type {
|
||||
MessageChannelListener,
|
||||
RequestChannel,
|
||||
RequestChannelHandler,
|
||||
RequestChannelListener,
|
||||
} from "@k8slens/messaging";
|
||||
|
||||
import {
|
||||
@ -21,27 +22,26 @@ import asyncFn, { AsyncFnMock } from "@async-fn/jest";
|
||||
|
||||
export type MessageBridgeFake = {
|
||||
involve: (...dis: DiContainer[]) => void;
|
||||
messagePropagation: () => Promise<void>;
|
||||
messagePropagationRecursive: (callback: () => any) => any;
|
||||
messagePropagation: (callback?: () => Promise<void>) => Promise<void>;
|
||||
messagePropagationRecursive: (callback?: () => Promise<void>) => Promise<void>;
|
||||
setAsync: (value: boolean) => void;
|
||||
};
|
||||
|
||||
type MessageHandlers = Set<MessageChannelHandler<MessageChannel<any>>>;
|
||||
type MessageHandlers = Set<MessageChannelHandler<MessageChannel<unknown>>>;
|
||||
|
||||
interface OverrideMessagingArgs {
|
||||
di: DiContainer;
|
||||
messageListenersByDi: Map<DiContainer, Map<string, MessageHandlers>>;
|
||||
messagePropagationBuffer: Set<AsyncFnMock<() => Promise<void>>>;
|
||||
getAsyncModeStatus: () => boolean;
|
||||
}
|
||||
|
||||
const overrideMessaging = ({
|
||||
di,
|
||||
messageListenersByDi,
|
||||
messagePropagationBuffer,
|
||||
getAsyncModeStatus,
|
||||
}: {
|
||||
di: DiContainer;
|
||||
|
||||
messageListenersByDi: Map<DiContainer, Map<string, MessageHandlers>>;
|
||||
|
||||
messagePropagationBuffer: Set<{ resolve: () => Promise<void> }>;
|
||||
|
||||
getAsyncModeStatus: () => boolean;
|
||||
}) => {
|
||||
}: OverrideMessagingArgs) => {
|
||||
const messageHandlersByChannel = new Map<string, MessageHandlers>();
|
||||
|
||||
messageListenersByDi.set(di, messageHandlersByChannel);
|
||||
@ -59,9 +59,9 @@ const overrideMessaging = ({
|
||||
}
|
||||
|
||||
if (getAsyncModeStatus()) {
|
||||
const resolvableHandlePromise = asyncFn();
|
||||
const resolvableHandlePromise = asyncFn<() => Promise<void>>();
|
||||
|
||||
resolvableHandlePromise().then(() => {
|
||||
void resolvableHandlePromise().then(() => {
|
||||
handlersForChannel.forEach((handler) => handler(message, { frameId: 42, processId: 42 }));
|
||||
});
|
||||
|
||||
@ -74,24 +74,23 @@ const overrideMessaging = ({
|
||||
|
||||
di.override(
|
||||
enlistMessageChannelListenerInjectionToken,
|
||||
() =>
|
||||
<T>(listener: MessageChannelListener<MessageChannel<T>>) => {
|
||||
if (!messageHandlersByChannel.has(listener.channel.id)) {
|
||||
messageHandlersByChannel.set(listener.channel.id, new Set());
|
||||
}
|
||||
() => (listener: MessageChannelListener<MessageChannel<unknown>>) => {
|
||||
if (!messageHandlersByChannel.has(listener.channel.id)) {
|
||||
messageHandlersByChannel.set(listener.channel.id, new Set());
|
||||
}
|
||||
|
||||
const handlerSet = messageHandlersByChannel.get(listener.channel.id);
|
||||
const handlerSet = messageHandlersByChannel.get(listener.channel.id);
|
||||
|
||||
handlerSet?.add(listener.handler);
|
||||
handlerSet?.add(listener.handler);
|
||||
|
||||
return () => {
|
||||
handlerSet?.delete(listener.handler);
|
||||
};
|
||||
},
|
||||
return () => {
|
||||
handlerSet?.delete(listener.handler);
|
||||
};
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
type RequestHandlers = Set<RequestChannelHandler<RequestChannel<any, any>>>;
|
||||
type RequestHandlers = Set<RequestChannelHandler<RequestChannel<unknown, unknown>>>;
|
||||
|
||||
const overrideRequesting = ({
|
||||
di,
|
||||
@ -128,9 +127,7 @@ const overrideRequesting = ({
|
||||
}
|
||||
|
||||
const listeners = channelSpecificListeners[0];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const [handler] = listeners!;
|
||||
const [handler] = listeners ?? [];
|
||||
|
||||
return handler;
|
||||
},
|
||||
@ -139,35 +136,41 @@ const overrideRequesting = ({
|
||||
)) as RequestFromChannel,
|
||||
);
|
||||
|
||||
di.override(enlistRequestChannelListenerInjectionToken, () => (listener) => {
|
||||
if (!requestHandlersByChannel.has(listener.channel.id)) {
|
||||
requestHandlersByChannel.set(listener.channel.id, new Set());
|
||||
}
|
||||
di.override(
|
||||
enlistRequestChannelListenerInjectionToken,
|
||||
() => (listener: RequestChannelListener<RequestChannel<unknown, unknown>>) => {
|
||||
if (!requestHandlersByChannel.has(listener.channel.id)) {
|
||||
requestHandlersByChannel.set(listener.channel.id, new Set());
|
||||
}
|
||||
|
||||
const handlerSet = requestHandlersByChannel.get(listener.channel.id);
|
||||
const handlerSet = requestHandlersByChannel.get(listener.channel.id);
|
||||
|
||||
handlerSet?.add(listener.handler);
|
||||
handlerSet?.add(listener.handler);
|
||||
|
||||
return () => {
|
||||
handlerSet?.delete(listener.handler);
|
||||
};
|
||||
});
|
||||
return () => {
|
||||
handlerSet?.delete(listener.handler);
|
||||
};
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const getMessageBridgeFake = (): MessageBridgeFake => {
|
||||
const messageListenersByDi = new Map<DiContainer, Map<string, MessageHandlers>>();
|
||||
const requestListenersByDi = new Map<DiContainer, Map<string, RequestHandlers>>();
|
||||
const messagePropagationBuffer = new Set<AsyncFnMock<() => Promise<void>>>();
|
||||
|
||||
const messagePropagationBuffer = new Set<AsyncFnMock<() => void>>();
|
||||
|
||||
const messagePropagation = async (wrapper: (callback: any) => any = (callback) => callback()) => {
|
||||
const messagePropagation = async (
|
||||
wrapper: (callback: () => Promise<void>) => Promise<void> = (callback) => callback(),
|
||||
) => {
|
||||
const oldMessages = [...messagePropagationBuffer.values()];
|
||||
|
||||
messagePropagationBuffer.clear();
|
||||
await Promise.all(oldMessages.map((x) => wrapper(x.resolve)));
|
||||
};
|
||||
|
||||
const messagePropagationRecursive = async (wrapper = (callback: () => any) => callback()) => {
|
||||
const messagePropagationRecursive = async (
|
||||
wrapper: (callback: () => Promise<void>) => Promise<void> = (callback) => callback(),
|
||||
) => {
|
||||
while (messagePropagationBuffer.size) {
|
||||
await messagePropagation(wrapper);
|
||||
}
|
||||
|
||||
@ -4,12 +4,12 @@
|
||||
*/
|
||||
|
||||
import "./button.scss";
|
||||
import type { ButtonHTMLAttributes } from "react";
|
||||
import type { AnchorHTMLAttributes, ButtonHTMLAttributes } from "react";
|
||||
import React from "react";
|
||||
import { cssNames, StrictReactNode } from "@k8slens/utilities";
|
||||
import { withTooltip } from "@k8slens/tooltip";
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<any> {
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
label?: StrictReactNode;
|
||||
waiting?: boolean;
|
||||
primary?: boolean;
|
||||
@ -21,7 +21,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<any> {
|
||||
active?: boolean;
|
||||
big?: boolean;
|
||||
round?: boolean;
|
||||
href?: string; // render as hyperlink
|
||||
href?: string;
|
||||
target?: "_blank"; // in case of using @href
|
||||
children?: StrictReactNode;
|
||||
}
|
||||
@ -46,19 +46,21 @@ export const Button = withTooltip((props: ButtonProps) => {
|
||||
light,
|
||||
});
|
||||
|
||||
// render as link
|
||||
if (props.href) {
|
||||
return (
|
||||
<a {...btnProps}>
|
||||
<a {...(btnProps as AnchorHTMLAttributes<HTMLAnchorElement>)}>
|
||||
{label}
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// render as button
|
||||
return (
|
||||
<button type="button" {...btnProps} data-waiting={typeof waiting === "boolean" ? String(waiting) : undefined}>
|
||||
<button
|
||||
type="button"
|
||||
{...(btnProps as ButtonHTMLAttributes<HTMLButtonElement>)}
|
||||
data-waiting={typeof waiting === "boolean" ? String(waiting) : undefined}
|
||||
>
|
||||
{label}
|
||||
{children}
|
||||
</button>
|
||||
|
||||
@ -323,7 +323,7 @@ describe("computeNextPosition technical tests", () => {
|
||||
it("doesn't throw if the preferredPosition is invalid", () => {
|
||||
expect(
|
||||
computeNextPosition({
|
||||
preferredPositions: "some-invalid-data" as any,
|
||||
preferredPositions: "some-invalid-data" as unknown as TooltipPosition,
|
||||
offset: 10,
|
||||
target: {
|
||||
getBoundingClientRect: () =>
|
||||
|
||||
@ -11,7 +11,6 @@ import { observer } from "mobx-react";
|
||||
import type { IClassName, StrictReactNode } from "@k8slens/utilities";
|
||||
import { cssNames } from "@k8slens/utilities";
|
||||
import { observable, makeObservable, action, runInAction } from "mobx";
|
||||
import autoBindReact from "auto-bind/react";
|
||||
import { computeNextPosition } from "./helpers";
|
||||
|
||||
export enum TooltipPosition {
|
||||
@ -67,7 +66,6 @@ class DefaultedTooltip extends React.Component<TooltipProps & typeof defaultProp
|
||||
constructor(props: TooltipProps & typeof defaultProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
autoBindReact(this);
|
||||
}
|
||||
|
||||
get targetElem(): HTMLElement | null {
|
||||
@ -97,17 +95,15 @@ class DefaultedTooltip extends React.Component<TooltipProps & typeof defaultProp
|
||||
this.hoverTarget?.removeEventListener("mouseleave", this.onLeaveTarget);
|
||||
}
|
||||
|
||||
@action
|
||||
protected onEnterTarget() {
|
||||
onEnterTarget = action(() => {
|
||||
this.isVisible = true;
|
||||
requestAnimationFrame(action(() => (this.isContentVisible = true)));
|
||||
}
|
||||
});
|
||||
|
||||
@action
|
||||
protected onLeaveTarget() {
|
||||
onLeaveTarget = action(() => {
|
||||
this.isVisible = false;
|
||||
this.isContentVisible = false;
|
||||
}
|
||||
});
|
||||
|
||||
refreshPosition() {
|
||||
const { preferredPositions, offset } = this.props;
|
||||
|
||||
@ -49,10 +49,12 @@ export function withTooltip<TargetProps>(
|
||||
);
|
||||
}
|
||||
|
||||
const ResolvedTarget = Target as React.FunctionComponent<TargetProps>;
|
||||
|
||||
return (
|
||||
<Target id={targetId} {...(targetProps as any)}>
|
||||
<ResolvedTarget id={targetId} {...(targetProps as TargetProps)}>
|
||||
{targetChildren}
|
||||
</Target>
|
||||
</ResolvedTarget>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getOrInsert } from "./collection-functions";
|
||||
|
||||
export type Falsy = false | 0 | "" | null | undefined;
|
||||
|
||||
interface Iterator<T> extends Iterable<T> {
|
||||
@ -11,7 +13,8 @@ interface Iterator<T> extends Iterable<T> {
|
||||
find(fn: (val: T) => unknown): T | undefined;
|
||||
collect<U>(fn: (values: Iterable<T>) => U): U;
|
||||
toArray(): T[];
|
||||
toMap(): T extends readonly [infer K, infer V] ? Map<K, V[]> : never;
|
||||
toMap(): T extends readonly [infer K, infer V] ? Map<K, V> : never;
|
||||
groupIntoMap(): T extends readonly [infer K, infer V] ? Map<K, V[]> : never;
|
||||
toSet(): Set<T>;
|
||||
map<U>(fn: (val: T) => U): Iterator<U>;
|
||||
flatMap<U>(fn: (val: T) => U[]): Iterator<U>;
|
||||
@ -30,21 +33,15 @@ function chain<T>(src: IterableIterator<T>): Iterator<T> {
|
||||
join: (sep) => join(src, sep),
|
||||
collect: (fn) => fn(src),
|
||||
toArray: () => [...src],
|
||||
toMap: () => {
|
||||
const res = new Map();
|
||||
toMap: () => new Map(src as IterableIterator<[any, any]>) as any,
|
||||
groupIntoMap: () => {
|
||||
const res = new Map() as T extends readonly [infer K, infer V] ? Map<K, V[]> : never;
|
||||
|
||||
for (const item of src[Symbol.iterator]()) {
|
||||
const [key, val] = item as [unknown, unknown];
|
||||
const existing = res.get(key);
|
||||
|
||||
if (existing) {
|
||||
existing.push(val);
|
||||
} else {
|
||||
res.set(key, [val]);
|
||||
}
|
||||
for (const [key, value] of src as IterableIterator<[any, any]>) {
|
||||
getOrInsert(res, key, []).push(value);
|
||||
}
|
||||
|
||||
return res as T extends readonly [infer K, infer V] ? Map<K, V[]> : never;
|
||||
return res;
|
||||
},
|
||||
toSet: () => new Set(src),
|
||||
concat: (src2) => chain(concat(src, src2)),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user