import { assert, describe, it } from "@effect/vitest"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { beforeEach, vi } from "vite-plus/test"; const { appendSwitchMock, getSwitchValueMock, hasSwitchMock, registerSchemesMock, setDesktopNameMock, mkdirSyncMock, writeFileSyncMock, } = vi.hoisted(() => ({ appendSwitchMock: vi.fn(), getSwitchValueMock: vi.fn(), hasSwitchMock: vi.fn(), registerSchemesMock: vi.fn(), setDesktopNameMock: vi.fn(), mkdirSyncMock: vi.fn(), writeFileSyncMock: vi.fn(), })); vi.mock("electron", () => ({ app: { setDesktopName: setDesktopNameMock, getVersion: () => "0.0.37", commandLine: { appendSwitch: appendSwitchMock, getSwitchValue: getSwitchValueMock, hasSwitch: hasSwitchMock, }, }, protocol: { registerSchemesAsPrivileged: registerSchemesMock, }, })); vi.mock("node:fs", () => ({ readFileSync: () => "{}", mkdirSync: mkdirSyncMock, writeFileSync: writeFileSyncMock, })); import * as DesktopPreReadyPlatform from "./DesktopPreReadyPlatform.ts"; describe("DesktopPreReadyPlatform", () => { beforeEach(() => { appendSwitchMock.mockReset(); getSwitchValueMock.mockReset(); hasSwitchMock.mockReset(); registerSchemesMock.mockReset(); setDesktopNameMock.mockReset(); mkdirSyncMock.mockReset(); writeFileSyncMock.mockReset(); }); it.effect("preserves an explicit Linux password-store switch", () => { hasSwitchMock.mockImplementation((switchName) => switchName === "password-store"); getSwitchValueMock.mockReturnValue(" basic "); return Effect.gen(function* () { const options = yield* DesktopPreReadyPlatform.DesktopPreReadyElectronOptions; assert.equal(options.linuxPasswordStoreCommandLine, "basic"); assert.isFalse(appendSwitchMock.mock.calls.some(([name]) => name === "password-store")); }).pipe( Effect.provide( DesktopPreReadyPlatform.layer.pipe( Layer.provide(Layer.succeed(HostProcessPlatform, "linux")), ), ), ); }); for (const previousEntry of [undefined, 'Exec="/Applications/deleted-previous.AppImage" %U']) { it.effect( `prepares a ${previousEntry ? "stale" : "missing"} Linux desktop entry before startup yields`, () => { vi.stubEnv("VITE_DEV_SERVER_URL", ""); vi.stubEnv("XDG_DATA_HOME", "/xdg"); vi.stubEnv("APPIMAGE", "/Applications/current.AppImage"); getSwitchValueMock.mockReturnValue(""); let desktopName = "t3code.desktop"; let desktopEntry = previousEntry; setDesktopNameMock.mockImplementation((name: string) => { desktopName = name; }); writeFileSyncMock.mockImplementation((path: string, contents: string) => { if (path === "/xdg/applications/com.t3tools.T3Code.desktop") desktopEntry = contents; }); return Effect.scoped( Effect.gen(function* () { const portalIdentity = Promise.resolve().then(() => ({ desktopName, desktopEntry })); yield* Layer.build( DesktopPreReadyPlatform.layer.pipe( Layer.provide(Layer.succeed(HostProcessPlatform, "linux")), ), ); const identity = yield* Effect.promise(() => portalIdentity); assert.equal(identity.desktopName, "com.t3tools.T3Code.desktop"); assert.include(identity.desktopEntry ?? "", 'Exec="/Applications/current.AppImage" %U'); assert.include(identity.desktopEntry ?? "", "Name=T3 Code (Alpha)"); assert.include(identity.desktopEntry ?? "", "MimeType=x-scheme-handler/t3code;"); }), ).pipe(Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs()))); }, ); } it.effect("keeps startup available when the early desktop entry cannot be written", () => { getSwitchValueMock.mockReturnValue(""); mkdirSyncMock.mockImplementation(() => { throw new Error("read-only filesystem"); }); return DesktopPreReadyPlatform.make.pipe( Effect.provideService(HostProcessPlatform, "linux"), Effect.asVoid, ); }); it.effect( "acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer", () => Effect.gen(function* () { class ClerkShaped extends Context.Service()( "@t3tools/desktop/app/DesktopPreReadyPlatform.test/ClerkShaped", ) {} const events: Array = []; registerSchemesMock.mockImplementation(() => { events.push("pre-ready"); }); const preReadyLayer = DesktopPreReadyPlatform.layer.pipe( Layer.provide(Layer.succeed(HostProcessPlatform, "darwin")), ); const clerkShapedLayer = Layer.effect( ClerkShaped, Effect.promise(() => Promise.resolve()).pipe( Effect.map(() => { events.push("clerk"); return { ready: true as const }; }), ), ); const runtimeLayer = clerkShapedLayer.pipe( Layer.flatMap((clerkContext) => Layer.succeedContext(clerkContext)), Layer.provideMerge(preReadyLayer), ); const result = yield* Effect.all({ clerk: ClerkShaped, preReady: DesktopPreReadyPlatform.DesktopPreReadyElectronOptions, }).pipe(Effect.provide(runtimeLayer)); assert.deepEqual(result, { clerk: { ready: true }, preReady: { linux: null, linuxPasswordStoreCommandLine: null, }, }); assert.deepEqual(events, ["pre-ready", "clerk"]); assert.equal(registerSchemesMock.mock.calls.length, 1); assert.equal(appendSwitchMock.mock.calls.length, 0); assert.equal(setDesktopNameMock.mock.calls.length, 0); }), ); });