import { assert, describe, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Ref from "effect/Ref"; import type * as Electron from "electron"; import * as ElectronApp from "../electron/ElectronApp.ts";import * as ElectronTheme from "../electron/ElectronTheme.ts";import * as DesktopEnvironment from "./DesktopEnvironment.ts";import * as DesktopLifecycle from "./DesktopLifecycle.ts";import * as DesktopShutdown from "./DesktopShutdown.ts";import * as DesktopState from "./DesktopState.ts";import * as DesktopWindow from "../window/DesktopWindow.ts"; describe("DesktopLifecycle", () => { for (const platform of ["darwin", "win32", "linux"] satisfies ReadonlyArray<NodeJS.Platform>) { it.effect(`lets the updater's quit event proceed on ${platform}`, () => { const appListeners = new Map<string, (...args: readonly unknown[]) => void>(); 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, getAppMetrics: Effect.succeed([]), isDefaultProtocolClient: () => Effect.succeed(false), setAsDefaultProtocolClient: () => Effect.succeed(true), setDesktopName: () => Effect.void, setDockIcon: () => Effect.void, appendCommandLineSwitch: () => Effect.void, removeCommandLineSwitch: () => Effect.void, onBeforeQuitForUpdate: (listener) => Effect.acquireRelease( Effect.sync(() => { appListeners.set("before-quit-for-update", listener); }), () => Effect.sync(() => { appListeners.delete("before-quit-for-update"); }), ).pipe(Effect.asVoid), on: (eventName, listener) => Effect.acquireRelease( Effect.sync(() => { appListeners.set( eventName, listener as unknown as (...args: readonly unknown[]) => void, ); }), () => Effect.sync(() => { appListeners.delete(eventName); }), ).pipe(Effect.asVoid), } satisfies ElectronApp.ElectronApp["Service"]); const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { shouldUseDarkColors: Effect.succeed(false), setSource: () => Effect.void, onUpdated: () => Effect.void, }); const desktopWindowLayer = Layer.succeed(DesktopWindow.DesktopWindow, { createMain: Effect.die("unexpected window creation"), ensureMain: Effect.die("unexpected window creation"), revealOrCreateMain: Effect.die("unexpected window creation"), activate: Effect.void, createMainIfBackendReady: Effect.void, showConnectingSplash: Effect.void, handleBackendReady: () => Effect.void, handleBackendNotReady: Effect.void, flushMainWindowBounds: Effect.void, dispatchMenuAction: () => Effect.void, syncAppearance: Effect.void, }); const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { platform, isDevelopment: false, } as DesktopEnvironment.DesktopEnvironment["Service"]); const layer = DesktopLifecycle.layer.pipe( Layer.provideMerge(electronAppLayer), Layer.provideMerge(electronThemeLayer), Layer.provideMerge(desktopWindowLayer), Layer.provideMerge(environmentLayer), Layer.provideMerge(DesktopShutdown.layer), Layer.provideMerge(DesktopState.layer), ); return Effect.scoped( Effect.gen(function* () { const lifecycle = yield* DesktopLifecycle.DesktopLifecycle; yield* lifecycle.register; appListeners.get("before-quit-for-update")?.(); let prevented = false; const event = { preventDefault: () => { prevented = true; }, } as Electron.Event; appListeners.get("before-quit")?.(event); assert.isFalse( prevented, "cancelling this event prevents the updater from completing its relaunch", ); const state = yield* DesktopState.DesktopState; assert.isTrue(yield* Ref.get(state.quitting)); }), ).pipe(Effect.provide(layer)); }); }});
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Ref from "effect/Ref";
import type * as Electron from "electron";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as ElectronTheme from "../electron/ElectronTheme.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";
import * as DesktopLifecycle from "./DesktopLifecycle.ts";
import * as DesktopShutdown from "./DesktopShutdown.ts";
import * as DesktopState from "./DesktopState.ts";
import * as DesktopWindow from "../window/DesktopWindow.ts";
describe("DesktopLifecycle", () => {
for (const platform of ["darwin", "win32", "linux"] satisfies ReadonlyArray<NodeJS.Platform>) {
it.effect(`lets the updater's quit event proceed on ${platform}`, () => {
const appListeners = new Map<string, (...args: readonly unknown[]) => void>();
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,
getAppMetrics: Effect.succeed([]),
isDefaultProtocolClient: () => Effect.succeed(false),
setAsDefaultProtocolClient: () => Effect.succeed(true),
setDesktopName: () => Effect.void,
setDockIcon: () => Effect.void,
appendCommandLineSwitch: () => Effect.void,
removeCommandLineSwitch: () => Effect.void,
onBeforeQuitForUpdate: (listener) =>
Effect.acquireRelease(
Effect.sync(() => {
appListeners.set("before-quit-for-update", listener);
}),
() =>
Effect.sync(() => {
appListeners.delete("before-quit-for-update");
}),
).pipe(Effect.asVoid),
on: (eventName, listener) =>
Effect.acquireRelease(
Effect.sync(() => {
appListeners.set(
eventName,
listener as unknown as (...args: readonly unknown[]) => void,
);
}),
() =>
Effect.sync(() => {
appListeners.delete(eventName);
}),
).pipe(Effect.asVoid),
} satisfies ElectronApp.ElectronApp["Service"]);
const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, {
shouldUseDarkColors: Effect.succeed(false),
setSource: () => Effect.void,
onUpdated: () => Effect.void,
});
const desktopWindowLayer = Layer.succeed(DesktopWindow.DesktopWindow, {
createMain: Effect.die("unexpected window creation"),
ensureMain: Effect.die("unexpected window creation"),
revealOrCreateMain: Effect.die("unexpected window creation"),
activate: Effect.void,
createMainIfBackendReady: Effect.void,
showConnectingSplash: Effect.void,
handleBackendReady: () => Effect.void,
handleBackendNotReady: Effect.void,
flushMainWindowBounds: Effect.void,
dispatchMenuAction: () => Effect.void,
syncAppearance: Effect.void,
});
const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
platform,
isDevelopment: false,
} as DesktopEnvironment.DesktopEnvironment["Service"]);
const layer = DesktopLifecycle.layer.pipe(
Layer.provideMerge(electronAppLayer),
Layer.provideMerge(electronThemeLayer),
Layer.provideMerge(desktopWindowLayer),
Layer.provideMerge(environmentLayer),
Layer.provideMerge(DesktopShutdown.layer),
Layer.provideMerge(DesktopState.layer),
);
return Effect.scoped(
Effect.gen(function* () {
const lifecycle = yield* DesktopLifecycle.DesktopLifecycle;
yield* lifecycle.register;
appListeners.get("before-quit-for-update")?.();
let prevented = false;
const event = {
preventDefault: () => {
prevented = true;
},
} as Electron.Event;
appListeners.get("before-quit")?.(event);
assert.isFalse(
prevented,
"cancelling this event prevents the updater from completing its relaunch",
);
const state = yield* DesktopState.DesktopState;
assert.isTrue(yield* Ref.get(state.quitting));
}),
).pipe(Effect.provide(layer));
});
}
});