mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Move unit tests for tray menu items originating from extensions under behaviours
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
2a5b4af344
commit
e051155890
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { LensMainExtension } from "../../extensions/lens-main-extension";
|
||||
import type { TrayMenuRegistration } from "../../main/tray/tray-menu-registration";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
|
||||
describe("clicking tray menu item originating from extension", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let logErrorMock: jest.Mock;
|
||||
|
||||
beforeEach(async () => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
|
||||
logErrorMock = jest.fn();
|
||||
|
||||
mainDi.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
|
||||
});
|
||||
|
||||
await applicationBuilder.render();
|
||||
});
|
||||
|
||||
describe("when extension is enabled", () => {
|
||||
let someExtension: SomeTestExtension;
|
||||
let clickMock: jest.Mock;
|
||||
|
||||
beforeEach(async () => {
|
||||
clickMock = jest.fn();
|
||||
|
||||
someExtension = new SomeTestExtension({
|
||||
id: "some-extension-id",
|
||||
trayMenus: [{ label: "some-label", click: clickMock }],
|
||||
});
|
||||
|
||||
await applicationBuilder.addMainExtensions(someExtension);
|
||||
});
|
||||
|
||||
it("when item is clicked, triggers the click handler", () => {
|
||||
applicationBuilder.tray.click(
|
||||
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1",
|
||||
);
|
||||
|
||||
expect(clickMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("given click handler throws synchronously, when item is clicked", () => {
|
||||
beforeEach(() => {
|
||||
clickMock.mockImplementation(() => {
|
||||
throw new Error("some-error");
|
||||
});
|
||||
|
||||
applicationBuilder.tray.click(
|
||||
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("logs the error", () => {
|
||||
expect(logErrorMock).toHaveBeenCalledWith(
|
||||
'[TRAY]: Clicking of tray item "some-label" from extension "some-extension-id" failed.',
|
||||
expect.any(Error),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given click handler rejects asynchronously, when item is clicked", () => {
|
||||
beforeEach(() => {
|
||||
clickMock.mockImplementation(() => Promise.reject("some-rejection"));
|
||||
|
||||
applicationBuilder.tray.click(
|
||||
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("logs the error", () => {
|
||||
expect(logErrorMock).toHaveBeenCalledWith(
|
||||
'[TRAY]: Clicking of tray item "some-label" from extension "some-extension-id" failed.',
|
||||
"some-rejection",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("when disabling extension, does not have tray menu items", () => {
|
||||
applicationBuilder.removeMainExtensions(someExtension);
|
||||
|
||||
expect(
|
||||
applicationBuilder.tray.get(
|
||||
"some-label-tray-menu-item-for-extension-some-extension-id-instance-1",
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class SomeTestExtension extends LensMainExtension {
|
||||
constructor({ id, trayMenus }: {
|
||||
id: string;
|
||||
trayMenus: TrayMenuRegistration[];
|
||||
}) {
|
||||
super({
|
||||
id,
|
||||
absolutePath: "irrelevant",
|
||||
isBundled: false,
|
||||
isCompatible: false,
|
||||
isEnabled: false,
|
||||
manifest: { name: id, version: "some-version", engines: { lens: "^5.5.0" }},
|
||||
manifestPath: "irrelevant",
|
||||
});
|
||||
|
||||
this.trayMenus = trayMenus;
|
||||
}
|
||||
}
|
||||
@ -55,20 +55,27 @@ const toItemInjectablesFor = (extension: LensMainExtension, installationCounter:
|
||||
label: computed(() => registration.label || ""),
|
||||
tooltip: registration.toolTip,
|
||||
|
||||
click: pipeline(
|
||||
() => {
|
||||
registration.click?.(registration);
|
||||
},
|
||||
click: () => {
|
||||
const decorated = pipeline(
|
||||
registration.click || (() => {}),
|
||||
|
||||
withErrorLoggingFor(() => `[TRAY]: Clicking of tray item "${trayItemId}" from extension "${extension.sanitizedExtensionId}" failed.`),
|
||||
withErrorLoggingFor(
|
||||
() =>
|
||||
`[TRAY]: Clicking of tray item "${trayItemId}" from extension "${extension.sanitizedExtensionId}" failed.`,
|
||||
),
|
||||
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
(x) => withErrorSuppression(x),
|
||||
),
|
||||
// TODO: Find out how to improve typing so that instead of
|
||||
// x => withErrorSuppression(x) there could only be withErrorSuppression
|
||||
x => withErrorSuppression(x),
|
||||
);
|
||||
|
||||
return decorated(registration);
|
||||
},
|
||||
|
||||
enabled: computed(() => !!registration.enabled),
|
||||
visible: computed(() => true),
|
||||
|
||||
extension,
|
||||
}),
|
||||
|
||||
injectionToken: trayMenuItemInjectionToken,
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
|
||||
|
||||
const trayItemsInjectable = getInjectable({
|
||||
id: "tray-items",
|
||||
|
||||
instantiate: (di) => {
|
||||
const extensions = di.inject(mainExtensionsInjectable);
|
||||
|
||||
return computed(() => extensions.get().flatMap(extension => extension.trayMenus));
|
||||
},
|
||||
});
|
||||
|
||||
export default trayItemsInjectable;
|
||||
@ -1,120 +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 { LensMainExtension } from "../../extensions/lens-main-extension";
|
||||
import trayItemsInjectable from "./tray-menu-items.injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed, ObservableMap, runInAction } from "mobx";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
|
||||
import type { TrayMenuRegistration } from "./tray-menu-registration";
|
||||
|
||||
describe("tray-menu-items", () => {
|
||||
let di: DiContainer;
|
||||
let trayMenuItems: IComputedValue<TrayMenuRegistration[]>;
|
||||
let extensionsStub: ObservableMap<string, LensMainExtension>;
|
||||
|
||||
beforeEach(async () => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
extensionsStub = new ObservableMap();
|
||||
|
||||
di.override(
|
||||
mainExtensionsInjectable,
|
||||
() => computed(() => [...extensionsStub.values()]),
|
||||
);
|
||||
|
||||
trayMenuItems = di.inject(trayItemsInjectable);
|
||||
});
|
||||
|
||||
it("does not have any items yet", () => {
|
||||
expect(trayMenuItems.get()).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("when extension is enabled", () => {
|
||||
beforeEach(() => {
|
||||
const someExtension = new SomeTestExtension({
|
||||
id: "some-extension-id",
|
||||
trayMenus: [{ label: "tray-menu-from-some-extension" }],
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
extensionsStub.set("some-extension-id", someExtension);
|
||||
});
|
||||
});
|
||||
|
||||
it("has tray menu items", () => {
|
||||
expect(trayMenuItems.get()).toEqual([
|
||||
{
|
||||
label: "tray-menu-from-some-extension",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("when disabling extension, does not have tray menu items", () => {
|
||||
runInAction(() => {
|
||||
extensionsStub.delete("some-extension-id");
|
||||
});
|
||||
|
||||
expect(trayMenuItems.get()).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("when other extension is enabled", () => {
|
||||
beforeEach(() => {
|
||||
const someOtherExtension = new SomeTestExtension({
|
||||
id: "some-extension-id",
|
||||
trayMenus: [{ label: "some-label-from-second-extension" }],
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
extensionsStub.set("some-other-extension-id", someOtherExtension);
|
||||
});
|
||||
});
|
||||
|
||||
it("has tray menu items for both extensions", () => {
|
||||
expect(trayMenuItems.get()).toEqual([
|
||||
{
|
||||
label: "tray-menu-from-some-extension",
|
||||
},
|
||||
|
||||
{
|
||||
label: "some-label-from-second-extension",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("when extension is disabled, still returns tray menu items for extensions that are enabled", () => {
|
||||
runInAction(() => {
|
||||
extensionsStub.delete("some-other-extension-id");
|
||||
});
|
||||
|
||||
expect(trayMenuItems.get()).toEqual([
|
||||
{
|
||||
label: "tray-menu-from-some-extension",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class SomeTestExtension extends LensMainExtension {
|
||||
constructor({ id, trayMenus }: {
|
||||
id: string;
|
||||
trayMenus: TrayMenuRegistration[];
|
||||
}) {
|
||||
super({
|
||||
id,
|
||||
absolutePath: "irrelevant",
|
||||
isBundled: false,
|
||||
isCompatible: false,
|
||||
isEnabled: false,
|
||||
manifest: { name: id, version: "some-version", engines: { lens: "^5.5.0" }},
|
||||
manifestPath: "irrelevant",
|
||||
});
|
||||
|
||||
this.trayMenus = trayMenus;
|
||||
}
|
||||
}
|
||||
@ -56,6 +56,8 @@ import { openMenu } from "react-select-event";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { StatusBar } from "../status-bar/status-bar";
|
||||
import lensProxyPortInjectable from "../../../main/lens-proxy/lens-proxy-port.injectable";
|
||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
import trayMenuItemsInjectable from "../../../main/tray/tray-menu-item/tray-menu-items.injectable";
|
||||
|
||||
type Callback = (dis: DiContainers) => void | Promise<void>;
|
||||
|
||||
@ -63,6 +65,8 @@ export interface ApplicationBuilder {
|
||||
dis: DiContainers;
|
||||
setEnvironmentToClusterFrame: () => ApplicationBuilder;
|
||||
addExtensions: (...extensions: LensRendererExtension[]) => Promise<ApplicationBuilder>;
|
||||
addMainExtensions: (...extensions: LensMainExtension[]) => Promise<ApplicationBuilder>;
|
||||
removeMainExtensions: (...extensions: LensMainExtension[]) => ApplicationBuilder;
|
||||
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
|
||||
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
|
||||
beforeRender: (callback: Callback) => ApplicationBuilder;
|
||||
@ -136,6 +140,7 @@ export const getApplicationBuilder = () => {
|
||||
const beforeRenderCallbacks: Callback[] = [];
|
||||
|
||||
const extensionsState = observable.array<LensRendererExtension>();
|
||||
const mainExtensionsState = observable.set<LensMainExtension>();
|
||||
|
||||
rendererDi.override(subscribeStoresInjectable, () => () => () => {});
|
||||
|
||||
@ -178,10 +183,9 @@ export const getApplicationBuilder = () => {
|
||||
);
|
||||
|
||||
mainDi.override(mainExtensionsInjectable, () =>
|
||||
computed(() => []),
|
||||
computed(() => [...mainExtensionsState]),
|
||||
);
|
||||
|
||||
let trayMenuItemsStateFake: TrayMenuItem[];
|
||||
let trayMenuIconPath: string;
|
||||
|
||||
mainDi.override(electronTrayInjectable, () => ({
|
||||
@ -191,9 +195,7 @@ export const getApplicationBuilder = () => {
|
||||
trayMenuIconPath = iconPaths.normal;
|
||||
},
|
||||
stop: () => {},
|
||||
setMenuItems: (items) => {
|
||||
trayMenuItemsStateFake = items;
|
||||
},
|
||||
setMenuItems: () => {},
|
||||
setIconPath: (path) => {
|
||||
trayMenuIconPath = path;
|
||||
},
|
||||
@ -242,18 +244,20 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
tray: {
|
||||
get: (id: string) => {
|
||||
return trayMenuItemsStateFake.find(matches({ id })) ?? null;
|
||||
const trayMenuItems = mainDi.inject(trayMenuItemsInjectable).get();
|
||||
|
||||
return trayMenuItems.find(matches({ id })) ?? null;
|
||||
},
|
||||
getIconPath: () => trayMenuIconPath,
|
||||
|
||||
click: async (id: string) => {
|
||||
const menuItem = pipeline(
|
||||
trayMenuItemsStateFake,
|
||||
find((menuItem) => menuItem.id === id),
|
||||
);
|
||||
const trayMenuItems = mainDi.inject(trayMenuItemsInjectable).get();
|
||||
|
||||
const menuItem = trayMenuItems.find(matches({ id })) ?? null;
|
||||
|
||||
if (!menuItem) {
|
||||
const availableIds = pipeline(
|
||||
trayMenuItemsStateFake,
|
||||
trayMenuItems,
|
||||
filter(item => !!item.click),
|
||||
map(item => item.id),
|
||||
join(", "),
|
||||
@ -373,6 +377,44 @@ export const getApplicationBuilder = () => {
|
||||
return builder;
|
||||
},
|
||||
|
||||
addMainExtensions: async (...extensions) => {
|
||||
const extensionRegistrators = mainDi.injectMany(
|
||||
extensionRegistratorInjectionToken,
|
||||
);
|
||||
|
||||
const addAndEnableExtensions = async () => {
|
||||
const registratorPromises = extensions.flatMap((extension) =>
|
||||
extensionRegistrators.map((registrator) => registrator(extension, 1)),
|
||||
);
|
||||
|
||||
await Promise.all(registratorPromises);
|
||||
|
||||
runInAction(() => {
|
||||
extensions.forEach((extension) => {
|
||||
mainExtensionsState.add(extension);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (rendered) {
|
||||
await addAndEnableExtensions();
|
||||
} else {
|
||||
builder.beforeRender(addAndEnableExtensions);
|
||||
}
|
||||
|
||||
return builder;
|
||||
},
|
||||
|
||||
removeMainExtensions: (...extensions) => {
|
||||
extensions.forEach(extension => {
|
||||
runInAction(() => {
|
||||
mainExtensionsState.delete(extension);
|
||||
});
|
||||
});
|
||||
|
||||
return builder;
|
||||
},
|
||||
|
||||
allowKubeResource: (resourceName) => {
|
||||
environment.onAllowKubeResource();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user