1
0
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:
Janne Savolainen 2022-06-13 15:16:33 +03:00
parent 2a5b4af344
commit e051155890
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
5 changed files with 185 additions and 159 deletions

View File

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

View File

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

View File

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

View File

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

View File

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