From b7a2bb5385c6f2f3c8ce5c99d36f37065bee841b Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 7 Jun 2022 11:30:47 -0400 Subject: [PATCH] Respond to PR comments - Revert changes to structure of electronTrayInjectable - Add some behavioural tests for the tray icon Signed-off-by: Sebastian Malton --- .../installing-update-using-tray.test.ts.snap | 20 +-- .../installing-update-using-tray.test.ts | 52 ++++++-- .../application-update/update-channels.ts | 1 + .../sync-box/sync-box-injection-token.ts | 17 ++- .../build-from-template.injectable.ts | 17 --- .../electron-tray/electron-tray.injectable.ts | 21 +++- ...rt-to-electron-menu-template.injectable.ts | 63 ---------- .../reactive-tray-menu-items/converters.ts | 40 ++++++ .../reactive-tray-menu-items.injectable.ts | 8 +- .../tray-menu.injectable.ts | 28 ----- src/main/tray/tray-icon-path.injectable.ts | 7 +- .../test-utils/get-application-builder.tsx | 116 ++++-------------- 12 files changed, 155 insertions(+), 235 deletions(-) delete mode 100644 src/main/electron/build-from-template.injectable.ts delete mode 100644 src/main/tray/reactive-tray-menu-items/convert-to-electron-menu-template.injectable.ts create mode 100644 src/main/tray/reactive-tray-menu-items/converters.ts delete mode 100644 src/main/tray/reactive-tray-menu-items/tray-menu.injectable.ts diff --git a/src/behaviours/application-update/__snapshots__/installing-update-using-tray.test.ts.snap b/src/behaviours/application-update/__snapshots__/installing-update-using-tray.test.ts.snap index 32e6cb1cb1..5c5ebac6e4 100644 --- a/src/behaviours/application-update/__snapshots__/installing-update-using-tray.test.ts.snap +++ b/src/behaviours/application-update/__snapshots__/installing-update-using-tray.test.ts.snap @@ -44,7 +44,7 @@ exports[`installing update using tray when started when user checks for updates > { let applicationBuilder: ApplicationBuilder; let checkForPlatformUpdatesMock: AsyncFnMock; let downloadPlatformUpdateMock: AsyncFnMock; let showApplicationWindowMock: jest.Mock; + let trayIconPaths: TrayIconPaths; beforeEach(() => { applicationBuilder = getApplicationBuilder(); @@ -44,6 +47,7 @@ describe("installing update using tray", () => { mainDi.override(electronUpdaterIsActiveInjectable, () => true); mainDi.override(publishIsConfiguredInjectable, () => true); + trayIconPaths = mainDi.inject(trayIconPathsInjectable); }); }); @@ -58,6 +62,10 @@ describe("installing update using tray", () => { expect(rendered.baseElement).toMatchSnapshot(); }); + it("should use the normal tray icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); + }); + it("user cannot install update yet", () => { expect(applicationBuilder.tray.get("install-update")).toBeNull(); }); @@ -73,15 +81,19 @@ describe("installing update using tray", () => { expect(showApplicationWindowMock).not.toHaveBeenCalled(); }); + it("should still use the normal tray icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); + }); + it("user cannot check for updates again", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.enabled, + applicationBuilder.tray.get("check-for-updates")?.enabled.get(), ).toBe(false); }); it("name of tray item for checking updates indicates that checking is happening", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.label, + applicationBuilder.tray.get("check-for-updates")?.label?.get(), ).toBe("Checking for updates..."); }); @@ -106,19 +118,23 @@ describe("installing update using tray", () => { expect(showApplicationWindowMock).toHaveBeenCalled(); }); + it("should still use the normal tray icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); + }); + it("user cannot install update", () => { expect(applicationBuilder.tray.get("install-update")).toBeNull(); }); it("user can check for updates again", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.enabled, + applicationBuilder.tray.get("check-for-updates")?.enabled.get(), ).toBe(true); }); it("name of tray item for checking updates no longer indicates that checking is happening", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.label, + applicationBuilder.tray.get("check-for-updates")?.label?.get(), ).toBe("Check for updates"); }); @@ -141,15 +157,19 @@ describe("installing update using tray", () => { expect(showApplicationWindowMock).toHaveBeenCalled(); }); + it("should use the update available icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.updateAvailable); + }); + it("user cannot check for updates again yet", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.enabled, + applicationBuilder.tray.get("check-for-updates")?.enabled.get(), ).toBe(false); }); it("name of tray item for checking updates indicates that downloading is happening", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.label, + applicationBuilder.tray.get("check-for-updates")?.label?.get(), ).toBe("Downloading update some-version (0%)..."); }); @@ -161,7 +181,7 @@ describe("installing update using tray", () => { progressOfUpdateDownload.set({ percentage: 42.424242 }); expect( - applicationBuilder.tray.get("check-for-updates")?.label, + applicationBuilder.tray.get("check-for-updates")?.label?.get(), ).toBe("Downloading update some-version (42%)..."); }); @@ -184,15 +204,19 @@ describe("installing update using tray", () => { ).toBeNull(); }); + it("should revert to use the normal tray icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.normal); + }); + it("user can check for updates again", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.enabled, + applicationBuilder.tray.get("check-for-updates")?.enabled.get(), ).toBe(true); }); it("name of tray item for checking updates no longer indicates that downloading is happening", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.label, + applicationBuilder.tray.get("check-for-updates")?.label?.get(), ).toBe("Check for updates"); }); @@ -208,19 +232,23 @@ describe("installing update using tray", () => { it("user can install update", () => { expect( - applicationBuilder.tray.get("install-update")?.label, + applicationBuilder.tray.get("install-update")?.label?.get(), ).toBe("Install update some-version"); }); + it("should use the update available icon", () => { + expect(applicationBuilder.tray.getIconPath()).toBe(trayIconPaths.updateAvailable); + }); + it("user can check for updates again", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.enabled, + applicationBuilder.tray.get("check-for-updates")?.enabled.get(), ).toBe(true); }); it("name of tray item for checking updates no longer indicates that downloading is happening", () => { expect( - applicationBuilder.tray.get("check-for-updates")?.label, + applicationBuilder.tray.get("check-for-updates")?.label?.get(), ).toBe("Check for updates"); }); diff --git a/src/common/application-update/update-channels.ts b/src/common/application-update/update-channels.ts index c5f7b4b8c1..dff1e5879e 100644 --- a/src/common/application-update/update-channels.ts +++ b/src/common/application-update/update-channels.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ + export type UpdateChannelId = "alpha" | "beta" | "latest"; const latestChannel: UpdateChannel = { diff --git a/src/common/utils/sync-box/sync-box-injection-token.ts b/src/common/utils/sync-box/sync-box-injection-token.ts index d35c7d5367..76ba0679f3 100644 --- a/src/common/utils/sync-box/sync-box-injection-token.ts +++ b/src/common/utils/sync-box/sync-box-injection-token.ts @@ -4,12 +4,21 @@ */ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; -import type { JsonValue } from "type-fest"; -export interface SyncBox { +type AsJson = T extends string | number | boolean | null + ? T + : T extends Function + ? never + : T extends Array + ? AsJson[] + : T extends object + ? { [K in keyof T]: AsJson } + : never; + +export interface SyncBox { id: string; - value: IComputedValue; - set: (value: TValue) => void; + value: IComputedValue>; + set: (value: AsJson) => void; } export const syncBoxInjectionToken = getInjectionToken>({ diff --git a/src/main/electron/build-from-template.injectable.ts b/src/main/electron/build-from-template.injectable.ts deleted file mode 100644 index da53720862..0000000000 --- a/src/main/electron/build-from-template.injectable.ts +++ /dev/null @@ -1,17 +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 type { MenuItemConstructorOptions } from "electron"; -import { Menu } from "electron"; - -export type BuildMenuFromTemplate = (template: MenuItemConstructorOptions[]) => Menu; - -const buildMenuFromTemplateInjectable = getInjectable({ - id: "build-menu-from-template", - instantiate: (): BuildMenuFromTemplate => (template) => Menu.buildFromTemplate(template), - causesSideEffects: true, // Not really but isn't defined -}); - -export default buildMenuFromTemplateInjectable; diff --git a/src/main/tray/electron-tray/electron-tray.injectable.ts b/src/main/tray/electron-tray/electron-tray.injectable.ts index 9da05793ab..a96104f047 100644 --- a/src/main/tray/electron-tray/electron-tray.injectable.ts +++ b/src/main/tray/electron-tray/electron-tray.injectable.ts @@ -3,20 +3,28 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { Menu } from "electron"; -import { Tray } from "electron"; +import { Menu, Tray } from "electron"; import packageJsonInjectable from "../../../common/vars/package-json.injectable"; import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable"; import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import trayIconPathsInjectable from "../tray-icon-path.injectable"; +import type { TrayMenuItem } from "../tray-menu-item/tray-menu-item-injection-token"; +import { convertToElectronMenuTemplate } from "../reactive-tray-menu-items/converters"; const TRAY_LOG_PREFIX = "[TRAY]"; +export interface ElectronTray { + start(): void; + stop(): void; + setMenuItems(menuItems: TrayMenuItem[]): void; + setIconPath(iconPath: string): void; +} + const electronTrayInjectable = getInjectable({ id: "electron-tray", - instantiate: (di) => { + instantiate: (di): ElectronTray => { const packageJson = di.inject(packageJsonInjectable); const showApplicationWindow = di.inject(showApplicationWindowInjectable); const isWindows = di.inject(isWindowsInjectable); @@ -42,10 +50,13 @@ const electronTrayInjectable = getInjectable({ stop: () => { tray.destroy(); }, - setMenu: (menu: Menu) => { + setMenuItems: (menuItems) => { + const template = convertToElectronMenuTemplate(menuItems); + const menu = Menu.buildFromTemplate(template); + tray.setContextMenu(menu); }, - setIconPath: (iconPath: string) => { + setIconPath: (iconPath) => { tray.setImage(iconPath); }, }; diff --git a/src/main/tray/reactive-tray-menu-items/convert-to-electron-menu-template.injectable.ts b/src/main/tray/reactive-tray-menu-items/convert-to-electron-menu-template.injectable.ts deleted file mode 100644 index 87407d6859..0000000000 --- a/src/main/tray/reactive-tray-menu-items/convert-to-electron-menu-template.injectable.ts +++ /dev/null @@ -1,63 +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 loggerInjectable from "../../../common/logger.injectable"; -import type { TrayMenuItem } from "../tray-menu-item/tray-menu-item-injection-token"; - -const convertToElectronMenuTemplateInjectable = getInjectable({ - id: "convert-to-electron-menu-template", - instantiate: (di) => { - const logger = di.inject(loggerInjectable); - - return (trayMenuItems: TrayMenuItem[]) => { - const toTrayMenuOptions = (parentId: string | null) => ( - trayMenuItems - .filter((item) => item.parentId === parentId) - .map((trayMenuItem: TrayMenuItem): Electron.MenuItemConstructorOptions => { - if (trayMenuItem.separator) { - return { id: trayMenuItem.id, type: "separator" }; - } - - const childItems = toTrayMenuOptions(trayMenuItem.id); - - return { - id: trayMenuItem.id, - label: trayMenuItem.label?.get(), - enabled: trayMenuItem.enabled.get(), - toolTip: trayMenuItem.tooltip, - - ...(childItems.length === 0 - ? { - type: "normal", - submenu: toTrayMenuOptions(trayMenuItem.id), - - click: () => { - (async () => { - try { - await trayMenuItem.click?.(); - } catch (error) { - logger.error( - `[TRAY]: clicking item "${trayMenuItem.id} failed."`, - { error }, - ); - } - })(); - }, - } - : { - type: "submenu", - submenu: toTrayMenuOptions(trayMenuItem.id), - }), - - }; - }) - ); - - return toTrayMenuOptions(null); - }; - }, -}); - -export default convertToElectronMenuTemplateInjectable; diff --git a/src/main/tray/reactive-tray-menu-items/converters.ts b/src/main/tray/reactive-tray-menu-items/converters.ts new file mode 100644 index 0000000000..42add7481e --- /dev/null +++ b/src/main/tray/reactive-tray-menu-items/converters.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { TrayMenuItem } from "../tray-menu-item/tray-menu-item-injection-token"; + +export function convertToElectronMenuTemplate(trayMenuItems: TrayMenuItem[]): Electron.MenuItemConstructorOptions[] { + const toTrayMenuOptions = (parentId: string | null) => ( + trayMenuItems + .filter((item) => item.parentId === parentId) + .map((trayMenuItem: TrayMenuItem): Electron.MenuItemConstructorOptions => { + if (trayMenuItem.separator) { + return { id: trayMenuItem.id, type: "separator" }; + } + + const childItems = toTrayMenuOptions(trayMenuItem.id); + + return { + id: trayMenuItem.id, + label: trayMenuItem.label?.get(), + enabled: trayMenuItem.enabled.get(), + toolTip: trayMenuItem.tooltip, + + ...(childItems.length === 0 + ? { + type: "normal", + submenu: toTrayMenuOptions(trayMenuItem.id), + click: trayMenuItem.click, + } + : { + type: "submenu", + submenu: toTrayMenuOptions(trayMenuItem.id), + }), + + }; + }) + ); + + return toTrayMenuOptions(null); +} diff --git a/src/main/tray/reactive-tray-menu-items/reactive-tray-menu-items.injectable.ts b/src/main/tray/reactive-tray-menu-items/reactive-tray-menu-items.injectable.ts index ca705d23b4..22c3d29399 100644 --- a/src/main/tray/reactive-tray-menu-items/reactive-tray-menu-items.injectable.ts +++ b/src/main/tray/reactive-tray-menu-items/reactive-tray-menu-items.injectable.ts @@ -6,19 +6,19 @@ import { getInjectable } from "@ogre-tools/injectable"; import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable"; import { reaction } from "mobx"; import electronTrayInjectable from "../electron-tray/electron-tray.injectable"; -import trayMenuInjectable from "./tray-menu.injectable"; +import trayMenuItemsInjectable from "../tray-menu-item/tray-menu-items.injectable"; const reactiveTrayMenuItemsInjectable = getInjectable({ id: "reactive-tray-menu-items", instantiate: (di) => { const electronTray = di.inject(electronTrayInjectable); - const trayMenu = di.inject(trayMenuInjectable); + const trayMenuItems = di.inject(trayMenuItemsInjectable); return getStartableStoppable("reactive-tray-menu-items", () => ( reaction( - () => trayMenu.get(), - electronTray.setMenu, + () => trayMenuItems.get(), + electronTray.setMenuItems, { fireImmediately: true, }, diff --git a/src/main/tray/reactive-tray-menu-items/tray-menu.injectable.ts b/src/main/tray/reactive-tray-menu-items/tray-menu.injectable.ts deleted file mode 100644 index ddfbdd9357..0000000000 --- a/src/main/tray/reactive-tray-menu-items/tray-menu.injectable.ts +++ /dev/null @@ -1,28 +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 trayMenuItemsInjectable from "../tray-menu-item/tray-menu-items.injectable"; -import buildMenuFromTemplateInjectable from "../../electron/build-from-template.injectable"; -import convertToElectronMenuTemplateInjectable from "./convert-to-electron-menu-template.injectable"; - -const trayMenuInjectable = getInjectable({ - id: "tray-menu", - instantiate: (di) => { - const trayMenuItems = di.inject(trayMenuItemsInjectable); - const convertToElectronMenuTemplate = di.inject(convertToElectronMenuTemplateInjectable); - const buildMenuFromTemplate = di.inject(buildMenuFromTemplateInjectable); - - return computed(() => ( - buildMenuFromTemplate( - convertToElectronMenuTemplate( - trayMenuItems.get(), - ), - ) - )); - }, -}); - -export default trayMenuInjectable; diff --git a/src/main/tray/tray-icon-path.injectable.ts b/src/main/tray/tray-icon-path.injectable.ts index 34052124cb..df83a2e31c 100644 --- a/src/main/tray/tray-icon-path.injectable.ts +++ b/src/main/tray/tray-icon-path.injectable.ts @@ -8,10 +8,15 @@ import staticFilesDirectoryInjectable from "../../common/vars/static-files-direc import isDevelopmentInjectable from "../../common/vars/is-development.injectable"; import isMacInjectable from "../../common/vars/is-mac.injectable"; +export interface TrayIconPaths { + normal: string; + updateAvailable: string; +} + const trayIconPathsInjectable = getInjectable({ id: "tray-icon-paths", - instantiate: (di) => { + instantiate: (di): TrayIconPaths => { const getAbsolutePath = di.inject(getAbsolutePathInjectable); const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable); const isDevelopment = di.inject(isDevelopmentInjectable); diff --git a/src/renderer/components/test-utils/get-application-builder.tsx b/src/renderer/components/test-utils/get-application-builder.tsx index 87184c37fb..28201a079a 100644 --- a/src/renderer/components/test-utils/get-application-builder.tsx +++ b/src/renderer/components/test-utils/get-application-builder.tsx @@ -24,12 +24,12 @@ import type { ClusterStore } from "../../../common/cluster-store/cluster-store"; import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable"; import currentRouteComponentInjectable from "../../routes/current-route-component.injectable"; import { pipeline } from "@ogre-tools/fp"; -import { flatMap, compact, join, get, filter, map } from "lodash/fp"; +import { flatMap, compact, join, get, filter, map, matches, find } 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, Menu } from "electron"; +import type { MenuItemConstructorOptions, MenuItem } from "electron"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable"; @@ -50,7 +50,8 @@ import broadcastThatRootFrameIsRenderedInjectable from "../../frames/root-frame/ import { getDiForUnitTesting as getRendererDi } from "../../getDiForUnitTesting"; import { getDiForUnitTesting as getMainDi } from "../../../main/getDiForUnitTesting"; import { overrideChannels } from "../../../test-utils/channel-fakes/override-channels"; -import buildMenuFromTemplateInjectable from "../../../main/electron/build-from-template.injectable"; +import type { TrayMenuItem } from "../../../main/tray/tray-menu-item/tray-menu-item-injection-token"; +import trayIconPathsInjectable from "../../../main/tray/tray-icon-path.injectable"; type Callback = (dis: DiContainers) => void | Promise; @@ -65,7 +66,8 @@ export interface ApplicationBuilder { tray: { click: (id: string) => Promise; - get: (id: string) => Electron.MenuItem | null; + get: (id: string) => TrayMenuItem | null; + getIconPath: () => string; }; applicationMenu: { @@ -96,10 +98,6 @@ interface Environment { onAllowKubeResource: () => void; } -const getAllSubMenuItems = (item: MenuItem): MenuItem[] => { - return [item, ...(item.submenu?.items ?? []).flatMap(getAllSubMenuItems)]; -}; - export const getApplicationBuilder = () => { const mainDi = getMainDi({ doGeneralOverrides: true, @@ -170,84 +168,22 @@ export const getApplicationBuilder = () => { computed(() => []), ); - let commandId = 0; - const makeFakeMenuItem = (opts: MenuItemConstructorOptions, menu: Menu): MenuItem => { - const menuItemFake: MenuItem = { - accelerator: opts.accelerator, - checked: opts.checked ?? false, - click: () => opts.click?.(menuItemFake, undefined, new KeyboardEvent("fake")), - commandId: commandId += 1, - enabled: opts.enabled ?? false, - icon: opts.icon, - id: opts.id ?? "", - label: opts.label ?? "", - menu, - registerAccelerator: opts.registerAccelerator ?? true, - sharingItem: opts.sharingItem ?? {}, - sublabel: opts.sublabel ?? "", - toolTip: opts.toolTip ?? "", - type: opts.type ?? "normal", - visible: opts.visible ?? true, - role: opts.role, - submenu: opts.submenu === undefined - ? undefined - : Array.isArray(opts.submenu) - ? makeFakeMenu(opts.submenu) - : opts.submenu, - }; + const iconPaths = mainDi.inject(trayIconPathsInjectable); - return menuItemFake; - }; - const makeFakeMenu = (templates: MenuItemConstructorOptions[]): Menu => { - const menuFake: Electron.Menu = { - addListener: () => { - throw new Error("Adding listeners is not supported currently"); - }, - on: () => { - throw new Error("Adding listeners is not supported currently"); - }, - once: () => { - throw new Error("Adding listeners is not supported currently"); - }, - removeListener: () => { - throw new Error("Removing listeners is not supported currently"); - }, - append: () => { - throw new Error("Adding new menu items is not supported currently"); - }, - insert: () => { - throw new Error("Adding new menu items is not supported currently"); - }, - popup: () => { - throw new Error("Popping up menu is not supported currently"); - }, - closePopup: () => { - throw new Error("Popping up menu is not supported currently"); - }, - get items() { - return [...menuItems]; - }, - getMenuItemById: (id) => menuItems - .flatMap(getAllSubMenuItems) - .find(menuItem => menuItem.id === id) - ?? null, - }; - const menuItems = templates.map(template => makeFakeMenuItem(template, menuFake)); - - return menuFake; - }; - - mainDi.override(buildMenuFromTemplateInjectable, () => makeFakeMenu); - - let trayMenuStateFake: Electron.Menu | undefined; + let trayMenuItemsStateFake: TrayMenuItem[]; + let trayMenuIconPath: string; mainDi.override(electronTrayInjectable, () => ({ - start: () => {}, - stop: () => {}, - setMenu: (menu) => { - trayMenuStateFake = menu; + start: () => { + trayMenuIconPath = iconPaths.normal; + }, + stop: () => {}, + setMenuItems: (items) => { + trayMenuItemsStateFake = items; + }, + setIconPath: (path) => { + trayMenuIconPath = path; }, - setIconPath: () => {}, })); let allowedResourcesState: IObservableArray; @@ -294,20 +230,18 @@ export const getApplicationBuilder = () => { tray: { get: (id: string) => { - return trayMenuStateFake?.getMenuItemById(id) ?? null; + return trayMenuItemsStateFake.find(matches({ id })) ?? null; }, - + getIconPath: () => trayMenuIconPath, click: async (id: string) => { - if (!trayMenuStateFake) { - throw new Error(`Tried to click tray menu with ID ${id}, but tray menu has not been set yet`); - } - - const menuItem = trayMenuStateFake.getMenuItemById(id); + const menuItem = pipeline( + trayMenuItemsStateFake, + find((menuItem) => menuItem.id === id), + ); if (!menuItem) { const availableIds = pipeline( - trayMenuStateFake.items, - flatMap(getAllSubMenuItems), + trayMenuItemsStateFake, filter(item => !!item.click), map(item => item.id), join(", "),