1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/test-utils/get-application-builder.tsx
Sebastian Malton a32a1ad5cb fix behavioural test
Signed-off-by: Sebastian Malton <sebastian@malton.name>
2022-04-29 08:50:11 -04:00

332 lines
9.9 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
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 { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
import type { IObservableArray } from "mobx";
import { computed, observable, runInAction } from "mobx";
import { renderFor } from "./renderFor";
import observableHistoryInjectable from "../../navigation/observable-history.injectable";
import React from "react";
import { Router } from "react-router";
import { Observer } from "mobx-react";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import allowedResourcesInjectable from "../../../common/cluster-store/allowed-resources.injectable";
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import type { KubeResource } from "../../../common/rbac";
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
import { Sidebar } from "../layout/sidebar";
import { getDisForUnitTesting } from "../../../test-utils/get-dis-for-unit-testing";
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 mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
import { pipeline } from "@ogre-tools/fp";
import { flatMap, compact, join, get, filter } 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 showAboutInjectable from "../../../main/menu/show-about.injectable";
type Callback = (dis: DiContainers) => void | Promise<void>;
export interface ApplicationMenu {
click: (path: string) => void;
}
export interface ApplicationPreferences {
close: () => void;
navigate: () => void;
navigation: {
click: (id: string) => void;
};
}
export interface ApplicationActions {
showAbout: jest.MockedFunction<() => void>;
}
export interface ApplicationBuilder {
setEnvironmentToClusterFrame: () => ApplicationBuilder;
addExtensions: (...extensions: LensRendererExtension[]) => Promise<ApplicationBuilder>;
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
beforeSetups: (callback: Callback) => ApplicationBuilder;
beforeRender: (callback: Callback) => ApplicationBuilder;
render: () => Promise<RenderResult>;
readonly dis: DiContainers;
readonly menu: ApplicationMenu;
readonly preferences: ApplicationPreferences;
readonly actions: ApplicationActions;
}
interface DiContainers {
rendererDi: DiContainer;
mainDi: DiContainer;
}
interface Environment {
renderSidebar: () => React.ReactNode;
onAllowKubeResource: () => void;
}
export const getApplicationBuilder = () => {
const { rendererDi, mainDi, runSetups } = getDisForUnitTesting({
doGeneralOverrides: true,
});
const dis = { rendererDi, mainDi };
const clusterStoreStub = {
getById: (): null => null,
} as unknown as ClusterStore;
rendererDi.override(clusterStoreInjectable, () => clusterStoreStub);
mainDi.override(clusterStoreInjectable, () => clusterStoreStub);
const beforeSetupsCallbacks: Callback[] = [];
const beforeRenderCallbacks: Callback[] = [];
const extensionsState = observable.array<LensRendererExtension>();
rendererDi.override(subscribeStoresInjectable, () => () => () => {});
const environments: Record<string, Environment> = {
application: {
renderSidebar: () => null,
onAllowKubeResource: () => {
throw new Error(
"Tried to allow kube resource when environment is not cluster frame.",
);
},
},
clusterFrame: {
renderSidebar: () => <Sidebar />,
onAllowKubeResource: () => {},
},
};
let environment = environments.application;
rendererDi.override(
currentlyInClusterFrameInjectable,
() => environment === environments.clusterFrame,
);
rendererDi.override(rendererExtensionsInjectable, () =>
computed(() => extensionsState),
);
mainDi.override(mainExtensionsInjectable, () =>
computed((): LensMainExtension[] => []),
);
const showAbout = jest.fn();
mainDi.override(showAboutInjectable, () => showAbout);
let allowedResourcesState: IObservableArray<KubeResource>;
let rendered: RenderResult;
const builder: ApplicationBuilder = {
dis,
actions: {
showAbout,
},
menu: {
click: (path: string) => {
const applicationMenuItems = mainDi.inject(
applicationMenuItemsInjectable,
);
const menuItems = pipeline(
applicationMenuItems.get(),
flatMap(toFlatChildren(null)),
filter((menuItem) => !!menuItem.click),
);
const menuItem = menuItems.find((menuItem) => menuItem.path === path);
if (!menuItem) {
const availableIds = menuItems.map(get("path")).join('", "');
throw new Error(
`Tried to click application menu item with ID "${path}" which does not exist. Available IDs are: "${availableIds}"`,
);
}
menuItem.click(undefined, undefined, undefined);
},
},
preferences: {
close: () => {
const link = rendered.getByTestId("close-preferences");
fireEvent.click(link);
},
navigate: () => {
const navigateToPreferences = rendererDi.inject(navigateToPreferencesInjectable);
navigateToPreferences();
},
navigation: {
click: (id: string) => {
const link = rendered.queryByTestId(`tab-link-for-${id}`);
if (!link) {
const preferencesNavigationItems = rendererDi.inject(
preferenceNavigationItemsInjectable,
);
const availableIds = preferencesNavigationItems
.get()
.map(get("id"));
throw new Error(
`Tried to click navigation item "${id}" which does not exist in preferences. Available IDs are "${availableIds.join(
'", "',
)}"`,
);
}
fireEvent.click(link);
},
},
},
setEnvironmentToClusterFrame: () => {
environment = environments.clusterFrame;
allowedResourcesState = observable.array();
rendererDi.override(allowedResourcesInjectable, () =>
computed(() => new Set([...allowedResourcesState])),
);
rendererDi.override(
directoryForLensLocalStorageInjectable,
() => "/irrelevant",
);
return builder;
},
addExtensions: async (...extensions) => {
const extensionRegistrators = rendererDi.injectMany(
extensionRegistratorInjectionToken,
);
const addAndEnableExtensions = async () => {
const registratorPromises = extensions.flatMap((extension) =>
extensionRegistrators.map((registrator) => registrator(extension, 1)),
);
await Promise.all(registratorPromises);
runInAction(() => {
extensions.forEach((extension) => {
extensionsState.push(extension);
});
});
};
if (rendered) {
await addAndEnableExtensions();
} else {
builder.beforeRender(addAndEnableExtensions);
}
return builder;
},
allowKubeResource: (resourceName) => {
environment.onAllowKubeResource();
runInAction(() => {
allowedResourcesState.push(resourceName);
});
return builder;
},
beforeSetups(callback: (dis: DiContainers) => void) {
beforeSetupsCallbacks.push(callback);
return builder;
},
beforeRender(callback: (dis: DiContainers) => void) {
beforeRenderCallbacks.push(callback);
return builder;
},
async render() {
for (const callback of beforeSetupsCallbacks) {
await callback(dis);
}
await runSetups();
const render = renderFor(rendererDi);
const history = rendererDi.inject(observableHistoryInjectable);
const currentRouteComponent = rendererDi.inject(
currentRouteComponentInjectable,
);
for (const callback of beforeRenderCallbacks) {
await callback(dis);
}
rendered = render(
<Router history={history}>
{environment.renderSidebar()}
<Observer>
{() => {
const Component = currentRouteComponent.get();
if (!Component) {
return null;
}
return <Component />;
}}
</Observer>
</Router>,
);
return rendered;
},
};
return builder;
};
const toFlatChildren =
(parentId: string) =>
({
submenu = [],
...menuItem
}: MenuItemOpts): (MenuItemOpts & { path: string })[] =>
[
{
...menuItem,
path: pipeline([parentId, menuItem.id], compact, join(".")),
},
...submenu.flatMap(toFlatChildren(menuItem.id)),
];