1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Consolidate code for application window

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-05-02 12:03:46 +03:00
parent 5ac9b2aa87
commit 55d1471c10
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
68 changed files with 730 additions and 387 deletions

View File

@ -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", () => {

View File

@ -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", () => {

View File

@ -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", () => {

View File

@ -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", () => {

View File

@ -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);

View File

@ -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<CatalogEntityData> & 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", () => {

View File

@ -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<void[]>;
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", () => {

View File

@ -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;

View File

@ -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()),
);
};

View File

@ -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(() => {

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -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),

View File

@ -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);

View File

@ -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();
}
});
},

View File

@ -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;

View File

@ -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",

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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(),
);
},
},

View File

@ -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);
};
},

View File

@ -12,8 +12,8 @@ const navigateToUrlInjectable = getInjectable({
instantiate: (di) => {
const navigate = di.inject(navigateInjectable);
return (url) => {
navigate(url);
return async (url) => {
await navigate(url);
};
},

View File

@ -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);

View File

@ -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),
}),
});

View File

@ -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<boolean>;
@ -42,7 +39,7 @@ function checkHost<Query>(url: URLParse<Query>): boolean {
interface Dependencies {
extensionLoader: ExtensionLoader;
extensionsStore: ExtensionsStore;
applicationWindow: LensWindow;
showApplicationWindow: () => Promise<void>;
}
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()}`);

View File

@ -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);
});

View File

@ -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<Runnable>({
id: "after-application-is-loaded",

View File

@ -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<Runnable>({
id: "after-root-frame-is-ready",

View File

@ -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<Runnable>({
id: "after-window-is-opened",
});

View File

@ -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<Runnable>({
id: "before-application-is-loading",

View File

@ -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<RunnableSync>({

View File

@ -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<RunnableSync>({

View File

@ -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<RunnableSync>({

View File

@ -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;
};

View File

@ -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;

View File

@ -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<void>((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,

View File

@ -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<void>;
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<LensWindow> => {
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<void>((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;

View File

@ -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<void>;
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;

View File

@ -13,9 +13,9 @@ export interface SendToViewArgs {
export interface LensWindow {
show: () => Promise<void>;
hide: () => void;
close: () => void;
send: (args: SendToViewArgs) => Promise<void>;
visible: boolean;
}
export const lensWindowInjectionToken = getInjectionToken<LensWindow>({

View File

@ -11,7 +11,7 @@ const sendToChannelInElectronBrowserWindowInjectable = getInjectable({
instantiate:
() =>
async (
(
browserWindow: BrowserWindow,
{ channel, frameInfo, data = [] }: SendToViewArgs,
) => {

View File

@ -26,6 +26,8 @@ const setupListenerForCurrentClusterFrameInjectable = getInjectable({
},
}),
causesSideEffects: true,
injectionToken: whenApplicationIsLoadingInjectionToken,
});

View File

@ -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;

View File

@ -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<string, any>,
frameId?: number,
) => {
await applicationWindow.show();
await showApplicationWindow();
const frameInfo = iter.find(
clusterFrames.values(),

View File

@ -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(),

View File

@ -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;

View File

@ -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();
}

View File

@ -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({

View File

@ -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<Runnable>({
id: "when-application-is-loading",

View File

@ -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);

View File

@ -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,
),
);

View File

@ -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<void>,
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<void>,
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 }));
},

View File

@ -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;

View File

@ -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<Runnable>({
id: "after-application-is-ready",
export const beforeFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "before-frame-starts",
});

View File

@ -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();

View File

@ -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("<DeleteClusterDialog />", () => {
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: () => <DeleteClusterDialog />,
}),
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("<DeleteClusterDialog />", () => {
});
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("<DeleteClusterDialog />", () => {
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("<DeleteClusterDialog />", () => {
});
it("renders w/o errors", () => {
const { container } = render(<DeleteClusterDialog />);
expect(container).toBeInstanceOf(HTMLElement);
expect(rendered.container).toBeInstanceOf(HTMLElement);
});
it("shows warning when deleting non-current-context cluster", () => {
@ -150,11 +194,10 @@ describe("<DeleteClusterDialog />", () => {
});
deleteClusterDialogModel.open({ cluster, config });
const { getByText } = render(<DeleteClusterDialog />);
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("<DeleteClusterDialog />", () => {
deleteClusterDialogModel.open({ cluster, config });
const { getByTestId } = render(<DeleteClusterDialog />);
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("<DeleteClusterDialog />", () => {
deleteClusterDialogModel.open({ cluster, config });
const { getByText } = render(<DeleteClusterDialog />);
const { getByText } = rendered;
expect(getByText("Select...")).toBeInTheDocument();
selectEvent.openMenu(getByText("Select..."));
@ -207,7 +248,7 @@ describe("<DeleteClusterDialog />", () => {
deleteClusterDialogModel.open({ cluster, config });
const { getByText, getByTestId } = render(<DeleteClusterDialog />);
const { getByText, getByTestId } = rendered;
const link = getByTestId("context-switch");
expect(link).toBeInstanceOf(HTMLElement);
@ -234,15 +275,17 @@ describe("<DeleteClusterDialog />", () => {
deleteClusterDialogModel.open({ cluster, config });
const { getByTestId } = render(<DeleteClusterDialog />);
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("<DeleteClusterDialog />", () => {
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("<DeleteClusterDialog />", () => {
deleteClusterDialogModel.open({ cluster, config });
const { getByTestId } = render(<DeleteClusterDialog />);
expect(getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement);
expect(rendered.getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement);
});
});
});

View File

@ -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<void>;
@ -55,7 +57,7 @@ export interface ApplicationBuilder {
render: () => Promise<RenderResult>;
applicationMenu: {
click: (path: string) => void;
click: (path: string) => Promise<void>;
};
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);

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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)]),
};
};

View File

@ -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));
},
);
};