diff --git a/src/behaviours/add-cluster/navigation-using-application-menu.test.ts b/src/behaviours/add-cluster/navigation-using-application-menu.test.ts index cc7749a7e9..4c945c8d6c 100644 --- a/src/behaviours/add-cluster/navigation-using-application-menu.test.ts +++ b/src/behaviours/add-cluster/navigation-using-application-menu.test.ts @@ -35,8 +35,8 @@ describe("add-cluster - navigation using application menu", () => { }); describe("when navigating to add cluster using application menu", () => { - beforeEach(() => { - applicationBuilder.applicationMenu.click("file.add-cluster"); + beforeEach(async () => { + await applicationBuilder.applicationMenu.click("file.add-cluster"); }); it("renders", () => { diff --git a/src/behaviours/extensions/navigation-using-application-menu.test.ts b/src/behaviours/extensions/navigation-using-application-menu.test.ts index ef714a75d3..b11fb417be 100644 --- a/src/behaviours/extensions/navigation-using-application-menu.test.ts +++ b/src/behaviours/extensions/navigation-using-application-menu.test.ts @@ -46,8 +46,8 @@ describe("extensions - navigation using application menu", () => { }); describe("when navigating to extensions using application menu", () => { - beforeEach(() => { - applicationBuilder.applicationMenu.click("root.extensions"); + beforeEach(async () => { + await applicationBuilder.applicationMenu.click("root.extensions"); }); it("focuses the window", () => { diff --git a/src/behaviours/preferences/navigation-using-application-menu.test.ts b/src/behaviours/preferences/navigation-using-application-menu.test.ts index 42664dbfdf..efe5a29884 100644 --- a/src/behaviours/preferences/navigation-using-application-menu.test.ts +++ b/src/behaviours/preferences/navigation-using-application-menu.test.ts @@ -18,7 +18,6 @@ describe("preferences - navigation using application menu", () => { beforeEach(async () => { applicationBuilder = getApplicationBuilder(); - applicationBuilder.beforeSetups(({ rendererDi, mainDi }) => { mainDi.override(isAutoUpdateEnabledInjectable, () => () => false); @@ -47,8 +46,8 @@ describe("preferences - navigation using application menu", () => { }); describe("when navigating to preferences using application menu", () => { - beforeEach(() => { - applicationBuilder.applicationMenu.click("root.preferences"); + beforeEach(async () => { + await applicationBuilder.applicationMenu.click("root.preferences"); }); it("renders", () => { diff --git a/src/behaviours/welcome/navigation-using-application-menu.test.ts b/src/behaviours/welcome/navigation-using-application-menu.test.ts index daab31c40e..c330275e82 100644 --- a/src/behaviours/welcome/navigation-using-application-menu.test.ts +++ b/src/behaviours/welcome/navigation-using-application-menu.test.ts @@ -31,8 +31,8 @@ describe("welcome - navigation using application menu", () => { }); describe("when navigating to welcome using application menu", () => { - beforeEach(() => { - applicationBuilder.applicationMenu.click("help.welcome"); + beforeEach(async () => { + await applicationBuilder.applicationMenu.click("help.welcome"); }); it("renders", () => { diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index 8ddce07c18..d58a5b3b14 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -14,17 +14,13 @@ import { stdout, stderr } from "process"; import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; import clusterStoreInjectable from "../cluster-store/cluster-store.injectable"; import type { ClusterModel } from "../cluster-types"; -import type { - DiContainer, -} from "@ogre-tools/injectable"; - +import type { DiContainer } from "@ogre-tools/injectable"; import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token"; - import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable"; -import { runSetups } from "../setupable-injection-token/run-setups"; +import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable"; console = new Console(stdout, stderr); @@ -86,12 +82,12 @@ describe("cluster-store", () => { mockFs(); mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + mainDi.override(directoryForTempInjectable, () => "some-temp-directory"); mainDi.permitSideEffects(getConfigurationFileModelInjectable); mainDi.permitSideEffects(appVersionInjectable); mainDi.permitSideEffects(clusterStoreInjectable); - // @ts-ignore mainDi.unoverride(clusterStoreInjectable); }); @@ -115,8 +111,6 @@ describe("cluster-store", () => { mockFs(mockOpts); - await runSetups(mainDi); - createCluster = mainDi.inject(createClusterInjectionToken); clusterStore = mainDi.inject(clusterStoreInjectable); @@ -239,8 +233,6 @@ describe("cluster-store", () => { mockFs(mockOpts); - await runSetups(mainDi); - createCluster = mainDi.inject(createClusterInjectionToken); clusterStore = mainDi.inject(clusterStoreInjectable); @@ -325,8 +317,6 @@ users: mockFs(mockOpts); - await runSetups(mainDi); - createCluster = mainDi.inject(createClusterInjectionToken); clusterStore = mainDi.inject(clusterStoreInjectable); @@ -371,8 +361,6 @@ users: mockFs(mockOpts); - await runSetups(mainDi); - createCluster = mainDi.inject(createClusterInjectionToken); clusterStore = mainDi.inject(clusterStoreInjectable); diff --git a/src/common/__tests__/hotbar-store.test.ts b/src/common/__tests__/hotbar-store.test.ts index cf570478e5..1364b8a337 100644 --- a/src/common/__tests__/hotbar-store.test.ts +++ b/src/common/__tests__/hotbar-store.test.ts @@ -12,11 +12,10 @@ import getConfigurationFileModelInjectable from "../get-configuration-file-model import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import hotbarStoreInjectable from "../hotbar-store.injectable"; -import { HotbarStore } from "../hotbar-store"; -import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; -import { runSetups } from "../setupable-injection-token/run-setups"; +import type { HotbarStore } from "../hotbar-store"; import catalogEntityRegistryInjectable from "../../main/catalog/catalog-entity-registry.injectable"; - +import directoryForUserDataInjectable + from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; function getMockCatalogEntity(data: Partial & CatalogEntityKindData): CatalogEntity { return { @@ -82,6 +81,9 @@ describe("HotbarStore", () => { di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(appVersionInjectable); + di.permitSideEffects(hotbarStoreInjectable); + di.unoverride(hotbarStoreInjectable); + di.override(catalogEntityRegistryInjectable, () => ({ addComputedSource: (id, source) => {}, items: [ @@ -135,13 +137,7 @@ describe("HotbarStore", () => { ], })); - di.override(hotbarStoreInjectable, () => { - HotbarStore.resetInstance(); - - return HotbarStore.createInstance({ - catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable), - }); - }); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); }); afterEach(() => { @@ -152,9 +148,9 @@ describe("HotbarStore", () => { beforeEach(async () => { mockFs(); - await runSetups(di); - hotbarStore = di.inject(hotbarStoreInjectable); + + hotbarStore.load(); }); describe("load", () => { @@ -320,7 +316,7 @@ describe("HotbarStore", () => { describe("given pre beta-5 configurations", () => { beforeEach(async () => { const configurationToBeMigrated = { - "some-electron-app-path-for-user-data": { + "some-directory-for-user-data": { "lens-hotbar-store.json": JSON.stringify({ __internal__: { migrations: { @@ -383,9 +379,9 @@ describe("HotbarStore", () => { mockFs(configurationToBeMigrated); - await runSetups(di); - hotbarStore = di.inject(hotbarStoreInjectable); + + hotbarStore.load(); }); it("allows to retrieve a hotbar", () => { diff --git a/src/common/app-paths/app-paths.test.ts b/src/common/app-paths/app-paths.test.ts index b0fa396e23..f5f3d5d885 100644 --- a/src/common/app-paths/app-paths.test.ts +++ b/src/common/app-paths/app-paths.test.ts @@ -2,27 +2,27 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { DiContainer } from "@ogre-tools/injectable"; import type { AppPaths } from "./app-path-injection-token"; import { appPathsInjectionToken } from "./app-path-injection-token"; import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable"; -import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing"; import type { PathName } from "./app-path-names"; import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable"; import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable"; import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable"; +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import type { DiContainer } from "@ogre-tools/injectable"; describe("app-paths", () => { - let mainDi: DiContainer; + let applicationBuilder: ApplicationBuilder; let rendererDi: DiContainer; - let runSetups: () => Promise; + let mainDi: DiContainer; beforeEach(() => { - const dis = getDisForUnitTesting({ doGeneralOverrides: true }); + applicationBuilder = getApplicationBuilder(); - mainDi = dis.mainDi; - rendererDi = dis.rendererDi; - runSetups = dis.runSetups; + rendererDi = applicationBuilder.dis.rendererDi; + mainDi = applicationBuilder.dis.mainDi; const defaultAppPathsStub: AppPaths = { appData: "some-app-data", @@ -43,27 +43,29 @@ describe("app-paths", () => { userData: "some-irrelevant-user-data", }; - mainDi.override( - getElectronAppPathInjectable, - () => - (key: PathName): string | null => - defaultAppPathsStub[key], - ); + applicationBuilder.beforeSetups(({ mainDi }) => { + mainDi.override( + getElectronAppPathInjectable, + () => + (key: PathName): string | null => + defaultAppPathsStub[key], + ); - mainDi.override( - setElectronAppPathInjectable, - () => - (key: PathName, path: string): void => { - defaultAppPathsStub[key] = path; - }, - ); + mainDi.override( + setElectronAppPathInjectable, + () => + (key: PathName, path: string): void => { + defaultAppPathsStub[key] = path; + }, + ); - mainDi.override(appNameInjectable, () => "some-app-name"); + mainDi.override(appNameInjectable, () => "some-app-name"); + }); }); describe("normally", () => { beforeEach(async () => { - await runSetups(); + await applicationBuilder.render(); }); it("given in renderer, when injecting app paths, returns application specific app paths", () => { @@ -115,12 +117,14 @@ describe("app-paths", () => { describe("when running integration tests", () => { beforeEach(async () => { - mainDi.override( - directoryForIntegrationTestingInjectable, - () => "some-integration-testing-app-data", - ); + applicationBuilder.beforeSetups(({ mainDi }) => { + mainDi.override( + directoryForIntegrationTestingInjectable, + () => "some-integration-testing-app-data", + ); + }); - await runSetups(); + await applicationBuilder.render(); }); it("given in renderer, when injecting path for app data, has integration specific app data path", () => { diff --git a/src/common/ipc/broadcast-message.injectable.ts b/src/common/ipc/broadcast-message.injectable.ts new file mode 100644 index 0000000000..290e6a50bf --- /dev/null +++ b/src/common/ipc/broadcast-message.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { broadcastMessage } from "./ipc"; + +const broadcastMessageInjectable = getInjectable({ + id: "broadcast-message", + instantiate: () => broadcastMessage, + // causesSideEffects: true, +}); + +export default broadcastMessageInjectable; diff --git a/src/main/start-main-application/run-many-for.test.ts b/src/common/runnable/run-many-for.test.ts similarity index 100% rename from src/main/start-main-application/run-many-for.test.ts rename to src/common/runnable/run-many-for.test.ts diff --git a/src/main/start-main-application/run-many-for.ts b/src/common/runnable/run-many-for.ts similarity index 100% rename from src/main/start-main-application/run-many-for.ts rename to src/common/runnable/run-many-for.ts diff --git a/src/main/start-main-application/run-many-sync-for.test.ts b/src/common/runnable/run-many-sync-for.test.ts similarity index 100% rename from src/main/start-main-application/run-many-sync-for.test.ts rename to src/common/runnable/run-many-sync-for.test.ts diff --git a/src/main/start-main-application/run-many-sync-for.ts b/src/common/runnable/run-many-sync-for.ts similarity index 100% rename from src/main/start-main-application/run-many-sync-for.ts rename to src/common/runnable/run-many-sync-for.ts diff --git a/src/common/setupable-injection-token/run-setups.ts b/src/common/setupable-injection-token/run-setups.ts deleted file mode 100644 index cbe037b9d1..0000000000 --- a/src/common/setupable-injection-token/run-setups.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { DiContainer } from "@ogre-tools/injectable"; -import { beforeElectronIsReadyInjectionToken } from "../../main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token"; -import { afterApplicationIsReadyInjectionToken } from "../../main/start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; - -export const runSetups = async (di: DiContainer) => { - await Promise.all( - di - .injectMany(beforeElectronIsReadyInjectionToken) - .map((setupable) => setupable.run()), - ); - - await Promise.all( - di - .injectMany(afterApplicationIsReadyInjectionToken) - .map((setupable) => setupable.run()), - ); -}; diff --git a/src/extensions/__tests__/extension-loader.test.ts b/src/extensions/__tests__/extension-loader.test.ts index c2f0d226bf..ae150e4f99 100644 --- a/src/extensions/__tests__/extension-loader.test.ts +++ b/src/extensions/__tests__/extension-loader.test.ts @@ -9,8 +9,8 @@ import { stdout, stderr } from "process"; import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable"; import { runInAction } from "mobx"; import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable"; -import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing"; import mockFs from "mock-fs"; +import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; console = new Console(stdout, stderr); @@ -110,15 +110,15 @@ describe("ExtensionLoader", () => { let updateExtensionStateMock: jest.Mock; beforeEach(() => { - const dis = getDisForUnitTesting({ doGeneralOverrides: true }); + const di = getDiForUnitTesting({ doGeneralOverrides: true }); mockFs(); updateExtensionStateMock = jest.fn(); - dis.mainDi.override(updateExtensionsStateInjectable, () => updateExtensionStateMock); + di.override(updateExtensionsStateInjectable, () => updateExtensionStateMock); - extensionLoader = dis.mainDi.inject(extensionLoaderInjectable); + extensionLoader = di.inject(extensionLoaderInjectable); }); afterEach(() => { diff --git a/src/main/__test__/cluster.test.ts b/src/main/__test__/cluster.test.ts index fd7c56f4e6..7d9655168e 100644 --- a/src/main/__test__/cluster.test.ts +++ b/src/main/__test__/cluster.test.ts @@ -2,7 +2,8 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { runSetups } from "../../common/setupable-injection-token/run-setups"; +import directoryForTempInjectable + from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; const logger = { silly: jest.fn(), @@ -44,6 +45,8 @@ import { createClusterInjectionToken } from "../../common/cluster/create-cluster import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable"; +import directoryForUserDataInjectable + from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; console = new Console(process.stdout, process.stderr); // fix mockFS @@ -80,14 +83,14 @@ describe("create clusters", () => { }), }); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + di.override(directoryForTempInjectable, () => "some-directory-for-temp"); di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true)); di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ])); di.override(createContextHandlerInjectable, () => () => { throw new Error("you should never come here"); }); - await runSetups(di); - createCluster = di.inject(createClusterInjectionToken); jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true)); diff --git a/src/main/__test__/kube-auth-proxy.test.ts b/src/main/__test__/kube-auth-proxy.test.ts index 4161c30443..739cf9ebf6 100644 --- a/src/main/__test__/kube-auth-proxy.test.ts +++ b/src/main/__test__/kube-auth-proxy.test.ts @@ -57,7 +57,10 @@ import path from "path"; import spawnInjectable from "../child-process/spawn.injectable"; import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable"; -import { runSetups } from "../../common/setupable-injection-token/run-setups"; +import directoryForUserDataInjectable + from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForTempInjectable + from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; console = new Console(stdout, stderr); @@ -100,6 +103,9 @@ describe("kube auth proxy tests", () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + di.override(directoryForTempInjectable, () => "some-directory-for-temp"); + di.override(spawnInjectable, () => mockSpawn); di.permitSideEffects(getConfigurationFileModelInjectable); @@ -107,8 +113,6 @@ describe("kube auth proxy tests", () => { mockFs(mockMinikubeConfig); - await runSetups(di); - createCluster = di.inject(createClusterInjectionToken); createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable); diff --git a/src/main/__test__/kubeconfig-manager.test.ts b/src/main/__test__/kubeconfig-manager.test.ts index d53e8ba04e..514de3d890 100644 --- a/src/main/__test__/kubeconfig-manager.test.ts +++ b/src/main/__test__/kubeconfig-manager.test.ts @@ -2,8 +2,13 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { runSetups } from "../../common/setupable-injection-token/run-setups"; +import directoryForUserDataInjectable + from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ const logger = { silly: jest.fn(), debug: jest.fn(), @@ -57,6 +62,8 @@ describe("kubeconfig manager tests", () => { di = getDiForUnitTesting({ doGeneralOverrides: true }); di.override(directoryForTempInjectable, () => "some-directory-for-temp"); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + mockFs({ "minikube-config.yml": JSON.stringify({ @@ -86,8 +93,6 @@ describe("kubeconfig manager tests", () => { throw new Error("you should never come here"); }); - await runSetups(di); - const createCluster = di.inject(createClusterInjectionToken); createKubeconfigManager = di.inject(createKubeconfigManagerInjectable); diff --git a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index 5da47128b1..151863bf24 100644 --- a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -17,8 +17,9 @@ import directoryForKubeConfigsInjectable from "../../../common/app-paths/directo import { ClusterStore } from "../../../common/cluster-store/cluster-store"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable"; -import { runSetups } from "../../../common/setupable-injection-token/run-setups"; import clusterManagerInjectable from "../../cluster-manager.injectable"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; jest.mock("electron", () => ({ app: { @@ -44,6 +45,9 @@ describe("kubeconfig-sync.source tests", () => { mockFs(); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + di.override(directoryForTempInjectable, () => "some-directory-for-temp"); + di.override(clusterStoreInjectable, () => ClusterStore.createInstance({ createCluster: () => null }), ); @@ -51,8 +55,6 @@ describe("kubeconfig-sync.source tests", () => { di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(appVersionInjectable); - await runSetups(di); - computeDiff = computeDiffFor({ directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable), createCluster: di.inject(createClusterInjectionToken), diff --git a/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts index ea73acff9e..cb15cd1536 100644 --- a/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-deep-linking.injectable.ts @@ -10,7 +10,8 @@ import commandLineArgumentsInjectable from "../../utils/command-line-arguments.i import { pipeline } from "@ogre-tools/fp"; import { find, startsWith, toLower, map } from "lodash/fp"; import { whenApplicationIsLoadingInjectionToken } from "../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; -import applicationWindowInjectable from "../../start-main-application/lens-window/application-window/application-window.injectable"; +import showApplicationWindowInjectable + from "../../start-main-application/lens-window/show-application-window.injectable"; const setupDeepLinkingInjectable = getInjectable({ id: "setup-deep-linking", @@ -19,7 +20,7 @@ const setupDeepLinkingInjectable = getInjectable({ const app = di.inject(electronAppInjectable); const logger = di.inject(loggerInjectable); const openDeepLinkForUrl = di.inject(openDeepLinkInjectable); - const applicationWindow = di.inject(applicationWindowInjectable); + const showApplicationWindow = di.inject(showApplicationWindowInjectable); const firstInstanceCommandLineArguments = di.inject( commandLineArgumentsInjectable, @@ -53,7 +54,7 @@ const setupDeepLinkingInjectable = getInjectable({ async (_, secondInstanceCommandLineArguments) => { const url = getDeepLinkUrl(secondInstanceCommandLineArguments); - await applicationWindow.show(); + await showApplicationWindow(); if (url) { await openDeepLinkForUrl(url); diff --git a/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts b/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts index f0bb635d55..1b1443c318 100644 --- a/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts +++ b/src/main/electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable.ts @@ -6,14 +6,14 @@ import { getInjectable } from "@ogre-tools/injectable"; import electronAppInjectable from "../electron-app.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import { whenApplicationIsLoadingInjectionToken } from "../../start-main-application/when-application-is-loading/when-application-is-loading-injection-token"; -import applicationWindowInjectable from "../../start-main-application/lens-window/application-window/application-window.injectable"; +import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable"; const setupMainWindowVisibilityAfterActivationInjectable = getInjectable({ id: "setup-main-window-visibility-after-activation", instantiate: (di) => { const app = di.inject(electronAppInjectable); - const applicationWindow = di.inject(applicationWindowInjectable); + const showApplicationWindow = di.inject(showApplicationWindowInjectable); const logger = di.inject(loggerInjectable); return { @@ -22,7 +22,7 @@ const setupMainWindowVisibilityAfterActivationInjectable = getInjectable({ logger.info("APP:ACTIVATE", { hasVisibleWindows: windowIsVisible }); if (!windowIsVisible) { - await applicationWindow.show(); + await showApplicationWindow(); } }); }, diff --git a/src/main/electron-app/before-application-is-ready/setup-runnables-after-window-is-opened.injectable.ts b/src/main/electron-app/before-application-is-ready/setup-runnables-after-window-is-opened.injectable.ts new file mode 100644 index 0000000000..3c9c2e0484 --- /dev/null +++ b/src/main/electron-app/before-application-is-ready/setup-runnables-after-window-is-opened.injectable.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { beforeElectronIsReadyInjectionToken } from "../../start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token"; +import electronAppInjectable from "../electron-app.injectable"; +import { runManyFor } from "../../../common/runnable/run-many-for"; +import { afterWindowIsOpenedInjectionToken } from "../../start-main-application/after-window-is-opened/after-window-is-opened-injection-token"; + +const setupRunnablesAfterWindowIsOpenedInjectable = getInjectable({ + id: "setup-runnables-after-window-is-opened", + + instantiate: (di) => { + const afterWindowIsOpened = runManyFor(di)(afterWindowIsOpenedInjectionToken); + + return { + run: () => { + const app = di.inject(electronAppInjectable); + + app.on("browser-window-created", async () => { + await afterWindowIsOpened(); + }); + }, + }; + }, + + injectionToken: beforeElectronIsReadyInjectionToken, +}); + +export default setupRunnablesAfterWindowIsOpenedInjectable; diff --git a/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts b/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts index 38be51fdbb..8b4b6a372d 100644 --- a/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts +++ b/src/main/electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable.ts @@ -9,7 +9,7 @@ import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/ import electronAppInjectable from "../electron-app.injectable"; import isIntegrationTestingInjectable from "../../../common/vars/is-integration-testing.injectable"; import autoUpdaterInjectable from "../features/auto-updater.injectable"; -import { runManySyncFor } from "../../start-main-application/run-many-sync-for"; +import { runManySyncFor } from "../../../common/runnable/run-many-sync-for"; const setupRunnablesBeforeClosingOfApplicationInjectable = getInjectable({ id: "setup-closing-of-application", diff --git a/src/main/electron-app/dock-visibility/hide-dock-for-last-closed-window.injectable.ts b/src/main/electron-app/dock-visibility/hide-dock-for-last-closed-window.injectable.ts new file mode 100644 index 0000000000..99bbcf15f0 --- /dev/null +++ b/src/main/electron-app/dock-visibility/hide-dock-for-last-closed-window.injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { beforeQuitOfFrontEndInjectionToken } from "../../start-main-application/before-quit-of-front-end/before-quit-of-front-end-injection-token"; +import electronAppInjectable from "../electron-app.injectable"; +import { lensWindowInjectionToken } from "../../start-main-application/lens-window/application-window/lens-window-injection-token"; +import { pipeline } from "@ogre-tools/fp"; +import { filter, isEmpty } from "lodash/fp"; + +const hideDockForLastClosedWindowInjectable = getInjectable({ + id: "hide-dock-when-there-are-no-windows", + + instantiate: (di) => { + const app = di.inject(electronAppInjectable); + const getLensWindows = () => di.injectMany(lensWindowInjectionToken); + + return { + run: () => { + const visibleWindows = pipeline( + getLensWindows(), + filter(window => !!window.visible), + ); + + if (isEmpty(visibleWindows)) { + app.dock?.hide(); + } + }, + }; + }, + + injectionToken: beforeQuitOfFrontEndInjectionToken, +}); + +export default hideDockForLastClosedWindowInjectable; diff --git a/src/main/electron-app/dock-visibility/show-dock-for-first-opened-window.injectable.ts b/src/main/electron-app/dock-visibility/show-dock-for-first-opened-window.injectable.ts new file mode 100644 index 0000000000..b1209db2a2 --- /dev/null +++ b/src/main/electron-app/dock-visibility/show-dock-for-first-opened-window.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import electronAppInjectable from "../electron-app.injectable"; +import { afterWindowIsOpenedInjectionToken } from "../../start-main-application/after-window-is-opened/after-window-is-opened-injection-token"; + +const showDockForFirstOpenedWindowInjectable = getInjectable({ + id: "show-dock-for-first-opened-window", + + instantiate: (di) => { + const app = di.inject(electronAppInjectable); + + return { + run: () => { + app.dock?.show(); + }, + }; + }, + + injectionToken: afterWindowIsOpenedInjectionToken, +}); + +export default showDockForFirstOpenedWindowInjectable; diff --git a/src/main/electron-app/features/wait-for-electron-to-be-ready.injectable.ts b/src/main/electron-app/features/wait-for-electron-to-be-ready.injectable.ts new file mode 100644 index 0000000000..586e8d7ac2 --- /dev/null +++ b/src/main/electron-app/features/wait-for-electron-to-be-ready.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import electronAppInjectable from "../electron-app.injectable"; + +const waitForElectronToBeReadyInjectable = getInjectable({ + id: "wait-for-electron-to-be-ready", + + instantiate: (di) => () => di.inject(electronAppInjectable).whenReady(), +}); + +export default waitForElectronToBeReadyInjectable; diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 65db823e4c..a8dcd448b7 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -72,8 +72,21 @@ import showMessagePopupInjectable from "./electron-app/features/show-message-pop import clusterFramesInjectable from "../common/cluster-frames.injectable"; import type { ClusterFrameInfo } from "../common/cluster-frames"; import { observable } from "mobx"; -import createBrowserWindowInjectable from "./start-main-application/lens-window/application-window/create-browser-window.injectable"; -import type { BrowserWindow } from "electron"; +// import createBrowserWindowInjectable from "./start-main-application/lens-window/application-window/create-browser-window.injectable"; +import waitForElectronToBeReadyInjectable from "./electron-app/features/wait-for-electron-to-be-ready.injectable"; + + +import setupListenerForCurrentClusterFrameInjectable + from "./start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable"; +import ipcMainInjectable from "./app-paths/register-channel/ipc-main/ipc-main.injectable"; +import createElectronWindowForInjectable + from "./start-main-application/lens-window/application-window/create-electron-window-for.injectable"; +import setupRunnablesAfterWindowIsOpenedInjectable + from "./electron-app/before-application-is-ready/setup-runnables-after-window-is-opened.injectable"; +import sendToChannelInElectronBrowserWindowInjectable + from "./start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable"; + + export const getDiForUnitTesting = ( { doGeneralOverrides } = { doGeneralOverrides: false }, @@ -180,6 +193,8 @@ const overrideRunnablesHavingSideEffects = (di: DiContainer) => { setupShellInjectable, setupSyncingOfWeblinksInjectable, setupSystemCaInjectable, + setupListenerForCurrentClusterFrameInjectable, + setupRunnablesAfterWindowIsOpenedInjectable, ].forEach((injectable) => { di.override(injectable, () => ({ run: () => {} })); }); @@ -211,11 +226,20 @@ const overrideElectronFeatures = (di: DiContainer) => { di.override(disableHardwareAccelerationInjectable, () => () => {}); di.override(shouldStartHiddenInjectable, () => true); di.override(showMessagePopupInjectable, () => () => {}); + di.override(waitForElectronToBeReadyInjectable, () => () => Promise.resolve()); + di.override(ipcMainInjectable, () => ({})); - di.override(createBrowserWindowInjectable, () => async () => ({ + di.override(createElectronWindowForInjectable, () => () => async () => ({ show: () => {}, - hide: () => {}, - }) as unknown as BrowserWindow); + + close: () => {}, + + send: (arg) => { + const sendFake = di.inject(sendToChannelInElectronBrowserWindowInjectable); + + sendFake(null, arg); + }, + })); di.override( getElectronAppPathInjectable, diff --git a/src/main/menu/application-menu-items.injectable.ts b/src/main/menu/application-menu-items.injectable.ts index d10b8a398c..7e90c716c4 100644 --- a/src/main/menu/application-menu-items.injectable.ts +++ b/src/main/menu/application-menu-items.injectable.ts @@ -24,6 +24,8 @@ import { computed } from "mobx"; import showAboutInjectable from "./show-about.injectable"; import applicationWindowInjectable from "../start-main-application/lens-window/application-window/application-window.injectable"; import reloadWindowInjectable from "../start-main-application/lens-window/reload-window.injectable"; +import showApplicationWindowInjectable + from "../start-main-application/lens-window/show-application-window.injectable"; function ignoreIf(check: boolean, menuItems: MenuItemConstructorOptions[]) { return check ? [] : menuItems; @@ -44,6 +46,7 @@ const applicationMenuItemsInjectable = getInjectable({ const electronMenuItems = di.inject(electronMenuItemsInjectable); const showAbout = di.inject(showAboutInjectable); const applicationWindow = di.inject(applicationWindowInjectable); + const showApplicationWindow = di.inject(showApplicationWindowInjectable); const reloadApplicationWindow = di.inject(reloadWindowInjectable, applicationWindow); return computed((): MenuItemOpts[] => { @@ -76,7 +79,7 @@ const applicationMenuItemsInjectable = getInjectable({ { label: "Check for updates", click() { - checkForUpdates().then(() => applicationWindow.show()); + checkForUpdates().then(() => showApplicationWindow()); }, }, ]), @@ -294,7 +297,7 @@ const applicationMenuItemsInjectable = getInjectable({ label: "Check for updates", click() { checkForUpdates().then(() => - applicationWindow.show(), + showApplicationWindow(), ); }, }, diff --git a/src/main/navigate-to-route/navigate-to-route.injectable.ts b/src/main/navigate-to-route/navigate-to-route.injectable.ts index 12bf077ced..e097bf1569 100644 --- a/src/main/navigate-to-route/navigate-to-route.injectable.ts +++ b/src/main/navigate-to-route/navigate-to-route.injectable.ts @@ -13,7 +13,7 @@ const navigateToRouteInjectable = getInjectable({ instantiate: (di) => { const navigateToUrl = di.inject(navigateToUrlInjectionToken); - return (route, options) => { + return async (route, options) => { const url = buildURL(route.path, { // TODO: enhance typing params: options?.parameters as any, @@ -21,7 +21,7 @@ const navigateToRouteInjectable = getInjectable({ fragment: options?.fragment, }); - navigateToUrl(url, options); + await navigateToUrl(url, options); }; }, diff --git a/src/main/navigate-to-url/navigate-to-url.injectable.ts b/src/main/navigate-to-url/navigate-to-url.injectable.ts index 353cb3cff0..fcef5680c1 100644 --- a/src/main/navigate-to-url/navigate-to-url.injectable.ts +++ b/src/main/navigate-to-url/navigate-to-url.injectable.ts @@ -12,8 +12,8 @@ const navigateToUrlInjectable = getInjectable({ instantiate: (di) => { const navigate = di.inject(navigateInjectable); - return (url) => { - navigate(url); + return async (url) => { + await navigate(url); }; }, diff --git a/src/main/protocol-handler/__test__/router.test.ts b/src/main/protocol-handler/__test__/router.test.ts index 1652e11f43..129d55e016 100644 --- a/src/main/protocol-handler/__test__/router.test.ts +++ b/src/main/protocol-handler/__test__/router.test.ts @@ -18,7 +18,9 @@ import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens- import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable"; -import { runSetups } from "../../../common/setupable-injection-token/run-setups"; +import directoryForUserDataInjectable + from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; + jest.mock("../../../common/ipc"); @@ -47,7 +49,7 @@ describe("protocol router tests", () => { di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(appVersionInjectable); - await runSetups(di); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); extensionLoader = di.inject(extensionLoaderInjectable); diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts index 2c4a899ebd..630d63d82c 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import { LensProtocolRouterMain } from "./lens-protocol-router-main"; import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; -import applicationWindowInjectable from "../../start-main-application/lens-window/application-window/application-window.injectable"; +import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable"; const lensProtocolRouterMainInjectable = getInjectable({ id: "lens-protocol-router-main", @@ -15,7 +15,7 @@ const lensProtocolRouterMainInjectable = getInjectable({ new LensProtocolRouterMain({ extensionLoader: di.inject(extensionLoaderInjectable), extensionsStore: di.inject(extensionsStoreInjectable), - applicationWindow: di.inject(applicationWindowInjectable), + showApplicationWindow: di.inject(showApplicationWindowInjectable), }), }); diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts index 45aee8d6d0..f689501fdb 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts @@ -14,9 +14,6 @@ import { ProtocolHandlerInvalid } from "../../../common/protocol-handler"; import { disposer, noop } from "../../../common/utils"; import type { ExtensionLoader } from "../../../extensions/extension-loader"; import type { ExtensionsStore } from "../../../extensions/extensions-store/extensions-store"; -import type { - LensWindow, -} from "../../start-main-application/lens-window/application-window/lens-window-injection-token"; export interface FallbackHandler { (name: string): Promise; @@ -42,7 +39,7 @@ function checkHost(url: URLParse): boolean { interface Dependencies { extensionLoader: ExtensionLoader; extensionsStore: ExtensionsStore; - applicationWindow: LensWindow; + showApplicationWindow: () => Promise; } export class LensProtocolRouterMain extends proto.LensProtocolRouter { @@ -79,7 +76,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { throw new proto.RoutingError(proto.RoutingErrorType.INVALID_PROTOCOL, url); } - this.dependencies.applicationWindow.show().catch(noop); + this.dependencies.showApplicationWindow().catch(noop); const routeInternally = checkHost(url); logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); diff --git a/src/main/router/router.test.ts b/src/main/router/router.test.ts index fffbf4d309..1edaa55cfd 100644 --- a/src/main/router/router.test.ts +++ b/src/main/router/router.test.ts @@ -14,7 +14,9 @@ import asyncFn from "@async-fn/jest"; import parseRequestInjectable from "./parse-request.injectable"; import { contentTypes } from "./router-content-types"; import mockFs from "mock-fs"; -import { runSetups } from "../../common/setupable-injection-token/run-setups"; +import directoryForUserDataInjectable + from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; + describe("router", () => { let router: Router; @@ -28,6 +30,7 @@ describe("router", () => { mockFs(); di.override(parseRequestInjectable, () => () => Promise.resolve({ payload: "some-payload" })); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); const injectable = getInjectable({ id: "some-route", @@ -43,8 +46,6 @@ describe("router", () => { di.register(injectable); - await runSetups(di); - router = di.inject(routerInjectable); }); diff --git a/src/main/start-main-application/after-application-is-loaded/after-application-is-loaded-injection-token.ts b/src/main/start-main-application/after-application-is-loaded/after-application-is-loaded-injection-token.ts index abe95f10bd..f89f445ecf 100644 --- a/src/main/start-main-application/after-application-is-loaded/after-application-is-loaded-injection-token.ts +++ b/src/main/start-main-application/after-application-is-loaded/after-application-is-loaded-injection-token.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { Runnable } from "../run-many-for"; +import type { Runnable } from "../../../common/runnable/run-many-for"; export const afterApplicationIsLoadedInjectionToken = getInjectionToken({ id: "after-application-is-loaded", diff --git a/src/main/start-main-application/after-root-frame-is-ready/after-root-frame-is-ready-injection-token.ts b/src/main/start-main-application/after-root-frame-is-ready/after-root-frame-is-ready-injection-token.ts index 16a1de74ba..f066c124ba 100644 --- a/src/main/start-main-application/after-root-frame-is-ready/after-root-frame-is-ready-injection-token.ts +++ b/src/main/start-main-application/after-root-frame-is-ready/after-root-frame-is-ready-injection-token.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { Runnable } from "../run-many-for"; +import type { Runnable } from "../../../common/runnable/run-many-for"; export const afterRootFrameIsReadyInjectionToken = getInjectionToken({ id: "after-root-frame-is-ready", diff --git a/src/main/start-main-application/after-window-is-opened/after-window-is-opened-injection-token.ts b/src/main/start-main-application/after-window-is-opened/after-window-is-opened-injection-token.ts new file mode 100644 index 0000000000..d5f33bceff --- /dev/null +++ b/src/main/start-main-application/after-window-is-opened/after-window-is-opened-injection-token.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "../../../common/runnable/run-many-for"; + +export const afterWindowIsOpenedInjectionToken = getInjectionToken({ + id: "after-window-is-opened", +}); diff --git a/src/main/start-main-application/before-application-is-loading/before-application-is-loading-injection-token.ts b/src/main/start-main-application/before-application-is-loading/before-application-is-loading-injection-token.ts index 9604601ecb..7cda9e6aee 100644 --- a/src/main/start-main-application/before-application-is-loading/before-application-is-loading-injection-token.ts +++ b/src/main/start-main-application/before-application-is-loading/before-application-is-loading-injection-token.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { Runnable } from "../run-many-for"; +import type { Runnable } from "../../../common/runnable/run-many-for"; export const beforeApplicationIsLoadingInjectionToken = getInjectionToken({ id: "before-application-is-loading", diff --git a/src/main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token.ts b/src/main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token.ts index 05219fd638..08173ebef2 100644 --- a/src/main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token.ts +++ b/src/main/start-main-application/before-electron-is-ready/before-electron-is-ready-injection-token.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { RunnableSync } from "../run-many-sync-for"; +import type { RunnableSync } from "../../../common/runnable/run-many-sync-for"; export const beforeElectronIsReadyInjectionToken = getInjectionToken({ diff --git a/src/main/start-main-application/before-quit-of-back-end/before-quit-of-back-end-injection-token.ts b/src/main/start-main-application/before-quit-of-back-end/before-quit-of-back-end-injection-token.ts index a38e30add3..bdb1d1e1be 100644 --- a/src/main/start-main-application/before-quit-of-back-end/before-quit-of-back-end-injection-token.ts +++ b/src/main/start-main-application/before-quit-of-back-end/before-quit-of-back-end-injection-token.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { RunnableSync } from "../run-many-sync-for"; +import type { RunnableSync } from "../../../common/runnable/run-many-sync-for"; export const beforeQuitOfBackEndInjectionToken = getInjectionToken({ diff --git a/src/main/start-main-application/before-quit-of-front-end/before-quit-of-front-end-injection-token.ts b/src/main/start-main-application/before-quit-of-front-end/before-quit-of-front-end-injection-token.ts index f53ce2c8c2..2327688cb3 100644 --- a/src/main/start-main-application/before-quit-of-front-end/before-quit-of-front-end-injection-token.ts +++ b/src/main/start-main-application/before-quit-of-front-end/before-quit-of-front-end-injection-token.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { RunnableSync } from "../run-many-sync-for"; +import type { RunnableSync } from "../../../common/runnable/run-many-sync-for"; export const beforeQuitOfFrontEndInjectionToken = getInjectionToken({ diff --git a/src/main/start-main-application/lens-window/application-is-loading-window/application-is-loading-window.injectable.ts b/src/main/start-main-application/lens-window/application-is-loading-window/application-is-loading-window.injectable.ts index 82e92668c6..8755bc50f1 100644 --- a/src/main/start-main-application/lens-window/application-is-loading-window/application-is-loading-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-is-loading-window/application-is-loading-window.injectable.ts @@ -3,86 +3,28 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { BrowserWindow } from "electron"; -import type { - SendToViewArgs, -} from "../application-window/lens-window-injection-token"; -import { - lensWindowInjectionToken, -} from "../application-window/lens-window-injection-token"; +import { lensWindowInjectionToken } from "../application-window/lens-window-injection-token"; +import createLensWindowInjectable from "../application-window/create-lens-window.injectable"; const applicationIsLoadingWindowInjectable = getInjectable({ id: "application-is-loading-window", - instantiate: () => { - let loadingWindow: BrowserWindow; + instantiate: (di) => { + const createLensWindow = di.inject(createLensWindowInjectable); - const hideWindow = () => { - loadingWindow?.hide(); - }; - - return { - show: async () => { - if (!loadingWindow) { - loadingWindow = await createLoadingWindow(); - } - - loadingWindow.show(); - }, - - hide: hideWindow, - - close: () => { - hideWindow(); - - loadingWindow = null; - }, - - send: async ({ channel, frameInfo, data = [] }: SendToViewArgs) => { - if (!loadingWindow) { - loadingWindow = await createLoadingWindow(); - } - - if (frameInfo) { - loadingWindow.webContents.sendToFrame( - [frameInfo.processId, frameInfo.frameId], - channel, - ...data, - ); - - } else { - loadingWindow.webContents.send(channel, ...data); - } - }, - }; + return createLensWindow({ + id: "splash", + title: "Loading", + getContentUrl: () => "static://splash.html", + defaultWidth: 500, + defaultHeight: 300, + resizable: false, + windowFrameUtilitiesAreShown: false, + centered: true, + }); }, injectionToken: lensWindowInjectionToken, - - causesSideEffects: true, }); export default applicationIsLoadingWindowInjectable; - -const createLoadingWindow = async () => { - const loadingWindow = new BrowserWindow({ - width: 500, - height: 300, - backgroundColor: "#1e2124", - center: true, - frame: false, - resizable: false, - show: false, - - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - nodeIntegrationInSubFrames: true, - nativeWindowOpen: true, - }, - }); - - await loadingWindow.loadURL("static://splash.html"); - - return loadingWindow; -}; diff --git a/src/main/start-main-application/lens-window/application-window/application-window-state.injectable.ts b/src/main/start-main-application/lens-window/application-window/application-window-state.injectable.ts index 2ec80b5dc3..4ba8f9afd2 100644 --- a/src/main/start-main-application/lens-window/application-window/application-window-state.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/application-window-state.injectable.ts @@ -2,20 +2,31 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getInjectable } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import windowStateKeeper from "electron-window-state"; +interface WindowStateConfiguration { + id: string; + defaultHeight: number; + defaultWidth: number; +} + const applicationWindowStateInjectable = getInjectable({ id: "application-window-state", - instantiate: () => { - console.log("asdasd"); + instantiate: ( + di, + { id, defaultHeight, defaultWidth }: WindowStateConfiguration, + ) => + windowStateKeeper({ + defaultHeight, + defaultWidth, + file: `window-state-for-${id}.json`, + }), - return windowStateKeeper({ - defaultHeight: 900, - defaultWidth: 1440, - }); - }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, { id }: WindowStateConfiguration) => id, + }), }); export default applicationWindowStateInjectable; diff --git a/src/main/start-main-application/lens-window/application-window/application-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/application-window.injectable.ts index 8029d0fe8d..fa2f9a5d99 100644 --- a/src/main/start-main-application/lens-window/application-window/application-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/application-window.injectable.ts @@ -3,56 +3,63 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import createBrowserWindowInjectable from "./create-browser-window.injectable"; -import type { - SendToViewArgs } from "./lens-window-injection-token"; -import { - lensWindowInjectionToken, -} from "./lens-window-injection-token"; -import type { BrowserWindow } from "electron"; -import sendToChannelInElectronBrowserWindowInjectable from "./send-to-channel-in-electron-browser-window.injectable"; +import { lensWindowInjectionToken } from "./lens-window-injection-token"; +import createLensWindowInjectable from "./create-lens-window.injectable"; +import lensProxyPortNumberStateInjectable from "../../../lens-proxy-port-number-state.injectable"; +import isMacInjectable from "../../../../common/vars/is-mac.injectable"; +import appNameInjectable from "../../../app-paths/app-name/app-name.injectable"; +import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; +import { delay } from "../../../../common/utils"; +import { bundledExtensionsLoaded } from "../../../../common/ipc/extension-handling"; +import ipcMainInjectable from "../../../app-paths/register-channel/ipc-main/ipc-main.injectable"; const applicationWindowInjectable = getInjectable({ id: "application-window", instantiate: (di) => { - const createBrowserWindow = di.inject(createBrowserWindowInjectable); + const createLensWindow = di.inject(createLensWindowInjectable); + const isMac = di.inject(isMacInjectable); + const applicationName = di.inject(appNameInjectable); + const appEventBus = di.inject(appEventBusInjectable); + const ipcMain = di.inject(ipcMainInjectable); - let browserWindow: BrowserWindow = null; - - const hideWindow = () => { - browserWindow?.hide(); - }; - - const sendToChannelInLensWindow = di.inject( - sendToChannelInElectronBrowserWindowInjectable, + const lensProxyPortNumberState = di.inject( + lensProxyPortNumberStateInjectable, ); - return { - show: async () => { - if (!browserWindow) { - browserWindow = await createBrowserWindow("only-application-window"); - } + const getContentUrl = () => `http://localhost:${lensProxyPortNumberState.get()}`; - browserWindow.show(); + return createLensWindow({ + id: "only-application-window", + title: applicationName, + defaultHeight: 900, + defaultWidth: 1440, + getContentUrl, + resizable: true, + windowFrameUtilitiesAreShown: isMac, + centered: false, + + onFocus: () => { + appEventBus.emit({ name: "app", action: "focus" }); }, - hide: hideWindow, - - close: () => { - hideWindow(); - - browserWindow = null; + onBlur: () => { + appEventBus.emit({ name: "app", action: "blur" }); }, - send: async (args: SendToViewArgs) => { - if (!browserWindow) { - browserWindow = await createBrowserWindow("only-application-window"); - } - - return sendToChannelInLensWindow(browserWindow, args); + onDomReady: () => { + appEventBus.emit({ name: "app", action: "dom-ready" }); }, - }; + + beforeOpen: async () => { + const viewHasLoaded = new Promise((resolve) => { + ipcMain.once(bundledExtensionsLoaded, () => resolve()); + }); + + await viewHasLoaded; + await delay(50); // wait just a bit longer to let the first round of rendering happen + }, + }); }, injectionToken: lensWindowInjectionToken, diff --git a/src/main/start-main-application/lens-window/application-window/create-browser-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-electron-window-for.injectable.ts similarity index 51% rename from src/main/start-main-application/lens-window/application-window/create-browser-window.injectable.ts rename to src/main/start-main-application/lens-window/application-window/create-electron-window-for.injectable.ts index 8ad8f26032..4239692b2c 100644 --- a/src/main/start-main-application/lens-window/application-window/create-browser-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/create-electron-window-for.injectable.ts @@ -4,32 +4,51 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import loggerInjectable from "../../../../common/logger.injectable"; -import appNameInjectable from "../../../app-paths/app-name/app-name.injectable"; import applicationWindowStateInjectable from "./application-window-state.injectable"; -import lensProxyPortNumberStateInjectable from "../../../lens-proxy-port-number-state.injectable"; import isMacInjectable from "../../../../common/vars/is-mac.injectable"; -import { BrowserWindow, ipcMain } from "electron"; -import { delay, openBrowser } from "../../../../common/utils"; -import { bundledExtensionsLoaded } from "../../../../common/ipc/extension-handling"; -import electronAppInjectable from "../../../electron-app/electron-app.injectable"; -import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; +import { BrowserWindow } from "electron"; +import { openBrowser } from "../../../../common/utils"; +import type { SendToViewArgs } from "./lens-window-injection-token"; +import sendToChannelInElectronBrowserWindowInjectable from "./send-to-channel-in-electron-browser-window.injectable"; +import type { LensWindow } from "./create-lens-window.injectable"; -const createBrowserWindowInjectable = getInjectable({ - id: "create-browser-window", +interface ElectronWindowConfiguration { + id: string; + title: string; + defaultHeight: number; + defaultWidth: number; + getContentUrl: () => string; + resizable: boolean; + windowFrameUtilitiesAreShown: boolean; + centered: boolean; + + beforeOpen?: () => Promise; + onClose: () => void; + onFocus?: () => void; + onBlur?: () => void; + onDomReady?: () => void; +} + +const createElectronWindowFor = getInjectable({ + id: "create-electron-window-for", instantiate: (di) => { const logger = di.inject(loggerInjectable); - const applicationName = di.inject(appNameInjectable); const isMac = di.inject(isMacInjectable); - const appEventBus = di.inject(appEventBusInjectable); - const lensProxyPortNumberState = di.inject( - lensProxyPortNumberStateInjectable, + const sendToChannelInLensWindow = di.inject( + sendToChannelInElectronBrowserWindowInjectable, ); - return async (id: string) => { - const applicationWindowState = di.inject(applicationWindowStateInjectable); - const app = di.inject(electronAppInjectable); + return (configuration: ElectronWindowConfiguration) => async (): Promise => { + const applicationWindowState = di.inject( + applicationWindowStateInjectable, + { + id: configuration.id, + defaultHeight: configuration.defaultHeight, + defaultWidth: configuration.defaultWidth, + }, + ); const { width, height, x, y } = applicationWindowState; @@ -38,13 +57,16 @@ const createBrowserWindowInjectable = getInjectable({ y, width, height, - title: applicationName, + title: configuration.title, + resizable: configuration.resizable, + center: configuration.centered, + frame: configuration.windowFrameUtilitiesAreShown, show: false, minWidth: 700, // accommodate 800 x 600 display minimum minHeight: 500, // accommodate 800 x 600 display minimum titleBarStyle: isMac ? "hiddenInset" : "hidden", - frame: isMac, backgroundColor: "#1e2124", + webPreferences: { nodeIntegration: true, nodeIntegrationInSubFrames: true, @@ -56,48 +78,51 @@ const createBrowserWindowInjectable = getInjectable({ applicationWindowState.manage(browserWindow); - // open external links in default browser (target=_blank, window.open) browserWindow .on("focus", () => { - appEventBus.emit({ name: "app", action: "focus" }); + configuration.onFocus?.(); }) .on("blur", () => { - appEventBus.emit({ name: "app", action: "blur" }); + configuration.onBlur?.(); }) .on("closed", () => { - // clean up + configuration.onClose(); applicationWindowState.unmanage(); - // this.mainWindow = null; - // this.splashWindow = null; - app.dock?.hide(); // hide icon in dock (mac-os) }) .webContents.on("dom-ready", () => { - appEventBus.emit({ name: "app", action: "dom-ready" }); + configuration.onDomReady?.(); }) .on("did-fail-load", (_event, code, desc) => { - logger.error(`[WINDOW-MANAGER]: Failed to load Main window`, { - code, - desc, - }); + logger.error( + `[CREATE-ELECTRON-WINDOW]: Failed to load window "${configuration.id}"`, + { + code, + desc, + }, + ); }) .on("did-finish-load", () => { - logger.info("[WINDOW-MANAGER]: Main window loaded"); + logger.info( + `[CREATE-ELECTRON-WINDOW]: Window "${configuration.id}" loaded`, + ); }) .on("will-attach-webview", (event, webPreferences, params) => { - logger.debug("[WINDOW-MANAGER]: Attaching webview"); + logger.debug( + `[CREATE-ELECTRON-WINDOW]: Attaching webview to window "${configuration.id}"`, + ); // Following is security recommendations because we allow webview tag (webviewTag: true) // suggested by https://www.electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation // and https://www.electronjs.org/docs/tutorial/security#10-do-not-use-allowpopups if (webPreferences.preload) { logger.warn( - "[WINDOW-MANAGER]: Strip away preload scripts of webview", + "[CREATE-ELECTRON-WINDOW]: Strip away preload scripts of webview", ); delete webPreferences.preload; } @@ -105,14 +130,14 @@ const createBrowserWindowInjectable = getInjectable({ // @ts-expect-error some electron version uses webPreferences.preloadURL/webPreferences.preload if (webPreferences.preloadURL) { logger.warn( - "[WINDOW-MANAGER]: Strip away preload scripts of webview", + "[CREATE-ELECTRON-WINDOW]: Strip away preload scripts of webview", ); delete webPreferences.preload; } if (params.allowpopups) { logger.warn( - "[WINDOW-MANAGER]: We do not allow allowpopups props, stop webview from renderer", + "[CREATE-ELECTRON-WINDOW]: We do not allow allowpopups props, stop webview from renderer", ); // event.preventDefault() will destroy the guest page. @@ -127,32 +152,35 @@ const createBrowserWindowInjectable = getInjectable({ .setWindowOpenHandler((details) => { openBrowser(details.url).catch((error) => { - logger.error("[WINDOW-MANAGER]: failed to open browser", { error }); + logger.error("[CREATE-ELECTRON-WINDOW]: failed to open browser", { + error, + }); }); return { action: "deny" }; }); - const contentUrl = `http://localhost:${lensProxyPortNumberState.get()}`; + const contentUrl = configuration.getContentUrl(); logger.info( - `[WINDOW-MANAGER]: Loading Main window from url: ${contentUrl} ...`, + `[CREATE-ELECTRON-WINDOW]: Loading content for window "${configuration.id}" from url: ${contentUrl}...`, ); await browserWindow.loadURL(contentUrl); - const viewHasLoaded = new Promise((resolve) => { - ipcMain.once(bundledExtensionsLoaded, () => resolve()); - }); + await configuration.beforeOpen?.(); - await viewHasLoaded; - await delay(50); // wait just a bit longer to let the first round of rendering happen + return { + show: () => browserWindow.show(), + close: () => browserWindow.close(), - return browserWindow; + send: (args: SendToViewArgs) => + sendToChannelInLensWindow(browserWindow, args), + }; }; }, causesSideEffects: true, }); -export default createBrowserWindowInjectable; +export default createElectronWindowFor; diff --git a/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts new file mode 100644 index 0000000000..e37a91eb5e --- /dev/null +++ b/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts @@ -0,0 +1,89 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { SendToViewArgs } from "./lens-window-injection-token"; +import createElectronWindowForInjectable from "./create-electron-window-for.injectable"; + +export interface LensWindow { + show: () => void; + close: () => void; + send: (args: SendToViewArgs) => void; +} + +interface LensWindowConfiguration { + id: string; + title: string; + defaultHeight: number; + defaultWidth: number; + getContentUrl: () => string; + resizable: boolean; + windowFrameUtilitiesAreShown: boolean; + centered: boolean; + + beforeOpen?: () => Promise; + onFocus?: () => void; + onBlur?: () => void; + onDomReady?: () => void; +} + +const createLensWindowInjectable = getInjectable({ + id: "create-lens-window", + + instantiate: + (di) => + (configuration: LensWindowConfiguration) => { + let browserWindow: LensWindow = null; + + const createElectronWindow = di.inject(createElectronWindowForInjectable)( + { + id: configuration.id, + title: configuration.title, + defaultHeight: configuration.defaultHeight, + defaultWidth: configuration.defaultWidth, + getContentUrl: configuration.getContentUrl, + resizable: configuration.resizable, + windowFrameUtilitiesAreShown: configuration.windowFrameUtilitiesAreShown, + centered: configuration.centered, + onFocus: configuration.onFocus, + onBlur: configuration.onBlur, + onDomReady: configuration.onDomReady, + beforeOpen: configuration.beforeOpen, + + onClose: () => { + browserWindow = null; + }, + }, + ); + + return { + get visible() { + return !!this.browserWindow; + }, + + show: async () => { + if (!browserWindow) { + browserWindow = await createElectronWindow(); + } + + browserWindow.show(); + }, + + close: () => { + browserWindow?.close(); + browserWindow = null; + }, + + send: async (args: SendToViewArgs) => { + if (!browserWindow) { + browserWindow = await createElectronWindow(); + } + + return browserWindow.send(args); + }, + }; + }, +}); + +export default createLensWindowInjectable; diff --git a/src/main/start-main-application/lens-window/application-window/lens-window-injection-token.ts b/src/main/start-main-application/lens-window/application-window/lens-window-injection-token.ts index 5ca55a49ec..7f5d2afa6f 100644 --- a/src/main/start-main-application/lens-window/application-window/lens-window-injection-token.ts +++ b/src/main/start-main-application/lens-window/application-window/lens-window-injection-token.ts @@ -13,9 +13,9 @@ export interface SendToViewArgs { export interface LensWindow { show: () => Promise; - hide: () => void; close: () => void; send: (args: SendToViewArgs) => Promise; + visible: boolean; } export const lensWindowInjectionToken = getInjectionToken({ diff --git a/src/main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable.ts index 003315a8cc..32422b6a94 100644 --- a/src/main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable.ts @@ -11,7 +11,7 @@ const sendToChannelInElectronBrowserWindowInjectable = getInjectable({ instantiate: () => - async ( + ( browserWindow: BrowserWindow, { channel, frameInfo, data = [] }: SendToViewArgs, ) => { diff --git a/src/main/start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable.ts b/src/main/start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable.ts index a9441c89b5..041f97b3c6 100644 --- a/src/main/start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable.ts +++ b/src/main/start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable.ts @@ -26,6 +26,8 @@ const setupListenerForCurrentClusterFrameInjectable = getInjectable({ }, }), + causesSideEffects: true, + injectionToken: whenApplicationIsLoadingInjectionToken, }); diff --git a/src/main/start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable.ts b/src/main/start-main-application/lens-window/hide-all-windows/close-all-windows.injectable.ts similarity index 75% rename from src/main/start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable.ts rename to src/main/start-main-application/lens-window/hide-all-windows/close-all-windows.injectable.ts index 243c85e574..f9bbd34935 100644 --- a/src/main/start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable.ts +++ b/src/main/start-main-application/lens-window/hide-all-windows/close-all-windows.injectable.ts @@ -5,16 +5,16 @@ import { getInjectable } from "@ogre-tools/injectable"; import { lensWindowInjectionToken } from "../application-window/lens-window-injection-token"; -const hideAllWindowsInjectable = getInjectable({ - id: "hide-all-windows", +const closeAllWindowsInjectable = getInjectable({ + id: "close-all-windows", instantiate: (di) => () => { const lensWindows = di.injectMany(lensWindowInjectionToken); lensWindows.forEach((lensWindow) => { - lensWindow.hide(); + lensWindow.close(); }); }, }); -export default hideAllWindowsInjectable; +export default closeAllWindowsInjectable; diff --git a/src/main/start-main-application/lens-window/navigate-for-extension.injectable.ts b/src/main/start-main-application/lens-window/navigate-for-extension.injectable.ts index b61f152272..483d9764bc 100644 --- a/src/main/start-main-application/lens-window/navigate-for-extension.injectable.ts +++ b/src/main/start-main-application/lens-window/navigate-for-extension.injectable.ts @@ -4,8 +4,9 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { iter } from "../../../common/utils"; -import applicationWindowInjectable from "./application-window/application-window.injectable"; import clusterFramesInjectable from "../../../common/cluster-frames.injectable"; +import showApplicationWindowInjectable from "./show-application-window.injectable"; +import applicationWindowInjectable from "./application-window/application-window.injectable"; const navigateForExtensionInjectable = getInjectable({ id: "navigate-for-extension", @@ -13,6 +14,7 @@ const navigateForExtensionInjectable = getInjectable({ instantiate: (di) => { const applicationWindow = di.inject(applicationWindowInjectable); const clusterFrames = di.inject(clusterFramesInjectable); + const showApplicationWindow = di.inject(showApplicationWindowInjectable); return async ( extId: string, @@ -20,7 +22,7 @@ const navigateForExtensionInjectable = getInjectable({ params?: Record, frameId?: number, ) => { - await applicationWindow.show(); + await showApplicationWindow(); const frameInfo = iter.find( clusterFrames.values(), diff --git a/src/main/start-main-application/lens-window/navigate.injectable.ts b/src/main/start-main-application/lens-window/navigate.injectable.ts index 8fc6c16eb2..c7cbb10d24 100644 --- a/src/main/start-main-application/lens-window/navigate.injectable.ts +++ b/src/main/start-main-application/lens-window/navigate.injectable.ts @@ -7,16 +7,18 @@ import { iter } from "../../../common/utils"; import applicationWindowInjectable from "./application-window/application-window.injectable"; import clusterFramesInjectable from "../../../common/cluster-frames.injectable"; import { IpcRendererNavigationEvents } from "../../../renderer/navigation/events"; +import showApplicationWindowInjectable from "./show-application-window.injectable"; const navigateInjectable = getInjectable({ id: "navigate", instantiate: (di) => { const applicationWindow = di.inject(applicationWindowInjectable); + const showApplicationWindow = di.inject(showApplicationWindowInjectable); const clusterFrames = di.inject(clusterFramesInjectable); return async (url: string, frameId?: number) => { - await applicationWindow.show(); + await showApplicationWindow(); const frameInfo = iter.find( clusterFrames.values(), diff --git a/src/main/start-main-application/lens-window/show-application-window.injectable.ts b/src/main/start-main-application/lens-window/show-application-window.injectable.ts new file mode 100644 index 0000000000..8c1a4d4af0 --- /dev/null +++ b/src/main/start-main-application/lens-window/show-application-window.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationIsLoadingWindowInjectable + from "./application-is-loading-window/application-is-loading-window.injectable"; +import applicationWindowInjectable from "./application-window/application-window.injectable"; + +const showApplicationWindowInjectable = getInjectable({ + id: "show-application-window", + + instantiate: (di) => { + const applicationWindow = di.inject(applicationWindowInjectable); + + const applicationIsLoadingWindow = di.inject( + applicationIsLoadingWindowInjectable, + ); + + return async () => { + if (applicationWindow.visible) { + return; + } + + await applicationIsLoadingWindow.show(); + + await applicationWindow.show(); + + applicationIsLoadingWindow.close(); + }; + }, +}); + +export default showApplicationWindowInjectable; diff --git a/src/main/start-main-application/start-main-application.injectable.ts b/src/main/start-main-application/start-main-application.injectable.ts index e5ea1c16bc..7bdd2c8d7a 100644 --- a/src/main/start-main-application/start-main-application.injectable.ts +++ b/src/main/start-main-application/start-main-application.injectable.ts @@ -4,9 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; -import electronAppInjectable from "../electron-app/electron-app.injectable"; -import { runManyFor } from "./run-many-for"; -import { runManySyncFor } from "./run-many-sync-for"; +import { runManyFor } from "../../common/runnable/run-many-for"; +import { runManySyncFor } from "../../common/runnable/run-many-sync-for"; import { beforeElectronIsReadyInjectionToken } from "./before-electron-is-ready/before-electron-is-ready-injection-token"; import { beforeApplicationIsLoadingInjectionToken } from "./before-application-is-loading/before-application-is-loading-injection-token"; import { whenApplicationIsLoadingInjectionToken } from "./when-application-is-loading/when-application-is-loading-injection-token"; @@ -19,6 +18,7 @@ import openDeepLinkInjectable from "../protocol-handler/lens-protocol-router-mai import { pipeline } from "@ogre-tools/fp"; import { find, map, startsWith, toLower } from "lodash/fp"; import commandLineArgumentsInjectable from "../utils/command-line-arguments.injectable"; +import waitForElectronToBeReadyInjectable from "../electron-app/features/wait-for-electron-to-be-ready.injectable"; const startMainApplicationInjectable = getInjectable({ id: "start-main-application", @@ -26,7 +26,7 @@ const startMainApplicationInjectable = getInjectable({ instantiate: (di) => { const runMany = runManyFor(di); const runManySync = runManySyncFor(di); - const electronApp = di.inject(electronAppInjectable); + const waitForElectronToBeReady = di.inject(waitForElectronToBeReadyInjectable); const applicationWindow = di.inject(applicationWindowInjectable); const applicationIsLoadingWindow = di.inject(applicationIsLoadingWindowInjectable); const shouldStartHidden = di.inject(shouldStartHiddenInjectable); @@ -43,7 +43,7 @@ const startMainApplicationInjectable = getInjectable({ // https://github.com/electron/electron/issues/21370 beforeElectronIsReady(); - await electronApp.whenReady(); + await waitForElectronToBeReady(); await beforeApplicationIsLoading(); @@ -54,10 +54,10 @@ const startMainApplicationInjectable = getInjectable({ await whenApplicationIsLoading(); if (!shouldStartHidden) { - const url = getDeepLinkUrl(commandLineArguments); + const deepLinkUrl = getDeepLinkUrl(commandLineArguments); - if (url) { - await openDeepLink(url); + if (deepLinkUrl) { + await openDeepLink(deepLinkUrl); } else { await applicationWindow.show(); } diff --git a/src/main/start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts b/src/main/start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts index 4a05ab6bbe..c3a12bf030 100644 --- a/src/main/start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts +++ b/src/main/start-main-application/when-application-is-loading/implementations/setup-runnables-for-after-root-frame-is-ready.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ipcMainOn } from "../../../../common/ipc"; import { IpcRendererNavigationEvents } from "../../../../renderer/navigation/events"; import { afterRootFrameIsReadyInjectionToken } from "../../after-root-frame-is-ready/after-root-frame-is-ready-injection-token"; -import { runManyFor } from "../../run-many-for"; +import { runManyFor } from "../../../../common/runnable/run-many-for"; import { whenApplicationIsLoadingInjectionToken } from "../when-application-is-loading-injection-token"; const setupRunnablesForAfterRootFrameIsReadyInjectable = getInjectable({ diff --git a/src/main/start-main-application/when-application-is-loading/when-application-is-loading-injection-token.ts b/src/main/start-main-application/when-application-is-loading/when-application-is-loading-injection-token.ts index 273949764a..d6f4158f08 100644 --- a/src/main/start-main-application/when-application-is-loading/when-application-is-loading-injection-token.ts +++ b/src/main/start-main-application/when-application-is-loading/when-application-is-loading-injection-token.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { Runnable } from "../run-many-for"; +import type { Runnable } from "../../../common/runnable/run-many-for"; export const whenApplicationIsLoadingInjectionToken = getInjectionToken({ id: "when-application-is-loading", diff --git a/src/main/stop-services-and-exit-app.injectable.ts b/src/main/stop-services-and-exit-app.injectable.ts index dc07f83304..25b4324aec 100644 --- a/src/main/stop-services-and-exit-app.injectable.ts +++ b/src/main/stop-services-and-exit-app.injectable.ts @@ -7,7 +7,7 @@ import exitAppInjectable from "./electron-app/features/exit-app.injectable"; import clusterManagerInjectable from "./cluster-manager.injectable"; import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable"; import loggerInjectable from "../common/logger.injectable"; -import hideAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/hide-all-windows.injectable"; +import closeAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/close-all-windows.injectable"; const stopServicesAndExitAppInjectable = getInjectable({ id: "stop-services-and-exit-app", @@ -17,11 +17,11 @@ const stopServicesAndExitAppInjectable = getInjectable({ const clusterManager = di.inject(clusterManagerInjectable); const appEventBus = di.inject(appEventBusInjectable); const logger = di.inject(loggerInjectable); - const hideAllWindows = di.inject(hideAllWindowsInjectable); + const closeAllWindows = di.inject(closeAllWindowsInjectable); return () => { appEventBus.emit({ name: "service", action: "close" }); - hideAllWindows(); + closeAllWindows(); clusterManager.stop(); logger.info("SERVICE:QUIT"); setTimeout(exitApp, 1000); diff --git a/src/main/tray/tray.injectable.ts b/src/main/tray/tray.injectable.ts index 787a37cdee..bfbe6a18f8 100644 --- a/src/main/tray/tray.injectable.ts +++ b/src/main/tray/tray.injectable.ts @@ -10,8 +10,9 @@ import stopServicesAndExitAppInjectable from "../stop-services-and-exit-app.inje import { getStartableStoppable } from "../../common/utils/get-startable-stoppable"; import isAutoUpdateEnabledInjectable from "../is-auto-update-enabled.injectable"; import trayIconPathInjectable from "./tray-icon-path.injectable"; -import applicationWindowInjectable from "../start-main-application/lens-window/application-window/application-window.injectable"; import showAboutInjectable from "../menu/show-about.injectable"; +import showApplicationWindowInjectable + from "../start-main-application/lens-window/show-application-window.injectable"; const trayInjectable = getInjectable({ id: "tray", @@ -22,7 +23,7 @@ const trayInjectable = getInjectable({ const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable); const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable); const trayIconPath = di.inject(trayIconPathInjectable); - const applicationWindow = di.inject(applicationWindowInjectable); + const showApplicationWindow = di.inject(showApplicationWindowInjectable); const showAboutPopup = di.inject(showAboutInjectable); return getStartableStoppable("build-of-tray", () => @@ -32,7 +33,7 @@ const trayInjectable = getInjectable({ stopServicesAndExitApp, isAutoUpdateEnabled, trayIconPath, - applicationWindow, + showApplicationWindow, showAboutPopup, ), ); diff --git a/src/main/tray/tray.ts b/src/main/tray/tray.ts index f9f1fceba8..e8eb542eb9 100644 --- a/src/main/tray/tray.ts +++ b/src/main/tray/tray.ts @@ -14,7 +14,6 @@ import type { Disposer } from "../../common/utils"; import { disposer, toJS } from "../../common/utils"; import type { TrayMenuRegistration } from "./tray-menu-registration"; import path from "path"; -import type { LensWindow } from "../start-main-application/lens-window/application-window/lens-window-injection-token"; const TRAY_LOG_PREFIX = "[TRAY]"; @@ -34,7 +33,7 @@ export function initTray( navigateToPreferences: () => void, stopServicesAndExitApp: () => void, isAutoUpdateEnabled: () => boolean, - applicationWindow: LensWindow, + showApplicationWindow: () => Promise, showAbout: () => void, ): Disposer { const icon = getTrayIconPath(); @@ -45,7 +44,7 @@ export function initTray( if (isWindows) { tray.on("click", () => { - applicationWindow.show() + showApplicationWindow() .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); }); } @@ -53,7 +52,7 @@ export function initTray( return disposer( autorun(() => { try { - const menu = createTrayMenu(toJS(trayMenuItems.get()), navigateToPreferences, stopServicesAndExitApp, isAutoUpdateEnabled, applicationWindow, showAbout); + const menu = createTrayMenu(toJS(trayMenuItems.get()), navigateToPreferences, stopServicesAndExitApp, isAutoUpdateEnabled, showApplicationWindow, showAbout); tray.setContextMenu(menu); } catch (error) { @@ -82,14 +81,14 @@ function createTrayMenu( navigateToPreferences: () => void, stopServicesAndExitApp: () => void, isAutoUpdateEnabled: () => boolean, - applicationWindow: LensWindow, + showApplicationWindow: () => Promise, showAbout: () => void, ): Menu { let template: Electron.MenuItemConstructorOptions[] = [ { label: `Open ${productName}`, click() { - applicationWindow.show().catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); + showApplicationWindow().catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to open lens`, { error })); }, }, { @@ -105,7 +104,7 @@ function createTrayMenu( label: "Check for updates", click() { checkForUpdates() - .then(() => applicationWindow.show()); + .then(() => showApplicationWindow()); }, }); } @@ -116,7 +115,7 @@ function createTrayMenu( { label: `About ${productName}`, click() { - applicationWindow.show() + showApplicationWindow() .then(showAbout) .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to show Lens About view`, { error })); }, diff --git a/src/renderer/app-paths/setup-app-paths.injectable.ts b/src/renderer/app-paths/setup-app-paths.injectable.ts index 4d40889b38..bcd3cb9624 100644 --- a/src/renderer/app-paths/setup-app-paths.injectable.ts +++ b/src/renderer/app-paths/setup-app-paths.injectable.ts @@ -3,10 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { AppPaths, appPathsIpcChannel } from "../../common/app-paths/app-path-injection-token"; +import type { AppPaths } from "../../common/app-paths/app-path-injection-token"; +import { appPathsIpcChannel } from "../../common/app-paths/app-path-injection-token"; import getValueFromRegisteredChannelInjectable from "./get-value-from-registered-channel/get-value-from-registered-channel.injectable"; import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable"; -import { afterApplicationIsReadyInjectionToken } from "../../main/start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; +import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token"; let syncAppPaths: AppPaths; @@ -27,7 +28,7 @@ const setupAppPathsInjectable = getInjectable({ }, }), - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: beforeFrameStartsInjectionToken, }); export default setupAppPathsInjectable; diff --git a/src/main/start-main-application/after-application-is-ready/after-application-is-ready-injection-token.ts b/src/renderer/before-frame-starts/before-frame-starts-injection-token.ts similarity index 54% rename from src/main/start-main-application/after-application-is-ready/after-application-is-ready-injection-token.ts rename to src/renderer/before-frame-starts/before-frame-starts-injection-token.ts index 7478c0ec76..e494508329 100644 --- a/src/main/start-main-application/after-application-is-ready/after-application-is-ready-injection-token.ts +++ b/src/renderer/before-frame-starts/before-frame-starts-injection-token.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { Runnable } from "../run-many-for"; +import type { Runnable } from "../../common/runnable/run-many-for"; -export const afterApplicationIsReadyInjectionToken = getInjectionToken({ - id: "after-application-is-ready", +export const beforeFrameStartsInjectionToken = getInjectionToken({ + id: "before-frame-starts", }); diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index d824b8f52d..28a68e0d0f 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -43,7 +43,10 @@ import hotbarStoreInjectable from "../common/hotbar-store.injectable"; import { bindEvents } from "./navigation/events"; import deleteClusterDialogModelInjectable from "./components/delete-cluster-dialog/delete-cluster-dialog-model/delete-cluster-dialog-model.injectable"; import { init } from "@sentry/electron/renderer"; -import { runSetups } from "../common/setupable-injection-token/run-setups"; +import { + beforeFrameStartsInjectionToken, +} from "./before-frame-starts/before-frame-starts-injection-token"; +import { runManyFor } from "../common/runnable/run-many-for"; configurePackages(); // global packages registerCustomThemes(); // monaco editor themes @@ -64,7 +67,9 @@ export async function bootstrap(di: DiContainer) { initializeSentryReporting(init); } - await runSetups(di); + const beforeFrameStarts = runManyFor(di)(beforeFrameStartsInjectionToken); + + await beforeFrameStarts(); // TODO: Consolidate import time side-effect to setup time bindEvents(); diff --git a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx index 1fe1d6e339..bd564ab574 100644 --- a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx +++ b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx @@ -4,6 +4,7 @@ */ import "@testing-library/jest-dom/extend-expect"; import { KubeConfig } from "@kubernetes/client-node"; +import type { RenderResult } from "@testing-library/react"; import { fireEvent } from "@testing-library/react"; import mockFs from "mock-fs"; import React from "react"; @@ -13,14 +14,25 @@ import type { Cluster } from "../../../../common/cluster/cluster"; import { DeleteClusterDialog } from "../delete-cluster-dialog"; import type { ClusterModel } from "../../../../common/cluster-types"; -import { getDisForUnitTesting } from "../../../../test-utils/get-dis-for-unit-testing"; import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token"; import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable"; import deleteClusterDialogModelInjectable from "../delete-cluster-dialog-model/delete-cluster-dialog-model.injectable"; import type { DeleteClusterDialogModel } from "../delete-cluster-dialog-model/delete-cluster-dialog-model"; -import type { DiRender } from "../../test-utils/renderFor"; -import { renderFor } from "../../test-utils/renderFor"; import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; +import type { + ApplicationBuilder } from "../../test-utils/get-application-builder"; +import { + getApplicationBuilder, +} from "../../test-utils/get-application-builder"; +import { routeInjectionToken } from "../../../../common/front-end-routing/route-injection-token"; +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { + routeSpecificComponentInjectionToken, +} from "../../../routes/route-specific-component-injection-token"; +import { + navigateToRouteInjectionToken, +} from "../../../../common/front-end-routing/navigate-to-route-injection-token"; jest.mock("electron", () => ({ app: { @@ -92,25 +104,50 @@ users: let config: KubeConfig; describe("", () => { - let createCluster: (model: ClusterModel) => Cluster; - let deleteClusterDialogModel: DeleteClusterDialogModel; - let render: DiRender; + let applicationBuilder: ApplicationBuilder; beforeEach(async () => { - const { mainDi, rendererDi, runSetups } = getDisForUnitTesting({ doGeneralOverrides: true }); - - render = renderFor(rendererDi); - - mainDi.override(createContextHandlerInjectable, () => () => undefined); + applicationBuilder = getApplicationBuilder(); mockFs(); - rendererDi.override(hotbarStoreInjectable, () => ({})); + applicationBuilder.beforeSetups(({ mainDi, rendererDi }) => { + mainDi.override(createContextHandlerInjectable, () => () => undefined); - await runSetups(); + rendererDi.override(hotbarStoreInjectable, () => ({})); - deleteClusterDialogModel = rendererDi.inject(deleteClusterDialogModelInjectable); - createCluster = mainDi.inject(createClusterInjectionToken); + const testRouteInjectable = getInjectable({ + id: "some-test-route", + + instantiate: () => ({ + path: "/some-test-path", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, + }); + + const testRouteComponent = getInjectable({ + id: "some-test-component", + + instantiate: (di) => ({ + route: di.inject(testRouteInjectable), + Component: () => , + }), + + injectionToken: routeSpecificComponentInjectionToken, + }); + + applicationBuilder.beforeRender(({ rendererDi }) => { + const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken); + const testRoute = rendererDi.inject(testRouteInjectable); + + navigateToRoute(testRoute); + }); + + rendererDi.register(testRouteInjectable, testRouteComponent); + }); }); afterEach(() => { @@ -118,6 +155,10 @@ describe("", () => { }); describe("Kubeconfig with different clusters", () => { + let rendered: RenderResult; + let createCluster: (model: ClusterModel) => Cluster; + let deleteClusterDialogModel: DeleteClusterDialogModel; + beforeEach(async () => { const mockOpts = { "temp-kube-config": kubeconfig, @@ -127,6 +168,11 @@ describe("", () => { config = new KubeConfig(); config.loadFromString(kubeconfig); + + rendered = await applicationBuilder.render(); + + deleteClusterDialogModel = applicationBuilder.dis.rendererDi.inject(deleteClusterDialogModelInjectable); + createCluster = applicationBuilder.dis.mainDi.inject(createClusterInjectionToken); }); afterEach(() => { @@ -134,9 +180,7 @@ describe("", () => { }); it("renders w/o errors", () => { - const { container } = render(); - - expect(container).toBeInstanceOf(HTMLElement); + expect(rendered.container).toBeInstanceOf(HTMLElement); }); it("shows warning when deleting non-current-context cluster", () => { @@ -150,11 +194,10 @@ describe("", () => { }); deleteClusterDialogModel.open({ cluster, config }); - const { getByText } = render(); const message = "The contents of kubeconfig file will be changed!"; - expect(getByText(message)).toBeInstanceOf(HTMLElement); + expect(rendered.getByText(message)).toBeInstanceOf(HTMLElement); }); it("shows warning when deleting current-context cluster", () => { @@ -169,9 +212,7 @@ describe("", () => { deleteClusterDialogModel.open({ cluster, config }); - const { getByTestId } = render(); - - expect(getByTestId("current-context-warning")).toBeInstanceOf(HTMLElement); + expect(rendered.getByTestId("current-context-warning")).toBeInstanceOf(HTMLElement); }); it("shows context switcher when deleting current cluster", async () => { @@ -186,7 +227,7 @@ describe("", () => { deleteClusterDialogModel.open({ cluster, config }); - const { getByText } = render(); + const { getByText } = rendered; expect(getByText("Select...")).toBeInTheDocument(); selectEvent.openMenu(getByText("Select...")); @@ -207,7 +248,7 @@ describe("", () => { deleteClusterDialogModel.open({ cluster, config }); - const { getByText, getByTestId } = render(); + const { getByText, getByTestId } = rendered; const link = getByTestId("context-switch"); expect(link).toBeInstanceOf(HTMLElement); @@ -234,15 +275,17 @@ describe("", () => { deleteClusterDialogModel.open({ cluster, config }); - const { getByTestId } = render(); - - expect(getByTestId("internal-kubeconfig-warning")).toBeInstanceOf(HTMLElement); + expect(rendered.getByTestId("internal-kubeconfig-warning")).toBeInstanceOf(HTMLElement); spy.mockRestore(); }); }); describe("Kubeconfig with single cluster", () => { + let rendered: RenderResult; + let createCluster: (model: ClusterModel) => Cluster; + let deleteClusterDialogModel: DeleteClusterDialogModel; + beforeEach(async () => { const mockOpts = { "temp-kube-config": singleClusterConfig, @@ -252,6 +295,11 @@ describe("", () => { config = new KubeConfig(); config.loadFromString(singleClusterConfig); + + rendered = await applicationBuilder.render(); + + deleteClusterDialogModel = applicationBuilder.dis.rendererDi.inject(deleteClusterDialogModelInjectable); + createCluster = applicationBuilder.dis.mainDi.inject(createClusterInjectionToken); }); afterEach(() => { @@ -270,9 +318,7 @@ describe("", () => { deleteClusterDialogModel.open({ cluster, config }); - const { getByTestId } = render(); - - expect(getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement); + expect(rendered.getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement); }); }); }); diff --git a/src/renderer/components/test-utils/get-application-builder.tsx b/src/renderer/components/test-utils/get-application-builder.tsx index 389dddfe2c..9242bad321 100644 --- a/src/renderer/components/test-utils/get-application-builder.tsx +++ b/src/renderer/components/test-utils/get-application-builder.tsx @@ -40,6 +40,8 @@ import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.st import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; +import startMainApplicationInjectable from "../../../main/start-main-application/start-main-application.injectable"; +import startFrameInjectable from "../../start-frame/start-frame.injectable"; type Callback = (dis: DiContainers) => void | Promise; @@ -55,7 +57,7 @@ export interface ApplicationBuilder { render: () => Promise; applicationMenu: { - click: (path: string) => void; + click: (path: string) => Promise; }; preferences: { @@ -82,7 +84,7 @@ interface Environment { } export const getApplicationBuilder = () => { - const { rendererDi, mainDi, runSetups } = getDisForUnitTesting({ + const { rendererDi, mainDi } = getDisForUnitTesting({ doGeneralOverrides: true, }); @@ -142,7 +144,7 @@ export const getApplicationBuilder = () => { dis, applicationMenu: { - click: (path: string) => { + click: async (path: string) => { const applicationMenuItems = mainDi.inject( applicationMenuItemsInjectable, ); @@ -164,6 +166,10 @@ export const getApplicationBuilder = () => { } menuItem.click(undefined, undefined, undefined); + + const flushPromises = () => new Promise(setImmediate); + + await flushPromises(); }, }, @@ -301,7 +307,13 @@ export const getApplicationBuilder = () => { await callback(dis); } - await runSetups(); + const startMainApplication = mainDi.inject(startMainApplicationInjectable); + + await startMainApplication(); + + const startFrame = rendererDi.inject(startFrameInjectable); + + await startFrame(); const render = renderFor(rendererDi); diff --git a/src/renderer/ipc-channel-listeners/register-ipc-channel-listeners.injectable.ts b/src/renderer/ipc-channel-listeners/register-ipc-channel-listeners.injectable.ts index 9470f51632..bf9568b71c 100644 --- a/src/renderer/ipc-channel-listeners/register-ipc-channel-listeners.injectable.ts +++ b/src/renderer/ipc-channel-listeners/register-ipc-channel-listeners.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ipcChannelListenerInjectionToken } from "./ipc-channel-listener-injection-token"; import registerIpcChannelListenerInjectable from "../app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable"; -import { afterApplicationIsReadyInjectionToken } from "../../main/start-main-application/after-application-is-ready/after-application-is-ready-injection-token"; +import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token"; const registerIpcChannelListenersInjectable = getInjectable({ id: "register-ipc-channel-listeners", @@ -22,7 +22,7 @@ const registerIpcChannelListenersInjectable = getInjectable({ }, }), - injectionToken: afterApplicationIsReadyInjectionToken, + injectionToken: beforeFrameStartsInjectionToken, }); export default registerIpcChannelListenersInjectable; diff --git a/src/renderer/routes/navigate-to-url.injectable.ts b/src/renderer/routes/navigate-to-url.injectable.ts index 468543d100..7aca22f292 100644 --- a/src/renderer/routes/navigate-to-url.injectable.ts +++ b/src/renderer/routes/navigate-to-url.injectable.ts @@ -6,14 +6,15 @@ import { getInjectable } from "@ogre-tools/injectable"; import observableHistoryInjectable from "../navigation/observable-history.injectable"; import { runInAction } from "mobx"; import { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token"; -import { broadcastMessage } from "../../common/ipc"; import { IpcRendererNavigationEvents } from "../navigation/events"; +import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; const navigateToUrlInjectable = getInjectable({ id: "navigate-to-url", instantiate: (di) => { const observableHistory = di.inject(observableHistoryInjectable); + const broadcastMessage = di.inject(broadcastMessageInjectable); return (url, options = {}) => { if (options.forceRootFrame) { diff --git a/src/renderer/start-frame/start-frame.injectable.ts b/src/renderer/start-frame/start-frame.injectable.ts new file mode 100644 index 0000000000..fc38cc7d5e --- /dev/null +++ b/src/renderer/start-frame/start-frame.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { runManyFor } from "../../common/runnable/run-many-for"; +import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token"; + +const startFrameInjectable = getInjectable({ + id: "start-frame", + + // TODO: Consolidate contents of bootstrap.tsx here + instantiate: (di) => async () => { + const beforeFrameStarts = runManyFor(di)(beforeFrameStartsInjectionToken); + + await beforeFrameStarts(); + }, +}); + +export default startFrameInjectable; diff --git a/src/test-utils/get-dis-for-unit-testing.ts b/src/test-utils/get-dis-for-unit-testing.ts index 3719485603..57a44d3cb7 100644 --- a/src/test-utils/get-dis-for-unit-testing.ts +++ b/src/test-utils/get-dis-for-unit-testing.ts @@ -5,7 +5,6 @@ import { getDiForUnitTesting as getRendererDi } from "../renderer/getDiForUnitTesting"; import { getDiForUnitTesting as getMainDi } from "../main/getDiForUnitTesting"; import { overrideIpcBridge } from "./override-ipc-bridge"; -import { runSetups } from "../common/setupable-injection-token/run-setups"; export const getDisForUnitTesting = ( { doGeneralOverrides } = { doGeneralOverrides: false }, @@ -18,8 +17,5 @@ export const getDisForUnitTesting = ( return { rendererDi, mainDi, - - runSetups: () => - Promise.all([runSetups(rendererDi), runSetups(mainDi)]), }; }; diff --git a/src/test-utils/override-ipc-bridge.ts b/src/test-utils/override-ipc-bridge.ts index 16fe979b47..241201d2ca 100644 --- a/src/test-utils/override-ipc-bridge.ts +++ b/src/test-utils/override-ipc-bridge.ts @@ -10,6 +10,7 @@ import asyncFn from "@async-fn/jest"; import registerIpcChannelListenerInjectable from "../renderer/app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable"; import type { SendToViewArgs } from "../main/start-main-application/lens-window/application-window/lens-window-injection-token"; import sendToChannelInElectronBrowserWindowInjectable from "../main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable"; +import { isEmpty } from "lodash/fp"; export const overrideIpcBridge = ({ @@ -89,9 +90,15 @@ export const overrideIpcBridge = ({ (browserWindow, { channel: channelName, data }: SendToViewArgs) => { const handles = rendererIpcFakeHandles.get(channelName); - handles.forEach((handle) => handle(...data)); + if (isEmpty(handles)) { + throw new Error( + `Tried to send message to channel "${channelName}" but there where no listeners. Current channels with listeners: "${[ + ...rendererIpcFakeHandles.keys(), + ].join('", "')}"`, + ); + } - return Promise.resolve(); + handles.forEach((handle) => handle(...data)); }, ); };