import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import type * as Electron from "electron"; import * as ElectronApp from "../electron/ElectronApp.ts"; import * as ElectronDialog from "../electron/ElectronDialog.ts"; import * as ElectronMenu from "../electron/ElectronMenu.ts"; import * as DesktopApplicationMenu from "./DesktopApplicationMenu.ts"; import * as DesktopConfig from "../app/DesktopConfig.ts"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; import * as DesktopUpdates from "../updates/DesktopUpdates.ts"; import * as DesktopWindow from "./DesktopWindow.ts"; const environmentInput = { dirname: "/repo/apps/desktop/dist-electron", homeDirectory: "/Users/alice", platform: "linux", processArch: "arm64", appVersion: "1.2.3", appPath: "/repo", isPackaged: false, resourcesPath: "/repo/resources", runningUnderArm64Translation: false, } satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, { metadata: Effect.die("unexpected metadata read"), name: Effect.succeed("T3 Code"), whenReady: Effect.void, quit: Effect.void, exit: () => Effect.void, relaunch: () => Effect.void, setPath: () => Effect.void, setName: () => Effect.void, setAboutPanelOptions: () => Effect.void, setAppUserModelId: () => Effect.void, requestSingleInstanceLock: Effect.succeed(true), isDefaultProtocolClient: () => Effect.succeed(false), setAsDefaultProtocolClient: () => Effect.succeed(true), setDesktopName: () => Effect.void, setDockIcon: () => Effect.void, appendCommandLineSwitch: () => Effect.void, on: () => Effect.void, } satisfies ElectronApp.ElectronApp["Service"]); const electronDialogLayer = Layer.succeed(ElectronDialog.ElectronDialog, { pickFolder: () => Effect.succeed(Option.none()), confirm: () => Effect.succeed(false), showMessageBox: () => Effect.succeed({ response: 0, checkboxChecked: false }), showErrorBox: () => Effect.void, } satisfies ElectronDialog.ElectronDialog["Service"]); const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, { getState: Effect.die("unexpected getState"), emitState: Effect.void, disabledReason: Effect.succeed(Option.none()), configure: Effect.void, setChannel: () => Effect.die("unexpected setChannel"), check: () => Effect.die("unexpected check"), download: Effect.die("unexpected download"), install: Effect.die("unexpected install"), } satisfies DesktopUpdates.DesktopUpdates["Service"]); const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred) => Layer.succeed(DesktopWindow.DesktopWindow, { createMain: Effect.die("unexpected createMain"), ensureMain: Effect.die("unexpected ensureMain"), revealOrCreateMain: Effect.die("unexpected revealOrCreateMain"), activate: Effect.void, createMainIfBackendReady: Effect.void, showConnectingSplash: Effect.void, handleBackendReady: () => Effect.void, handleBackendNotReady: Effect.void, flushMainWindowBounds: Effect.void, dispatchMenuAction: (action) => Deferred.succeed(selectedAction, action).pipe(Effect.asVoid), syncAppearance: Effect.void, } satisfies DesktopWindow.DesktopWindow["Service"]); const makeElectronMenuLayer = ( applicationMenuTemplate: Deferred.Deferred, ) => Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: (template) => Deferred.succeed(applicationMenuTemplate, template).pipe(Effect.asVoid), popupTemplate: () => Effect.void, showContextMenu: () => Effect.succeed(Option.none()), } satisfies ElectronMenu.ElectronMenu["Service"]); describe("DesktopApplicationMenu", () => { it.effect("installs the native menu and routes Settings through DesktopWindow", () => Effect.gen(function* () { const selectedAction = yield* Deferred.make(); const applicationMenuTemplate = yield* Deferred.make(); yield* Effect.gen(function* () { const menu = yield* DesktopApplicationMenu.DesktopApplicationMenu; yield* menu.configure; }).pipe( Effect.provide( DesktopApplicationMenu.layer.pipe( Layer.provideMerge(makeElectronMenuLayer(applicationMenuTemplate)), Layer.provideMerge(makeDesktopWindowLayer(selectedAction)), Layer.provideMerge(desktopUpdatesLayer), Layer.provideMerge(electronDialogLayer), Layer.provideMerge(electronAppLayer), Layer.provideMerge( DesktopEnvironment.layer(environmentInput).pipe( Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))), ), ), ), ), ); const template = yield* Deferred.await(applicationMenuTemplate); const fileMenu = template.find((item) => item.label === "File"); assert.isDefined(fileMenu); if (!Array.isArray(fileMenu.submenu)) { throw new Error("Expected File menu submenu to be an array."); } const settingsItem = fileMenu.submenu.find((item) => item.label === "Settings..."); assert.isDefined(settingsItem); const settingsClick = settingsItem.click; if (typeof settingsClick !== "function") { throw new Error("Expected Settings menu item to have a click handler."); } settingsClick({} as Electron.MenuItem, {} as Electron.BrowserWindow, {} as KeyboardEvent); assert.equal(yield* Deferred.await(selectedAction), "open-settings"); }), ); });