mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Replace static application window with ability to create many (#5979)
* Replace static application window with ability to create as many as you wish Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt tests for replacing static application window with ability to create as many as you wish and separate starting of main and window in behaviours Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make first render of application smaller in test that proves to be hard for CI Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
299922c8a7
commit
63d5a34379
98 files changed
+3283
-2743
No files matched your search
+12
-16
@@ -13,10 +13,10 @@ import type { PreferenceNavigationItem } from "../preference-navigation-items.in
|
||||
import preferenceNavigationItemsInjectable from "../preference-navigation-items.injectable";
|
||||
|
||||
describe.only("preferences - navigation block with links", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let builder: ApplicationBuilder;
|
||||
|
||||
beforeEach(() => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
builder = getApplicationBuilder();
|
||||
});
|
||||
|
||||
describe("given in preferences, when rendered", () => {
|
||||
@@ -24,17 +24,15 @@ describe.only("preferences - navigation block with links", () => {
|
||||
|
||||
describe("when general navigation items passed", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
|
||||
rendererDi.override(preferenceNavigationItemsInjectable, () =>
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(preferenceNavigationItemsInjectable, () =>
|
||||
computed(() => generalNavItems),
|
||||
);
|
||||
|
||||
builder.preferences.navigate();
|
||||
});
|
||||
|
||||
applicationBuilder.beforeRender(() => {
|
||||
applicationBuilder.preferences.navigate();
|
||||
});
|
||||
|
||||
renderer = await applicationBuilder.render();
|
||||
renderer = await builder.render();
|
||||
});
|
||||
|
||||
const links = ["General", "Proxy"];
|
||||
@@ -52,17 +50,15 @@ describe.only("preferences - navigation block with links", () => {
|
||||
|
||||
describe("when general + extension navigation items passed", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
|
||||
rendererDi.override(preferenceNavigationItemsInjectable, () =>
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(preferenceNavigationItemsInjectable, () =>
|
||||
computed(() => [...generalNavItems, ...extensionNavItems]),
|
||||
);
|
||||
|
||||
builder.preferences.navigate();
|
||||
});
|
||||
|
||||
applicationBuilder.beforeRender(() => {
|
||||
applicationBuilder.preferences.navigate();
|
||||
});
|
||||
|
||||
renderer = await applicationBuilder.render();
|
||||
renderer = await builder.render();
|
||||
});
|
||||
|
||||
const generalLinks = ["General", "Proxy"];
|
||||
|
||||
+45
-72
@@ -6,13 +6,10 @@ import "@testing-library/jest-dom/extend-expect";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import mockFs from "mock-fs";
|
||||
import * as selectEvent from "react-select-event";
|
||||
import type { CreateCluster } from "../../../../common/cluster/create-cluster-injection-token";
|
||||
import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token";
|
||||
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
|
||||
import type { OpenDeleteClusterDialog } from "../open.injectable";
|
||||
import openDeleteClusterDialogInjectable from "../open.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
||||
import createKubeconfigManagerInjectable from "../../../../main/kubeconfig-manager/create-kubeconfig-manager.injectable";
|
||||
import type { ApplicationBuilder } from "../../test-utils/get-application-builder";
|
||||
@@ -20,22 +17,8 @@ import { getApplicationBuilder } from "../../test-utils/get-application-builder"
|
||||
import normalizedPlatformInjectable from "../../../../common/vars/normalized-platform.injectable";
|
||||
import kubectlBinaryNameInjectable from "../../../../main/kubectl/binary-name.injectable";
|
||||
import kubectlDownloadingNormalizedArchInjectable from "../../../../main/kubectl/normalized-arch.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getName: () => "lens",
|
||||
setName: jest.fn(),
|
||||
setPath: jest.fn(),
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
import type { OpenDeleteClusterDialog } from "../open.injectable";
|
||||
import openDeleteClusterDialogInjectable from "../open.injectable";
|
||||
|
||||
const currentClusterServerUrl = "https://localhost";
|
||||
const nonCurrentClusterServerUrl = "http://localhost";
|
||||
@@ -87,65 +70,50 @@ users:
|
||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||
`;
|
||||
|
||||
let config: KubeConfig;
|
||||
|
||||
describe("<DeleteClusterDialog />", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let createCluster: CreateCluster;
|
||||
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
||||
let builder: ApplicationBuilder;
|
||||
|
||||
beforeEach(async () => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
beforeEach(() => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
|
||||
builder.beforeApplicationStart((mainDi) => {
|
||||
mainDi.override(createContextHandlerInjectable, () => () => undefined as never);
|
||||
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as never);
|
||||
mainDi.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||
mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
||||
rendererDi.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
});
|
||||
|
||||
mockFs();
|
||||
|
||||
applicationBuilder.beforeRender(({ rendererDi }) => {
|
||||
openDeleteClusterDialog = rendererDi.inject(openDeleteClusterDialogInjectable);
|
||||
createCluster = rendererDi.inject(createClusterInjectionToken);
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it("shows context switcher when deleting current cluster", async () => {
|
||||
const mockOpts = {
|
||||
"temp-kube-config": multiClusterConfig,
|
||||
};
|
||||
const config = new KubeConfig();
|
||||
|
||||
mockFs(mockOpts);
|
||||
|
||||
config = new KubeConfig();
|
||||
config.loadFromString(multiClusterConfig);
|
||||
|
||||
applicationBuilder.beforeRender(({ rendererDi }) => {
|
||||
const createCluster = rendererDi.inject(createClusterInjectionToken);
|
||||
const rendered = await builder.render();
|
||||
|
||||
const cluster = createCluster({
|
||||
id: "some-current-context-cluster",
|
||||
contextName: "some-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: currentClusterServerUrl,
|
||||
});
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
openDeleteClusterDialog({ cluster, config });
|
||||
const createCluster = windowDi.inject(createClusterInjectionToken);
|
||||
|
||||
const cluster = createCluster({
|
||||
id: "some-current-context-cluster",
|
||||
contextName: "some-current-context",
|
||||
preferences: {
|
||||
clusterName: "some-current-context-cluster",
|
||||
},
|
||||
kubeConfigPath: "./temp-kube-config",
|
||||
}, {
|
||||
clusterServerUrl: currentClusterServerUrl,
|
||||
});
|
||||
|
||||
const rendered = await applicationBuilder.render();
|
||||
const openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||
|
||||
openDeleteClusterDialog({ cluster, config });
|
||||
|
||||
const { getByText } = rendered;
|
||||
|
||||
@@ -158,21 +126,23 @@ describe("<DeleteClusterDialog />", () => {
|
||||
expect(getByText("some-non-current-context")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
describe("Kubeconfig with different clusters", () => {
|
||||
let rendered: RenderResult;
|
||||
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
||||
let createCluster: CreateCluster;
|
||||
let config: KubeConfig;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockOpts = {
|
||||
"temp-kube-config": multiClusterConfig,
|
||||
};
|
||||
|
||||
mockFs(mockOpts);
|
||||
|
||||
config = new KubeConfig();
|
||||
|
||||
config.loadFromString(multiClusterConfig);
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
rendered = await builder.render();
|
||||
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||
createCluster = windowDi.inject(createClusterInjectionToken);
|
||||
});
|
||||
|
||||
it("renders w/o errors", () => {
|
||||
@@ -268,18 +238,21 @@ describe("<DeleteClusterDialog />", () => {
|
||||
|
||||
describe("Kubeconfig with single cluster", () => {
|
||||
let rendered: RenderResult;
|
||||
let openDeleteClusterDialog: OpenDeleteClusterDialog;
|
||||
let createCluster: CreateCluster;
|
||||
let config: KubeConfig;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockOpts = {
|
||||
"temp-kube-config": singleClusterConfig,
|
||||
};
|
||||
|
||||
mockFs(mockOpts);
|
||||
|
||||
config = new KubeConfig();
|
||||
|
||||
config.loadFromString(singleClusterConfig);
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
rendered = await builder.render();
|
||||
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable);
|
||||
createCluster = windowDi.inject(createClusterInjectionToken);
|
||||
});
|
||||
|
||||
it("shows warning if no other contexts left", () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { cssNames, noop, stopPropagation } from "../../utils";
|
||||
import type { ObservableHistory } from "mobx-observable-history";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import observableHistoryInjectable from "../../navigation/observable-history.injectable";
|
||||
import requestAnimationFrameInjectable from "../animate/request-animation-frame.injectable";
|
||||
|
||||
// todo: refactor + handle animation-end in props.onClose()?
|
||||
|
||||
@@ -37,6 +38,7 @@ interface DialogState {
|
||||
|
||||
interface Dependencies {
|
||||
navigation: ObservableHistory<unknown>;
|
||||
requestAnimationFrame: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
@observer
|
||||
@@ -95,7 +97,7 @@ class NonInjectedDialog extends React.PureComponent<DialogProps & Dependencies &
|
||||
}
|
||||
|
||||
open() {
|
||||
requestAnimationFrame(this.onOpen); // wait for render(), bind close-event to this.elem
|
||||
this.props.requestAnimationFrame(this.onOpen); // wait for render(), bind close-event to this.elem
|
||||
this.setState({ isOpen: true });
|
||||
this.props.open?.();
|
||||
}
|
||||
@@ -182,5 +184,6 @@ export const Dialog = withInjectables<Dependencies, DialogProps>((props) => <Non
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
navigation: di.inject(observableHistoryInjectable),
|
||||
requestAnimationFrame: di.inject(requestAnimationFrameInjectable),
|
||||
}),
|
||||
});
|
||||
@@ -7,55 +7,40 @@ import React from "react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import type { IObservableArray } from "mobx";
|
||||
import { computed, observable } from "mobx";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import type { StatusBarItems } from "./status-bar-items.injectable";
|
||||
import statusBarItemsInjectable from "./status-bar-items.injectable";
|
||||
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import type { ApplicationBuilder } from "../test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../test-utils/get-application-builder";
|
||||
import getRandomIdInjectable from "../../../common/utils/get-random-id.injectable";
|
||||
|
||||
class SomeTestExtension extends LensRendererExtension {
|
||||
constructor(statusBarItems: IObservableArray<any>) {
|
||||
super({
|
||||
id: "some-id",
|
||||
absolutePath: "irrelevant",
|
||||
isBundled: false,
|
||||
isCompatible: false,
|
||||
isEnabled: false,
|
||||
manifest: { name: "some-id", version: "some-version", engines: { lens: "^5.5.0" }},
|
||||
manifestPath: "irrelevant",
|
||||
});
|
||||
|
||||
this.statusBarItems = statusBarItems;
|
||||
}
|
||||
}
|
||||
|
||||
describe("<StatusBar />", () => {
|
||||
let di: DiContainer;
|
||||
let statusBarItems: IObservableArray<any>;
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let builder: ApplicationBuilder;
|
||||
|
||||
beforeEach(async () => {
|
||||
statusBarItems = observable.array([]);
|
||||
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ rendererDi }) => {
|
||||
rendererDi.unoverride(getRandomIdInjectable);
|
||||
rendererDi.permitSideEffects(getRandomIdInjectable);
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.unoverride(getRandomIdInjectable);
|
||||
windowDi.permitSideEffects(getRandomIdInjectable);
|
||||
windowDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
});
|
||||
|
||||
applicationBuilder.extensions.renderer.enable(new SomeTestExtension(statusBarItems));
|
||||
|
||||
di = applicationBuilder.dis.rendererDi;
|
||||
builder.extensions.enable({
|
||||
id: "some-id",
|
||||
name: "some-name",
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
rendererOptions: {
|
||||
statusBarItems,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("renders w/o errors", async () => {
|
||||
const { container } = await applicationBuilder.render();
|
||||
const { container } = await builder.render();
|
||||
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
@@ -71,19 +56,21 @@ describe("<StatusBar />", () => {
|
||||
])("renders w/o errors when registrations are not type compliant (%p)", async val => {
|
||||
statusBarItems.replace([val]);
|
||||
|
||||
await expect(applicationBuilder.render()).resolves.toBeTruthy();
|
||||
await expect(builder.render()).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders items [{item: React.ReactNode}] (4.0.0-rc.1)", async () => {
|
||||
const testId = "testId";
|
||||
const text = "heee";
|
||||
|
||||
di.override(statusBarItemsInjectable, () => computed(() => ({
|
||||
right: [ () => <span data-testid={testId} >{text}</span> ],
|
||||
left: [],
|
||||
}) as StatusBarItems));
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(statusBarItemsInjectable, () => computed(() => ({
|
||||
right: [ () => <span data-testid={testId} >{text}</span> ],
|
||||
left: [],
|
||||
}) as StatusBarItems));
|
||||
});
|
||||
|
||||
const { getByTestId } = await applicationBuilder.render();
|
||||
const { getByTestId } = await builder.render();
|
||||
|
||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||
});
|
||||
@@ -96,7 +83,7 @@ describe("<StatusBar />", () => {
|
||||
item: () => <span data-testid={testId} >{text}</span>,
|
||||
}]);
|
||||
|
||||
const { getByTestId } = await applicationBuilder.render();
|
||||
const { getByTestId } = await builder.render();
|
||||
|
||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||
});
|
||||
@@ -129,7 +116,7 @@ describe("<StatusBar />", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const { getAllByTestId } = await applicationBuilder.render();
|
||||
const { getAllByTestId } = await builder.render();
|
||||
const elems = getAllByTestId("sortedElem");
|
||||
const positions = elems.map(elem => elem.textContent);
|
||||
|
||||
|
||||
@@ -5,27 +5,24 @@
|
||||
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
|
||||
import type { ObservableSet } from "mobx";
|
||||
import type { IComputedValue, ObservableMap } from "mobx";
|
||||
import { computed, observable, runInAction } from "mobx";
|
||||
import React from "react";
|
||||
import { Router } from "react-router";
|
||||
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
|
||||
import allowedResourcesInjectable from "../../cluster-frame-context/allowed-resources.injectable";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { queryByText, fireEvent } from "@testing-library/react";
|
||||
import { fireEvent, queryByText } from "@testing-library/react";
|
||||
import type { KubeResource } from "../../../common/rbac";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
|
||||
import type { ClusterStore } from "../../../common/cluster-store/cluster-store";
|
||||
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { flatMap, compact, join, get, filter, map, matches, last } from "lodash/fp";
|
||||
import { compact, filter, first, flatMap, get, join, last, map, matches } from "lodash/fp";
|
||||
import preferenceNavigationItemsInjectable from "../+preferences/preferences-navigation/preference-navigation-items.injectable";
|
||||
import navigateToPreferencesInjectable from "../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||
import type { MenuItemOpts } from "../../../main/menu/application-menu-items.injectable";
|
||||
import applicationMenuItemsInjectable from "../../../main/menu/application-menu-items.injectable";
|
||||
import type { MenuItemConstructorOptions, MenuItem } from "electron";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
|
||||
import type { MenuItem, MenuItemConstructorOptions } from "electron";
|
||||
import type { NavigateToHelmCharts } from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
|
||||
import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
|
||||
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
|
||||
@@ -39,7 +36,6 @@ import type { NamespaceStore } from "../+namespaces/store";
|
||||
import historyInjectable from "../../navigation/history.injectable";
|
||||
import type { MinimalTrayMenuItem } from "../../../main/tray/electron-tray/electron-tray.injectable";
|
||||
import electronTrayInjectable from "../../../main/tray/electron-tray/electron-tray.injectable";
|
||||
import applicationWindowInjectable from "../../../main/start-main-application/lens-window/application-window/application-window.injectable";
|
||||
import { getDiForUnitTesting as getRendererDi } from "../../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting as getMainDi } from "../../../main/getDiForUnitTesting";
|
||||
import { overrideChannels } from "../../../test-utils/channel-fakes/override-channels";
|
||||
@@ -52,7 +48,6 @@ import type { NavigateToRouteOptions } from "../../../common/front-end-routing/n
|
||||
import { navigateToRouteInjectionToken } from "../../../common/front-end-routing/navigate-to-route-injection-token";
|
||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
import type { LensExtension } from "../../../extensions/lens-extension";
|
||||
|
||||
import extensionInjectable from "../../../extensions/extension-loader/extension/extension.injectable";
|
||||
import { renderFor } from "./renderFor";
|
||||
import { RootFrame } from "../../frames/root-frame/root-frame";
|
||||
@@ -62,34 +57,50 @@ import activeKubernetesClusterInjectable from "../../cluster-frame-context/activ
|
||||
import { catalogEntityFromCluster } from "../../../main/cluster-manager";
|
||||
import namespaceStoreInjectable from "../+namespaces/store.injectable";
|
||||
import { isAllowedResource } from "../../../common/cluster/is-allowed-resource";
|
||||
import createApplicationWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-application-window.injectable";
|
||||
import type { CreateElectronWindow } from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable";
|
||||
import createElectronWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable";
|
||||
import { applicationWindowInjectionToken } from "../../../main/start-main-application/lens-window/application-window/application-window-injection-token";
|
||||
import sendToChannelInElectronBrowserWindowInjectable from "../../../main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable";
|
||||
import closeAllWindowsInjectable from "../../../main/start-main-application/lens-window/hide-all-windows/close-all-windows.injectable";
|
||||
import type { LensWindow } from "../../../main/start-main-application/lens-window/application-window/create-lens-window.injectable";
|
||||
import type { FakeExtensionOptions } from "./get-extension-fake";
|
||||
import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake";
|
||||
|
||||
type Callback = (dis: DiContainers) => void | Promise<void>;
|
||||
type Callback = (di: DiContainer) => void | Promise<void>;
|
||||
|
||||
type EnableExtensions<T> = (...extensions: T[]) => void;
|
||||
type DisableExtensions<T> = (...extensions: T[]) => void;
|
||||
type LensWindowWithHelpers = LensWindow & { rendered: RenderResult; di: DiContainer };
|
||||
|
||||
export interface ApplicationBuilder {
|
||||
dis: DiContainers;
|
||||
mainDi: DiContainer;
|
||||
setEnvironmentToClusterFrame: () => ApplicationBuilder;
|
||||
|
||||
extensions: {
|
||||
renderer: {
|
||||
enable: EnableExtensions<LensRendererExtension>;
|
||||
disable: DisableExtensions<LensRendererExtension>;
|
||||
};
|
||||
enable: (...extensions: FakeExtensionOptions[]) => void;
|
||||
disable: (...extensions: FakeExtensionOptions[]) => void;
|
||||
|
||||
main: {
|
||||
enable: EnableExtensions<LensMainExtension>;
|
||||
disable: DisableExtensions<LensMainExtension>;
|
||||
};
|
||||
get: (id: string) => {
|
||||
main: LensMainExtension;
|
||||
|
||||
enable: (...extensions: { renderer: LensRendererExtension; main: LensMainExtension }[]) => void;
|
||||
disable: (...extensions: { renderer: LensRendererExtension; main: LensMainExtension }[]) => void;
|
||||
applicationWindows: Record<string, LensRendererExtension> & {
|
||||
only: LensRendererExtension;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
applicationWindow: {
|
||||
closeAll: () => void;
|
||||
only: LensWindowWithHelpers;
|
||||
get: (id: string) => LensWindowWithHelpers;
|
||||
getAll: () => LensWindowWithHelpers[];
|
||||
create: (id: string) => LensWindowWithHelpers;
|
||||
};
|
||||
|
||||
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
|
||||
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
|
||||
beforeRender: (callback: Callback) => ApplicationBuilder;
|
||||
beforeWindowStart: (callback: Callback) => ApplicationBuilder;
|
||||
|
||||
startHidden: () => Promise<void>;
|
||||
render: () => Promise<RenderResult>;
|
||||
|
||||
tray: {
|
||||
@@ -116,17 +127,12 @@ export interface ApplicationBuilder {
|
||||
};
|
||||
|
||||
select: {
|
||||
openMenu: (id: string) => ({ selectOption: (labelText: string) => void });
|
||||
openMenu: (id: string) => { selectOption: (labelText: string) => void };
|
||||
selectOption: (menuId: string, labelText: string) => void;
|
||||
getValue: (menuId: string) => string;
|
||||
};
|
||||
}
|
||||
|
||||
interface DiContainers {
|
||||
rendererDi: DiContainer;
|
||||
mainDi: DiContainer;
|
||||
}
|
||||
|
||||
interface Environment {
|
||||
RootComponent: React.ElementType;
|
||||
onAllowKubeResource: () => void;
|
||||
@@ -137,64 +143,22 @@ export const getApplicationBuilder = () => {
|
||||
doGeneralOverrides: true,
|
||||
});
|
||||
|
||||
mainDi.register(mainExtensionsStateInjectable);
|
||||
|
||||
const overrideChannelsForWindow = overrideChannels(mainDi);
|
||||
|
||||
const rendererDi = getRendererDi({
|
||||
doGeneralOverrides: true,
|
||||
});
|
||||
|
||||
overrideChannelsForWindow(rendererDi);
|
||||
|
||||
const dis = { rendererDi, mainDi };
|
||||
|
||||
const clusterStoreStub = {
|
||||
provideInitialFromMain: () => {},
|
||||
getById: (): null => null,
|
||||
} as unknown as ClusterStore;
|
||||
|
||||
rendererDi.override(clusterStoreInjectable, () => clusterStoreStub);
|
||||
rendererDi.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
mainDi.override(clusterStoreInjectable, () => clusterStoreStub);
|
||||
|
||||
const beforeApplicationStartCallbacks: Callback[] = [];
|
||||
const beforeRenderCallbacks: Callback[] = [];
|
||||
|
||||
const rendererExtensionsState = observable.set<LensRendererExtension>();
|
||||
const mainExtensionsState = observable.set<LensMainExtension>();
|
||||
|
||||
rendererDi.override(subscribeStoresInjectable, () => () => () => {});
|
||||
|
||||
const environments = {
|
||||
application: {
|
||||
RootComponent: RootFrame,
|
||||
|
||||
onAllowKubeResource: () => {
|
||||
throw new Error(
|
||||
"Tried to allow kube resource when environment is not cluster frame.",
|
||||
);
|
||||
},
|
||||
} as Environment,
|
||||
|
||||
clusterFrame: {
|
||||
RootComponent: ClusterFrame,
|
||||
onAllowKubeResource: () => {},
|
||||
} as Environment,
|
||||
};
|
||||
const beforeWindowStartCallbacks: Callback[] = [];
|
||||
|
||||
let environment = environments.application;
|
||||
|
||||
rendererDi.override(
|
||||
currentlyInClusterFrameInjectable,
|
||||
() => environment === environments.clusterFrame,
|
||||
);
|
||||
mainDi.override(mainExtensionsInjectable, (di) => {
|
||||
const mainExtensionsState = di.inject(mainExtensionsStateInjectable);
|
||||
|
||||
rendererDi.override(rendererExtensionsInjectable, () =>
|
||||
computed(() => [...rendererExtensionsState]),
|
||||
);
|
||||
|
||||
mainDi.override(mainExtensionsInjectable, () =>
|
||||
computed(() => [...mainExtensionsState]),
|
||||
);
|
||||
return computed(() =>
|
||||
[...mainExtensionsState.values()],
|
||||
);
|
||||
});
|
||||
|
||||
let trayMenuIconPath: string;
|
||||
|
||||
@@ -209,60 +173,135 @@ export const getApplicationBuilder = () => {
|
||||
},
|
||||
}));
|
||||
|
||||
let allowedResourcesState: KubeResource[];
|
||||
let rendered: RenderResult;
|
||||
const allowedResourcesState = observable.array<KubeResource>();
|
||||
|
||||
const enableExtensionsFor = <T extends ObservableSet>(
|
||||
extensionState: T,
|
||||
di: DiContainer,
|
||||
) => {
|
||||
const getExtension = (extension: LensExtension) =>
|
||||
di.inject(extensionInjectable, extension);
|
||||
const windowHelpers = new Map<string, { di: DiContainer; getRendered: () => RenderResult }>();
|
||||
|
||||
return (...extensionInstances: LensExtension[]) => {
|
||||
const addAndEnableExtensions = () => {
|
||||
extensionInstances.forEach(instance => {
|
||||
const extension = getExtension(instance);
|
||||
const createElectronWindowFake: CreateElectronWindow = (configuration) => {
|
||||
const windowId = configuration.id;
|
||||
|
||||
extension.register();
|
||||
});
|
||||
const windowDi = getRendererDi({ doGeneralOverrides: true });
|
||||
|
||||
runInAction(() => {
|
||||
extensionInstances.forEach((extension) => {
|
||||
extensionState.add(extension);
|
||||
});
|
||||
});
|
||||
};
|
||||
overrideChannelsForWindow(windowDi, windowId);
|
||||
|
||||
if (rendered) {
|
||||
addAndEnableExtensions();
|
||||
} else {
|
||||
builder.beforeRender(addAndEnableExtensions);
|
||||
}
|
||||
windowDi.register(rendererExtensionsStateInjectable);
|
||||
|
||||
windowDi.override(
|
||||
currentlyInClusterFrameInjectable,
|
||||
() => environment === environments.clusterFrame,
|
||||
);
|
||||
|
||||
windowDi.override(rendererExtensionsInjectable, (di) => {
|
||||
const rendererExtensionState = di.inject(rendererExtensionsStateInjectable);
|
||||
|
||||
return computed(() => [...rendererExtensionState.values()]);
|
||||
});
|
||||
|
||||
let rendered: RenderResult;
|
||||
|
||||
windowHelpers.set(windowId, { di: windowDi, getRendered: () => rendered });
|
||||
|
||||
return {
|
||||
show: () => {},
|
||||
close: () => {},
|
||||
loadFile: async () => {},
|
||||
loadUrl: async () => {
|
||||
for (const callback of beforeWindowStartCallbacks) {
|
||||
await callback(windowDi);
|
||||
}
|
||||
|
||||
const startFrame = windowDi.inject(startFrameInjectable);
|
||||
|
||||
await startFrame();
|
||||
|
||||
const history = windowDi.inject(historyInjectable);
|
||||
|
||||
const render = renderFor(windowDi);
|
||||
|
||||
rendered = render(
|
||||
<Router history={history}>
|
||||
<environment.RootComponent />
|
||||
</Router>,
|
||||
);
|
||||
},
|
||||
|
||||
send: (arg) => {
|
||||
const sendFake = mainDi.inject(sendToChannelInElectronBrowserWindowInjectable) as any;
|
||||
|
||||
sendFake(windowId, null, arg);
|
||||
},
|
||||
|
||||
reload: () => {
|
||||
throw new Error("Tried to reload application window which is not implemented yet.");
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const enableRendererExtension = enableExtensionsFor(rendererExtensionsState, rendererDi);
|
||||
const enableMainExtension = enableExtensionsFor(mainExtensionsState, mainDi);
|
||||
const disableRendererExtension = disableExtensionsFor(rendererExtensionsState, rendererDi);
|
||||
const disableMainExtension = disableExtensionsFor(mainExtensionsState, mainDi);
|
||||
mainDi.override(createElectronWindowInjectable, () => createElectronWindowFake);
|
||||
|
||||
const selectOptionFor = (menuId: string) => (labelText: string) => {
|
||||
const menuOptions = rendered.baseElement.querySelector<HTMLElement>(
|
||||
`.${menuId}-options`,
|
||||
);
|
||||
|
||||
assert(menuOptions, `Could not find select options for menu with ID "${menuId}"`);
|
||||
|
||||
const option = queryByText(menuOptions, labelText);
|
||||
|
||||
assert(option, `Could not find select option with label "${labelText}" for menu with ID "${menuId}"`);
|
||||
|
||||
userEvent.click(option);
|
||||
};
|
||||
let applicationHasStarted = false;
|
||||
|
||||
const builder: ApplicationBuilder = {
|
||||
dis,
|
||||
mainDi,
|
||||
|
||||
applicationWindow: {
|
||||
closeAll: () => {
|
||||
const closeAll = mainDi.inject(closeAllWindowsInjectable);
|
||||
|
||||
closeAll();
|
||||
|
||||
document.documentElement.innerHTML = "";
|
||||
},
|
||||
|
||||
get only() {
|
||||
const applicationWindows = builder.applicationWindow.getAll();
|
||||
|
||||
if (applicationWindows.length > 1) {
|
||||
throw new Error(
|
||||
"Tried to get only application window when there are multiple windows.",
|
||||
);
|
||||
}
|
||||
|
||||
const applicationWindow = first(applicationWindows);
|
||||
|
||||
if (!applicationWindow) {
|
||||
throw new Error(
|
||||
"Tried to get only application window when there are no windows.",
|
||||
);
|
||||
}
|
||||
|
||||
return applicationWindow;
|
||||
},
|
||||
|
||||
getAll: () =>
|
||||
mainDi
|
||||
.injectMany(applicationWindowInjectionToken)
|
||||
.map(toWindowWithHelpersFor(windowHelpers)),
|
||||
|
||||
get: (id) => {
|
||||
const applicationWindows = builder.applicationWindow.getAll();
|
||||
|
||||
const applicationWindow = applicationWindows.find(
|
||||
(window) => window.id === id,
|
||||
);
|
||||
|
||||
if (!applicationWindow) {
|
||||
throw new Error(`Tried to get application window with ID "${id}" but it was not found.`);
|
||||
}
|
||||
|
||||
return applicationWindow;
|
||||
},
|
||||
|
||||
create: (id) => {
|
||||
const createApplicationWindow = mainDi.inject(
|
||||
createApplicationWindowInjectable,
|
||||
);
|
||||
|
||||
createApplicationWindow(id);
|
||||
|
||||
return builder.applicationWindow.get(id);
|
||||
},
|
||||
},
|
||||
|
||||
applicationMenu: {
|
||||
click: (path: string) => {
|
||||
@@ -340,29 +379,39 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
preferences: {
|
||||
close: () => {
|
||||
const rendered = builder.applicationWindow.only.rendered;
|
||||
|
||||
const link = rendered.getByTestId("close-preferences");
|
||||
|
||||
fireEvent.click(link);
|
||||
},
|
||||
|
||||
navigate: () => {
|
||||
const navigateToPreferences = rendererDi.inject(navigateToPreferencesInjectable);
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
const navigateToPreferences = windowDi.inject(
|
||||
navigateToPreferencesInjectable,
|
||||
);
|
||||
|
||||
navigateToPreferences();
|
||||
},
|
||||
|
||||
navigateTo: (route: Route<any>, params: Partial<NavigateToRouteOptions<any>>) => {
|
||||
const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken);
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
const navigateToRoute = windowDi.inject(navigateToRouteInjectionToken);
|
||||
|
||||
navigateToRoute(route, params);
|
||||
},
|
||||
|
||||
navigation: {
|
||||
click: (id: string) => {
|
||||
const { di: windowDi, rendered } = builder.applicationWindow.only;
|
||||
|
||||
const link = rendered.queryByTestId(`tab-link-for-${id}`);
|
||||
|
||||
if (!link) {
|
||||
const preferencesNavigationItems = rendererDi.inject(
|
||||
const preferencesNavigationItems = windowDi.inject(
|
||||
preferenceNavigationItemsInjectable,
|
||||
);
|
||||
|
||||
@@ -382,7 +431,11 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
helmCharts: {
|
||||
navigate: (parameters) => {
|
||||
const navigateToHelmCharts = rendererDi.inject(navigateToHelmChartsInjectable);
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
|
||||
const navigateToHelmCharts = windowDi.inject(
|
||||
navigateToHelmChartsInjectable,
|
||||
);
|
||||
|
||||
navigateToHelmCharts(parameters);
|
||||
},
|
||||
@@ -391,81 +444,129 @@ export const getApplicationBuilder = () => {
|
||||
setEnvironmentToClusterFrame: () => {
|
||||
environment = environments.clusterFrame;
|
||||
|
||||
allowedResourcesState = observable.array();
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(allowedResourcesInjectable, () =>
|
||||
computed(() => new Set([...allowedResourcesState])),
|
||||
);
|
||||
|
||||
rendererDi.override(allowedResourcesInjectable, () =>
|
||||
computed(() => new Set([...allowedResourcesState])),
|
||||
);
|
||||
const clusterStub = {
|
||||
accessibleNamespaces: [],
|
||||
isAllowedResource: isAllowedResource(allowedResourcesState),
|
||||
} as unknown as Cluster;
|
||||
|
||||
const clusterStub = {
|
||||
accessibleNamespaces: [],
|
||||
isAllowedResource: isAllowedResource(allowedResourcesState),
|
||||
} as unknown as Cluster;
|
||||
windowDi.override(activeKubernetesClusterInjectable, () =>
|
||||
computed(() => catalogEntityFromCluster(clusterStub)),
|
||||
);
|
||||
|
||||
rendererDi.override(activeKubernetesClusterInjectable, () =>
|
||||
computed(() => catalogEntityFromCluster(clusterStub)),
|
||||
);
|
||||
// TODO: Figure out a way to remove this stub.
|
||||
const namespaceStoreStub = {
|
||||
isLoaded: true,
|
||||
contextNamespaces: [],
|
||||
contextItems: [],
|
||||
api: {
|
||||
kind: "Namespace",
|
||||
},
|
||||
items: [],
|
||||
selectNamespaces: () => {},
|
||||
getByPath: () => undefined,
|
||||
pickOnlySelected: () => [],
|
||||
isSelectedAll: () => false,
|
||||
getTotalCount: () => 0,
|
||||
} as unknown as NamespaceStore;
|
||||
|
||||
// TODO: Figure out a way to remove this stub.
|
||||
const namespaceStoreStub = {
|
||||
isLoaded: true,
|
||||
contextNamespaces: [],
|
||||
contextItems: [],
|
||||
api: {
|
||||
kind: "Namespace",
|
||||
},
|
||||
items: [],
|
||||
selectNamespaces: () => {},
|
||||
getByPath: () => undefined,
|
||||
pickOnlySelected: () => [],
|
||||
isSelectedAll: () => false,
|
||||
getTotalCount: () => 0,
|
||||
} as unknown as NamespaceStore;
|
||||
const clusterFrameContextFake = new ClusterFrameContext(
|
||||
clusterStub,
|
||||
|
||||
const clusterFrameContextFake = new ClusterFrameContext(
|
||||
clusterStub,
|
||||
{
|
||||
namespaceStore: namespaceStoreStub,
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
namespaceStore: namespaceStoreStub,
|
||||
},
|
||||
);
|
||||
windowDi.override(namespaceStoreInjectable, () => namespaceStoreStub);
|
||||
windowDi.override(hostedClusterInjectable, () => clusterStub);
|
||||
windowDi.override(hostedClusterIdInjectable, () => "irrelevant-hosted-cluster-id");
|
||||
windowDi.override(clusterFrameContextInjectable, () => clusterFrameContextFake);
|
||||
|
||||
rendererDi.override(namespaceStoreInjectable, () => namespaceStoreStub);
|
||||
rendererDi.override(hostedClusterInjectable, () => clusterStub);
|
||||
rendererDi.override(hostedClusterIdInjectable, () => "irrelevant-hosted-cluster-id");
|
||||
rendererDi.override(clusterFrameContextInjectable, () => clusterFrameContextFake);
|
||||
|
||||
// Todo: get rid of global state.
|
||||
KubeObjectStore.defaultContext.set(clusterFrameContextFake);
|
||||
// Todo: get rid of global state.
|
||||
KubeObjectStore.defaultContext.set(clusterFrameContextFake);
|
||||
});
|
||||
|
||||
return builder;
|
||||
},
|
||||
|
||||
extensions: {
|
||||
renderer: {
|
||||
enable: enableRendererExtension,
|
||||
disable: disableRendererExtension,
|
||||
},
|
||||
get: (id: string) => {
|
||||
const windowInstances = pipeline(
|
||||
builder.applicationWindow.getAll(),
|
||||
|
||||
main: {
|
||||
enable: enableMainExtension,
|
||||
disable: disableMainExtension,
|
||||
map((window): [string, LensRendererExtension] => [
|
||||
window.id,
|
||||
findExtensionInstance(window.di, rendererExtensionsInjectable, id),
|
||||
]),
|
||||
|
||||
items => Object.fromEntries(items),
|
||||
);
|
||||
|
||||
return {
|
||||
main: findExtensionInstance(mainDi, mainExtensionsInjectable, id),
|
||||
|
||||
applicationWindows: {
|
||||
get only() {
|
||||
return findExtensionInstance(
|
||||
builder.applicationWindow.only.di,
|
||||
rendererExtensionsInjectable,
|
||||
id,
|
||||
);
|
||||
},
|
||||
|
||||
...windowInstances,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
enable: (...extensions) => {
|
||||
const rendererExtensions = extensions.map(extension => extension.renderer);
|
||||
const mainExtensions = extensions.map(extension => extension.main);
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
const rendererExtensionInstances = extensions.map((options) =>
|
||||
getExtensionFakeForRenderer(
|
||||
windowDi,
|
||||
options.id,
|
||||
options.name,
|
||||
options.rendererOptions || {},
|
||||
),
|
||||
);
|
||||
|
||||
enableRendererExtension(...rendererExtensions);
|
||||
enableMainExtension(...mainExtensions);
|
||||
rendererExtensionInstances.forEach(
|
||||
enableExtensionFor(windowDi, rendererExtensionsStateInjectable),
|
||||
);
|
||||
});
|
||||
|
||||
builder.beforeApplicationStart((mainDi) => {
|
||||
const mainExtensionInstances = extensions.map((extension) =>
|
||||
getExtensionFakeForMain(mainDi, extension.id, extension.name, extension.mainOptions || {}),
|
||||
);
|
||||
|
||||
mainExtensionInstances.forEach(
|
||||
enableExtensionFor(mainDi, mainExtensionsStateInjectable),
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
disable: (...extensions) => {
|
||||
const rendererExtensions = extensions.map(extension => extension.renderer);
|
||||
const mainExtensions = extensions.map(extension => extension.main);
|
||||
builder.beforeWindowStart(windowDi => {
|
||||
extensions
|
||||
.map((ext) => ext.id)
|
||||
.forEach(
|
||||
disableExtensionFor(windowDi, rendererExtensionsStateInjectable),
|
||||
);
|
||||
});
|
||||
|
||||
disableRendererExtension(...rendererExtensions);
|
||||
disableMainExtension(...mainExtensions);
|
||||
builder.beforeApplicationStart(mainDi => {
|
||||
extensions
|
||||
.map((ext) => ext.id)
|
||||
.forEach(
|
||||
disableExtensionFor(mainDi, mainExtensionsStateInjectable),
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -479,56 +580,72 @@ export const getApplicationBuilder = () => {
|
||||
return builder;
|
||||
},
|
||||
|
||||
beforeApplicationStart(callback: (dis: DiContainers) => void) {
|
||||
beforeApplicationStart(callback) {
|
||||
if (applicationHasStarted) {
|
||||
callback(mainDi);
|
||||
}
|
||||
|
||||
beforeApplicationStartCallbacks.push(callback);
|
||||
|
||||
return builder;
|
||||
},
|
||||
|
||||
beforeRender(callback: (dis: DiContainers) => void) {
|
||||
beforeRenderCallbacks.push(callback);
|
||||
beforeWindowStart(callback) {
|
||||
const alreadyRenderedWindows = builder.applicationWindow.getAll();
|
||||
|
||||
alreadyRenderedWindows.forEach((window) => {
|
||||
callback(window.di);
|
||||
});
|
||||
|
||||
beforeWindowStartCallbacks.push(callback);
|
||||
|
||||
return builder;
|
||||
},
|
||||
|
||||
startHidden: async () => {
|
||||
mainDi.inject(lensProxyPortInjectable).set(42);
|
||||
|
||||
for (const callback of beforeApplicationStartCallbacks) {
|
||||
await callback(mainDi);
|
||||
}
|
||||
|
||||
const startMainApplication = mainDi.inject(
|
||||
startMainApplicationInjectable,
|
||||
);
|
||||
|
||||
await startMainApplication(false);
|
||||
|
||||
applicationHasStarted = true;
|
||||
},
|
||||
|
||||
async render() {
|
||||
mainDi.inject(lensProxyPortInjectable).set(42);
|
||||
|
||||
for (const callback of beforeApplicationStartCallbacks) {
|
||||
await callback(dis);
|
||||
await callback(mainDi);
|
||||
}
|
||||
|
||||
const startMainApplication = mainDi.inject(startMainApplicationInjectable);
|
||||
|
||||
await startMainApplication();
|
||||
|
||||
const applicationWindow = mainDi.inject(applicationWindowInjectable);
|
||||
|
||||
await applicationWindow.start();
|
||||
|
||||
const startFrame = rendererDi.inject(startFrameInjectable);
|
||||
|
||||
await startFrame();
|
||||
|
||||
for (const callback of beforeRenderCallbacks) {
|
||||
await callback(dis);
|
||||
}
|
||||
|
||||
const history = rendererDi.inject(historyInjectable);
|
||||
|
||||
const render = renderFor(rendererDi);
|
||||
|
||||
rendered = render(
|
||||
<Router history={history}>
|
||||
<environment.RootComponent />
|
||||
</Router>,
|
||||
const startMainApplication = mainDi.inject(
|
||||
startMainApplicationInjectable,
|
||||
);
|
||||
|
||||
return rendered;
|
||||
await startMainApplication(true);
|
||||
|
||||
applicationHasStarted = true;
|
||||
|
||||
const applicationWindow = builder.applicationWindow.get(
|
||||
"first-application-window",
|
||||
);
|
||||
|
||||
assert(applicationWindow);
|
||||
|
||||
return applicationWindow.rendered;
|
||||
},
|
||||
|
||||
select: {
|
||||
openMenu: (menuId) => {
|
||||
const rendered = builder.applicationWindow.only.rendered;
|
||||
|
||||
const select = rendered.baseElement.querySelector<HTMLElement>(
|
||||
`#${menuId}`,
|
||||
);
|
||||
@@ -538,13 +655,15 @@ export const getApplicationBuilder = () => {
|
||||
openMenu(select);
|
||||
|
||||
return {
|
||||
selectOption: selectOptionFor(menuId),
|
||||
selectOption: selectOptionFor(builder, menuId),
|
||||
};
|
||||
},
|
||||
|
||||
selectOption: (menuId, labelText) => selectOptionFor(menuId)(labelText),
|
||||
selectOption: (menuId, labelText) => selectOptionFor(builder, menuId)(labelText),
|
||||
|
||||
getValue: (menuId) => {
|
||||
const rendered = builder.applicationWindow.only.rendered;
|
||||
|
||||
const select = rendered.baseElement.querySelector<HTMLInputElement>(
|
||||
`#${menuId}`,
|
||||
);
|
||||
@@ -582,22 +701,134 @@ function toFlatChildren(parentId: string | null | undefined): ToFlatChildren {
|
||||
];
|
||||
}
|
||||
|
||||
const disableExtensionsFor = <T extends ObservableSet>(
|
||||
extensionState: T,
|
||||
export const rendererExtensionsStateInjectable = getInjectable({
|
||||
id: "renderer-extensions-state",
|
||||
instantiate: () => observable.map<string, LensRendererExtension>(),
|
||||
});
|
||||
|
||||
const mainExtensionsStateInjectable = getInjectable({
|
||||
id: "main-extensions-state",
|
||||
instantiate: () => observable.map<string, LensMainExtension>(),
|
||||
});
|
||||
|
||||
const findExtensionInstance = <T extends LensExtension> (di: DiContainer, injectable: Injectable<IComputedValue<T[]>, any, any>, id: string) => {
|
||||
const instance = di.inject(injectable).get().find(ext => ext.id === id);
|
||||
|
||||
if (!instance) {
|
||||
throw new Error(`Tried to get extension with ID ${id}, but it didn't exist`);
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
type ApplicationWindowHelpers = Map<string, { di: DiContainer; getRendered: () => RenderResult }>;
|
||||
|
||||
const toWindowWithHelpersFor =
|
||||
(windowHelpers: ApplicationWindowHelpers) => (applicationWindow: LensWindow) => ({
|
||||
...applicationWindow,
|
||||
|
||||
get rendered() {
|
||||
const helpers = windowHelpers.get(applicationWindow.id);
|
||||
|
||||
if (!helpers) {
|
||||
throw new Error(
|
||||
`Tried to get rendered for application window "${applicationWindow.id}" before it was started.`,
|
||||
);
|
||||
}
|
||||
|
||||
return helpers.getRendered();
|
||||
},
|
||||
|
||||
get di() {
|
||||
const helpers = windowHelpers.get(applicationWindow.id);
|
||||
|
||||
if (!helpers) {
|
||||
throw new Error(
|
||||
`Tried to get di for application window "${applicationWindow.id}" before it was started.`,
|
||||
);
|
||||
}
|
||||
|
||||
return helpers.di;
|
||||
},
|
||||
});
|
||||
|
||||
const environments = {
|
||||
application: {
|
||||
RootComponent: RootFrame,
|
||||
|
||||
onAllowKubeResource: () => {
|
||||
throw new Error(
|
||||
"Tried to allow kube resource when environment is not cluster frame.",
|
||||
);
|
||||
},
|
||||
} as Environment,
|
||||
|
||||
clusterFrame: {
|
||||
RootComponent: ClusterFrame,
|
||||
onAllowKubeResource: () => {},
|
||||
} as Environment,
|
||||
};
|
||||
|
||||
const selectOptionFor = (builder: ApplicationBuilder, menuId: string) => (labelText: string) => {
|
||||
const rendered = builder.applicationWindow.only.rendered;
|
||||
|
||||
const menuOptions = rendered.baseElement.querySelector<HTMLElement>(
|
||||
`.${menuId}-options`,
|
||||
);
|
||||
|
||||
assert(menuOptions, `Could not find select options for menu with ID "${menuId}"`);
|
||||
|
||||
const option = queryByText(menuOptions, labelText);
|
||||
|
||||
assert(option, `Could not find select option with label "${labelText}" for menu with ID "${menuId}"`);
|
||||
|
||||
userEvent.click(option);
|
||||
};
|
||||
|
||||
const enableExtensionFor = (
|
||||
di: DiContainer,
|
||||
stateInjectable: Injectable<ObservableMap<string, any>, any, any>,
|
||||
) => {
|
||||
const getExtension = (instance: LensExtension) =>
|
||||
di.inject(extensionInjectable, instance);
|
||||
const extensionState = di.inject(stateInjectable);
|
||||
|
||||
return (...extensionInstances: LensExtension[]) => {
|
||||
extensionInstances.forEach((instance) => {
|
||||
const extension = getExtension(instance);
|
||||
const getExtension = (extension: LensExtension) =>
|
||||
di.inject(extensionInjectable, extension);
|
||||
|
||||
runInAction(() => {
|
||||
extension.deregister();
|
||||
return (extensionInstance: LensExtension) => {
|
||||
const extension = getExtension(extensionInstance);
|
||||
|
||||
extensionState.delete(instance);
|
||||
});
|
||||
runInAction(() => {
|
||||
extension.register();
|
||||
extensionState.set(extensionInstance.id, extensionInstance);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const disableExtensionFor =
|
||||
(
|
||||
di: DiContainer,
|
||||
stateInjectable: Injectable<ObservableMap<string, any>, unknown, void>,
|
||||
) =>
|
||||
(id: string) => {
|
||||
const getExtension = (extension: LensExtension) =>
|
||||
di.inject(extensionInjectable, extension);
|
||||
|
||||
const extensionsState = di.inject(stateInjectable);
|
||||
|
||||
const instance = extensionsState.get(id);
|
||||
|
||||
if (!instance) {
|
||||
throw new Error(
|
||||
`Tried to disable extension with ID "${id}", but it wasn't enabled`,
|
||||
);
|
||||
}
|
||||
|
||||
const injectable = getExtension(instance);
|
||||
|
||||
runInAction(() => {
|
||||
injectable.deregister();
|
||||
|
||||
extensionsState.delete(id);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import type { Mutable } from "type-fest";
|
||||
import fileSystemProvisionerStoreInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
|
||||
import { lensExtensionDependencies } from "../../../extensions/lens-extension";
|
||||
import type { ApplicationBuilder } from "./get-application-builder";
|
||||
import { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
import navigateForExtensionInjectable from "../../../main/start-main-application/lens-window/navigate-for-extension.injectable";
|
||||
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
@@ -27,21 +26,7 @@ export interface FakeExtensionOptions {
|
||||
mainOptions?: Partial<LensMainExtension>;
|
||||
}
|
||||
|
||||
export type GetExtensionFake = (arg: FakeExtensionOptions) => {
|
||||
main: TestExtensionMain;
|
||||
renderer: TestExtensionRenderer;
|
||||
};
|
||||
|
||||
export const getExtensionFakeFor =
|
||||
(builder: ApplicationBuilder): GetExtensionFake =>
|
||||
({ id, name, mainOptions = {}, rendererOptions = {}}) => {
|
||||
const mainInstance = getExtensionFakeForMain(builder.dis.mainDi, id, name, mainOptions);
|
||||
const rendererInstance = getExtensionFakeForRenderer(builder.dis.rendererDi, id, name, rendererOptions);
|
||||
|
||||
return { main: mainInstance, renderer: rendererInstance };
|
||||
};
|
||||
|
||||
const getExtensionFakeForMain = (di: DiContainer, id: string, name: string, options: Partial<LensMainExtension>) => {
|
||||
export const getExtensionFakeForMain = (di: DiContainer, id: string, name: string, options: Partial<LensMainExtension>) => {
|
||||
const instance = new TestExtensionMain({
|
||||
id,
|
||||
absolutePath: "irrelevant",
|
||||
@@ -71,7 +56,7 @@ const getExtensionFakeForMain = (di: DiContainer, id: string, name: string, opti
|
||||
return instance;
|
||||
};
|
||||
|
||||
const getExtensionFakeForRenderer = (di: DiContainer, id: string, name: string, options: Partial<LensRendererExtension>) => {
|
||||
export const getExtensionFakeForRenderer = (di: DiContainer, id: string, name: string, options: Partial<LensRendererExtension>) => {
|
||||
const instance = new TestExtensionRenderer({
|
||||
id,
|
||||
absolutePath: "irrelevant",
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { SetRequired } from "type-fest";
|
||||
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
import type { ApplicationBuilder } from "./get-application-builder";
|
||||
import { getExtensionFakeFor } from "./get-extension-fake";
|
||||
|
||||
export class TestExtension extends LensRendererExtension {}
|
||||
|
||||
export type FakeExtensionData = SetRequired<Partial<LensRendererExtension>, "id" | "name">;
|
||||
|
||||
export type GetRendererExtensionFake = (fakeExtensionData: FakeExtensionData) => TestExtension;
|
||||
|
||||
export const getRendererExtensionFakeFor = (
|
||||
builder: ApplicationBuilder,
|
||||
): GetRendererExtensionFake => {
|
||||
const getExtensionFake = getExtensionFakeFor(builder);
|
||||
|
||||
return ({ id, name, ...rest }) =>
|
||||
getExtensionFake({
|
||||
id,
|
||||
name,
|
||||
rendererOptions: rest,
|
||||
}).renderer;
|
||||
};
|
||||
|
||||
@@ -18,9 +18,6 @@ import extensionsStoreInjectable from "../extensions/extensions-store/extensions
|
||||
import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store";
|
||||
import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
|
||||
import type { FileSystemProvisionerStore } from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
|
||||
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
|
||||
import type { ClusterStore } from "../common/cluster-store/cluster-store";
|
||||
import type { Cluster } from "../common/cluster/cluster";
|
||||
import userStoreInjectable from "../common/user-store/user-store.injectable";
|
||||
import type { UserStore } from "../common/user-store";
|
||||
import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectable";
|
||||
@@ -41,7 +38,6 @@ import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.inject
|
||||
import { observable, computed } from "mobx";
|
||||
import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
|
||||
import appVersionInjectable from "../common/vars/app-version.injectable";
|
||||
import provideInitialValuesForSyncBoxesInjectable from "./utils/sync-box/provide-initial-values-for-sync-boxes.injectable";
|
||||
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
|
||||
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
||||
import getFilePathsInjectable from "./components/+preferences/kubernetes/helm-charts/adding-of-custom-helm-repository/helm-file-input/get-file-paths.injectable";
|
||||
@@ -189,12 +185,8 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
|
||||
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||
di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore);
|
||||
|
||||
di.override(setupSystemCaInjectable, () => ({ run: () => {} }));
|
||||
di.override(setupOnApiErrorListenersInjectable, () => ({ run: () => {} }));
|
||||
di.override(provideInitialValuesForSyncBoxesInjectable, () => ({ run: () => {} }));
|
||||
|
||||
di.override(defaultShellInjectable, () => "some-default-shell");
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
|
||||
import subscribeStoresInjectable from "./subscribe-stores.injectable";
|
||||
|
||||
export default getGlobalOverride(
|
||||
subscribeStoresInjectable,
|
||||
() => () => () => {},
|
||||
);
|
||||
@@ -4,11 +4,17 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { storesAndApisCanBeCreatedInjectionToken } from "../common/k8s-api/stores-apis-can-be-created.token";
|
||||
import { getClusterIdFromHost } from "./utils";
|
||||
import hostedClusterIdInjectable from "./cluster-frame-context/hosted-cluster-id.injectable";
|
||||
|
||||
const storesAndApisCanBeCreatedInjectable = getInjectable({
|
||||
id: "create-stores-and-apis",
|
||||
instantiate: () => Boolean(getClusterIdFromHost(location.host)),
|
||||
|
||||
instantiate: (di) => {
|
||||
const hostedClusterId = di.inject(hostedClusterIdInjectable);
|
||||
|
||||
return !!hostedClusterId;
|
||||
},
|
||||
|
||||
injectionToken: storesAndApisCanBeCreatedInjectionToken,
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user