From 6184c2f03b6323678cdecf5bb52f6a2db0ccb805 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 27 Apr 2023 12:13:01 -0400 Subject: [PATCH] chore: Fix lint for non @k8slens/core packages - Update eslint config to be tighter in control Signed-off-by: Sebastian Malton --- .../src/keyboard-shortcuts.test.tsx | 2 +- .../eslint-config/eslint-config.js | 34 +++++-- .../src/observable-history.injectable.ts | 5 +- .../src/registration-of-feature.test.ts | 5 +- .../listening-of-channels.injectable.ts | 96 ++++++++++++------- .../start-listening-of-channels.injectable.ts | 4 +- ...equest-channel-listener-injection-token.ts | 1 - .../computed-channel.test.tsx | 62 +++++------- ...icate-channel-observer-guard.injectable.ts | 52 +++++----- ...ist-message-channel-listener.injectable.ts | 6 +- .../enlist-message-channel-listener.test.ts | 4 + .../enlist-request-channel-listener.test.ts | 8 +- .../request-from-channel.injectable.ts | 9 +- ...allow-communication-listener.injectable.ts | 6 +- ...send-message-to-channel.injectable.test.ts | 14 +-- .../send-message-to-channel.injectable.ts | 33 +++---- ...ist-message-channel-listener.injectable.ts | 6 +- .../enlist-message-channel-listener.test.ts | 4 + .../invoke-ipc.injectable.ts | 6 +- .../requesting-of-requests/invoke-ipc.test.ts | 21 ---- .../send-to-ipc.injectable.ts | 6 +- .../sending-of-messages/send-to-ipc.test.ts | 21 ---- .../get-message-bridge-fake.test.ts | 30 +++--- .../get-message-bridge-fake.ts | 89 ++++++++--------- packages/ui-components/button/src/button.tsx | 16 ++-- .../tooltip/src/tooltip.test.tsx | 2 +- .../ui-components/tooltip/src/tooltip.tsx | 12 +-- .../ui-components/tooltip/src/withTooltip.tsx | 6 +- .../utility-features/utilities/src/iter.ts | 23 ++--- 29 files changed, 291 insertions(+), 292 deletions(-) delete mode 100644 packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.test.ts delete mode 100644 packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.test.ts diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx index 3631a4fa98..3ab79062bb 100644 --- a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx @@ -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(() => { diff --git a/packages/infrastructure/eslint-config/eslint-config.js b/packages/infrastructure/eslint-config/eslint-config.js index f69f91f46a..60851cad58 100644 --- a/packages/infrastructure/eslint-config/eslint-config.js +++ b/packages/infrastructure/eslint-config/eslint-config.js @@ -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", diff --git a/packages/routing/src/observable-history.injectable.ts b/packages/routing/src/observable-history.injectable.ts index 9625edd0a1..58ad0bc4bd 100644 --- a/packages/routing/src/observable-history.injectable.ts +++ b/packages/routing/src/observable-history.injectable.ts @@ -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, }); diff --git a/packages/technical-features/feature-core/src/registration-of-feature.test.ts b/packages/technical-features/feature-core/src/registration-of-feature.test.ts index b1a24cc555..557cf81b40 100644 --- a/packages/technical-features/feature-core/src/registration-of-feature.test.ts +++ b/packages/technical-features/feature-core/src/registration-of-feature.test.ts @@ -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 = getInjectable({ + const someInjectable: Injectable = getInjectable({ id: "some-injectable-1", // eslint-disable-next-line @typescript-eslint/no-use-before-define instantiate: (di) => di.inject(someInjectable2), }); - const someInjectable2: Injectable = getInjectable({ + const someInjectable2: Injectable = getInjectable({ id: "some-injectable-2", + // eslint-disable-next-line @typescript-eslint/no-use-before-define instantiate: (di) => di.inject(someInjectable), }); diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts index 6d65621f12..b325a492d7 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts @@ -1,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({ id: "listening-of-channels-injection-token", }); -const listening = | RequestChannel }>( - channelListeners: IComputedValue, - enlistChannelListener: (listener: T) => () => void, - getId: (listener: T) => string, -) => { +function messageListening( + channelListeners: IComputedValue>[]>, + enlistChannelListener: (listener: MessageChannelListener>) => Disposer, +): Disposer { const listenerDisposers = new Map void>(); const reactionDisposer = reaction( @@ -38,23 +38,20 @@ const listening = | 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 = | Reques reactionDisposer(); listenerDisposers.forEach((dispose) => dispose()); }; -}; +} + +function requestListening( + channelListeners: IComputedValue>[]>, + enlistChannelListener: (listener: RequestChannelListener>) => Disposer, +): Disposer { + const listenerDisposers = new Map 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(); diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/start-listening-of-channels.injectable.ts b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/start-listening-of-channels.injectable.ts index eb265b1ca2..c2707df3e0 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/start-listening-of-channels.injectable.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/start-listening-of-channels.injectable.ts @@ -9,8 +9,8 @@ const startListeningOfChannelsInjectable = getInjectable({ const listeningOfChannels = di.inject(listeningOfChannelsInjectionToken); return { - run: async () => { - await listeningOfChannels.start(); + run: () => { + listeningOfChannels.start(); }, }; }, diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts index 6caa95cc37..55edd86391 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/request/request-channel-listener-injection-token.ts @@ -12,7 +12,6 @@ export type RequestChannelHandler = Channel extends RequestChannel { - id: string; channel: Channel; handler: RequestChannelHandler; } diff --git a/packages/technical-features/messaging/computed-channel/src/computed-channel/computed-channel.test.tsx b/packages/technical-features/messaging/computed-channel/src/computed-channel/computed-channel.test.tsx index abab064191..ddc2d8c90e 100644 --- a/packages/technical-features/messaging/computed-channel/src/computed-channel/computed-channel.test.tsx +++ b/packages/technical-features/messaging/computed-channel/src/computed-channel/computed-channel.test.tsx @@ -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 = { id: "some-channel-id" }; const testChannel2: MessageChannel = { 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(); }); - 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); diff --git a/packages/technical-features/messaging/computed-channel/src/computed-channel/duplicate-channel-observer-guard.injectable.ts b/packages/technical-features/messaging/computed-channel/src/computed-channel/duplicate-channel-observer-guard.injectable.ts index 42f313ec36..6836486d75 100644 --- a/packages/technical-features/messaging/computed-channel/src/computed-channel/duplicate-channel-observer-guard.injectable.ts +++ b/packages/technical-features/messaging/computed-channel/src/computed-channel/duplicate-channel-observer-guard.injectable.ts @@ -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, }); diff --git a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts index a8368c489b..efe45ac190 100644 --- a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts +++ b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts @@ -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 ({ channel, handler }: MessageChannelListener>) => { + const nativeOnCallback = (nativeEvent: IpcMainEvent, message: T) => { handler(message, { frameId: nativeEvent.frameId, processId: nativeEvent.processId }); }; diff --git a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.test.ts b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.test.ts index 898efdf3f1..240ed29b93 100644 --- a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.test.ts +++ b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.test.ts @@ -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 }); diff --git a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-request-channel-listener.test.ts b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-request-channel-listener.test.ts index 68672f4e95..c37946c744 100644 --- a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-request-channel-listener.test.ts +++ b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-request-channel-listener.test.ts @@ -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; + let actualPromise: Promise; 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" }); diff --git a/packages/technical-features/messaging/electron/main/src/request-from-channel/request-from-channel.injectable.ts b/packages/technical-features/messaging/electron/main/src/request-from-channel/request-from-channel.injectable.ts index 94ce8c89be..f17d0fb1a4 100644 --- a/packages/technical-features/messaging/electron/main/src/request-from-channel/request-from-channel.injectable.ts +++ b/packages/technical-features/messaging/electron/main/src/request-from-channel/request-from-channel.injectable.ts @@ -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) => { - throw new Error(`Tried to request from channel "${channel.id}" but requesting in "main" it's not supported yet`); - }) as unknown as RequestFromChannel, - + (async (channel: RequestChannel) => { + 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 */ diff --git a/packages/technical-features/messaging/electron/main/src/send-message-to-channel/allow-communication-listener.injectable.ts b/packages/technical-features/messaging/electron/main/src/send-message-to-channel/allow-communication-listener.injectable.ts index 7b80adabc0..87f5750a7f 100644 --- a/packages/technical-features/messaging/electron/main/src/send-message-to-channel/allow-communication-listener.injectable.ts +++ b/packages/technical-features/messaging/electron/main/src/send-message-to-channel/allow-communication-listener.injectable.ts @@ -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); + } }; }, }); diff --git a/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts b/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts index 627aea98c2..d4d29a9845 100644 --- a/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts +++ b/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts @@ -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, diff --git a/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.ts b/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.ts index 4fb9d06076..c72ecccc05 100644 --- a/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.ts +++ b/packages/technical-features/messaging/electron/main/src/send-message-to-channel/send-message-to-channel.injectable.ts @@ -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 = - (predicate: (item: T) => void) => - (items: T[]) => - items.forEach(predicate); +const not = + (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; }, diff --git a/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts b/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts index fb1840f33f..1453446665 100644 --- a/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts +++ b/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts @@ -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 ({ channel, handler }: MessageChannelListener>) => { + const nativeCallback = (_: IpcRendererEvent, message: T) => { handler(message); }; diff --git a/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts b/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts index a325821b91..8bc2f8b44b 100644 --- a/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts +++ b/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.test.ts @@ -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" }); diff --git a/packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.injectable.ts b/packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.injectable.ts index 03329d0c92..12a3b27f9f 100644 --- a/packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.injectable.ts +++ b/packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.injectable.ts @@ -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; diff --git a/packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.test.ts b/packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.test.ts deleted file mode 100644 index e54c98616b..0000000000 --- a/packages/technical-features/messaging/electron/renderer/src/requesting-of-requests/invoke-ipc.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.injectable.ts b/packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.injectable.ts index 059dcbe8c2..7b0311161c 100644 --- a/packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.injectable.ts +++ b/packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.injectable.ts @@ -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; diff --git a/packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.test.ts b/packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.test.ts deleted file mode 100644 index 9d1b2303f5..0000000000 --- a/packages/technical-features/messaging/electron/renderer/src/sending-of-messages/send-to-ipc.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.test.ts b/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.test.ts index a3a3c86ad3..01a337428d 100644 --- a/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.test.ts +++ b/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.test.ts @@ -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; @@ -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>; beforeEach((done) => { - someWrapper = jest.fn((propagation) => propagation()); + someWrapper = jest.fn().mockImplementation((propagation: () => Promise) => 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>; beforeEach(async () => { - someWrapper = jest.fn((observation) => observation()); + someWrapper = jest.fn().mockImplementation((propagation: () => Promise) => 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", () => { diff --git a/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts b/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts index 3e7efea5f9..a94d1dc93b 100644 --- a/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts +++ b/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts @@ -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; - messagePropagationRecursive: (callback: () => any) => any; + messagePropagation: (callback?: () => Promise) => Promise; + messagePropagationRecursive: (callback?: () => Promise) => Promise; setAsync: (value: boolean) => void; }; -type MessageHandlers = Set>>; +type MessageHandlers = Set>>; + +interface OverrideMessagingArgs { + di: DiContainer; + messageListenersByDi: Map>; + messagePropagationBuffer: Set Promise>>; + getAsyncModeStatus: () => boolean; +} const overrideMessaging = ({ di, messageListenersByDi, messagePropagationBuffer, getAsyncModeStatus, -}: { - di: DiContainer; - - messageListenersByDi: Map>; - - messagePropagationBuffer: Set<{ resolve: () => Promise }>; - - getAsyncModeStatus: () => boolean; -}) => { +}: OverrideMessagingArgs) => { const messageHandlersByChannel = new Map(); messageListenersByDi.set(di, messageHandlersByChannel); @@ -59,9 +59,9 @@ const overrideMessaging = ({ } if (getAsyncModeStatus()) { - const resolvableHandlePromise = asyncFn(); + const resolvableHandlePromise = asyncFn<() => Promise>(); - resolvableHandlePromise().then(() => { + void resolvableHandlePromise().then(() => { handlersForChannel.forEach((handler) => handler(message, { frameId: 42, processId: 42 })); }); @@ -74,24 +74,23 @@ const overrideMessaging = ({ di.override( enlistMessageChannelListenerInjectionToken, - () => - (listener: MessageChannelListener>) => { - if (!messageHandlersByChannel.has(listener.channel.id)) { - messageHandlersByChannel.set(listener.channel.id, new Set()); - } + () => (listener: MessageChannelListener>) => { + 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>>; +type RequestHandlers = Set>>; 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>) => { + 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>(); const requestListenersByDi = new Map>(); + const messagePropagationBuffer = new Set Promise>>(); - const messagePropagationBuffer = new Set void>>(); - - const messagePropagation = async (wrapper: (callback: any) => any = (callback) => callback()) => { + const messagePropagation = async ( + wrapper: (callback: () => Promise) => Promise = (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) => Promise = (callback) => callback(), + ) => { while (messagePropagationBuffer.size) { await messagePropagation(wrapper); } diff --git a/packages/ui-components/button/src/button.tsx b/packages/ui-components/button/src/button.tsx index d6bfaebde0..7c8426c4aa 100644 --- a/packages/ui-components/button/src/button.tsx +++ b/packages/ui-components/button/src/button.tsx @@ -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 { +export interface ButtonProps extends ButtonHTMLAttributes { label?: StrictReactNode; waiting?: boolean; primary?: boolean; @@ -21,7 +21,7 @@ export interface ButtonProps extends ButtonHTMLAttributes { 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 ( - + )}> {label} {children} ); } - // render as button return ( - diff --git a/packages/ui-components/tooltip/src/tooltip.test.tsx b/packages/ui-components/tooltip/src/tooltip.test.tsx index c120c29ec8..9038a97c13 100644 --- a/packages/ui-components/tooltip/src/tooltip.test.tsx +++ b/packages/ui-components/tooltip/src/tooltip.test.tsx @@ -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: () => diff --git a/packages/ui-components/tooltip/src/tooltip.tsx b/packages/ui-components/tooltip/src/tooltip.tsx index 8e50d13402..9a3bc2d06e 100644 --- a/packages/ui-components/tooltip/src/tooltip.tsx +++ b/packages/ui-components/tooltip/src/tooltip.tsx @@ -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 { 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; diff --git a/packages/ui-components/tooltip/src/withTooltip.tsx b/packages/ui-components/tooltip/src/withTooltip.tsx index 225cb99bb9..f429c76cf0 100644 --- a/packages/ui-components/tooltip/src/withTooltip.tsx +++ b/packages/ui-components/tooltip/src/withTooltip.tsx @@ -49,10 +49,12 @@ export function withTooltip( ); } + const ResolvedTarget = Target as React.FunctionComponent; + return ( - + {targetChildren} - + ); }; diff --git a/packages/utility-features/utilities/src/iter.ts b/packages/utility-features/utilities/src/iter.ts index 3d5f8530f5..46f9eea668 100644 --- a/packages/utility-features/utilities/src/iter.ts +++ b/packages/utility-features/utilities/src/iter.ts @@ -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 extends Iterable { @@ -11,7 +13,8 @@ interface Iterator extends Iterable { find(fn: (val: T) => unknown): T | undefined; collect(fn: (values: Iterable) => U): U; toArray(): T[]; - toMap(): T extends readonly [infer K, infer V] ? Map : never; + toMap(): T extends readonly [infer K, infer V] ? Map : never; + groupIntoMap(): T extends readonly [infer K, infer V] ? Map : never; toSet(): Set; map(fn: (val: T) => U): Iterator; flatMap(fn: (val: T) => U[]): Iterator; @@ -30,21 +33,15 @@ function chain(src: IterableIterator): Iterator { 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 : 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 : never; + return res; }, toSet: () => new Set(src), concat: (src2) => chain(concat(src, src2)),