| 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync } from "node:fs"; |
| 3 | import { fileURLToPath } from "node:url"; |
| 4 | import { test } from "node:test"; |
| 5 | import { APP_USER_MODEL_ID, applyAppUserModelId, registerTaskbarRelaunch, windowsTaskbarDetails } from "./appIdentity.js"; |
| 6 | |
| 7 | function windowsApp() { |
| 8 | const applied: string[] = []; |
| 9 | return { applied, app: { setAppUserModelId: (id: string) => applied.push(id) } }; |
| 10 | } |
| 11 | |
| 12 | test("Desktop has its own identity across Studio generations", () => { |
| 13 | assert.equal(APP_USER_MODEL_ID, "io.reasonix.desktop"); |
| 14 | for (const other of ["Reasonix", "io.reasonix.studio", "dev.reasonix.desktop"]) { |
| 15 | assert.notEqual(APP_USER_MODEL_ID, other); |
| 16 | } |
| 17 | }); |
| 18 | |
| 19 | test("Windows adopts the launcher's shared AppUserModelID before any window exists", () => { |
| 20 | const { applied, app } = windowsApp(); |
| 21 | applyAppUserModelId(app, "win32"); |
| 22 | assert.deepEqual(applied, [APP_USER_MODEL_ID]); |
| 23 | }); |
| 24 | |
| 25 | test("other platforms never touch the Windows identity", () => { |
| 26 | for (const platform of ["darwin", "linux"] as const) { |
| 27 | const { applied, app } = windowsApp(); |
| 28 | applyAppUserModelId(app, platform); |
| 29 | assert.deepEqual(applied, [], `${platform} must not set an AppUserModelID`); |
| 30 | } |
| 31 | }); |
| 32 | |
| 33 | test("main applies the identity at load time rather than after readiness", () => { |
| 34 | const source = readFileSync(fileURLToPath(new URL("./index.ts", import.meta.url)), "utf8"); |
| 35 | const applied = source.indexOf("applyAppUserModelId(app, process.platform)"); |
| 36 | assert.notEqual(applied, -1, "index.ts must apply the AppUserModelID"); |
| 37 | // app.whenReady() creates the window, so a later call would miss the taskbar. |
| 38 | const ready = source.indexOf("app.whenReady()"); |
| 39 | assert.notEqual(ready, -1, "index.ts must still gate startup on app readiness"); |
| 40 | assert.ok(applied < ready, "the AppUserModelID must be applied before app.whenReady()"); |
| 41 | }); |
| 42 | |
| 43 | test("new taskbar pins relaunch the permanent launcher for versioned and flat installs", () => { |
| 44 | for (const executable of [String.raw`C:\Apps\Reasonix test\versions\v1.38.6\app\Reasonix.exe`, String.raw`C:\Apps\Reasonix test\app\Reasonix.exe`]) { |
| 45 | const launcher = String.raw`C:\Apps\Reasonix test\reasonix-launcher.exe`; |
| 46 | assert.deepEqual(windowsTaskbarDetails(executable, (path) => path === launcher), { |
| 47 | appId: APP_USER_MODEL_ID, |
| 48 | appIconPath: launcher, |
| 49 | appIconIndex: 0, |
| 50 | relaunchCommand: `"${launcher}"`, |
| 51 | relaunchDisplayName: "Reasonix", |
| 52 | }); |
| 53 | } |
| 54 | assert.equal(windowsTaskbarDetails(String.raw`C:\Apps\Reasonix\app\Reasonix.exe`, () => false), undefined); |
| 55 | assert.equal(windowsTaskbarDetails(String.raw`C:\Other\electron.exe`, () => true), undefined); |
| 56 | }); |
| 57 | |
| 58 | test("every created Windows window gets relaunch metadata and other platforms do not", () => { |
| 59 | const source = readFileSync(fileURLToPath(new URL("./index.ts", import.meta.url)), "utf8"); |
| 60 | const registered = source.indexOf("registerTaskbarRelaunch(app, process.platform, process.execPath, app.isPackaged)"); |
| 61 | assert.ok(registered >= 0 && registered < source.indexOf("bootstrap(home)")); |
| 62 | type Listener = (event: unknown, window: { setAppDetails(details: object): void }) => void; |
| 63 | const listeners: Listener[] = []; |
| 64 | const target = { on: (event: string, callback: Listener) => { assert.equal(event, "browser-window-created"); listeners.push(callback); } }; |
| 65 | registerTaskbarRelaunch(target, "darwin", "/Applications/Reasonix", true, () => true); |
| 66 | registerTaskbarRelaunch(target, "win32", String.raw`C:\Apps\Reasonix\app\Reasonix.exe`, false, () => true); |
| 67 | assert.equal(listeners.length, 0); |
| 68 | registerTaskbarRelaunch(target, "win32", String.raw`C:\Apps\Reasonix\app\Reasonix.exe`, true, () => true); |
| 69 | const listener = listeners[0]; |
| 70 | assert.ok(listener); |
| 71 | const applied: object[] = []; |
| 72 | listener({}, { setAppDetails: (details) => applied.push(details) }); |
| 73 | listener({}, { setAppDetails: (details) => applied.push(details) }); |
| 74 | assert.equal(applied.length, 2); |
| 75 | assert.deepEqual(applied[0], applied[1]); |
| 76 | }); |
| 77 | |
| 78 | test("taskbar pins prefer the canonical entry even when the legacy launcher exists", () => { |
| 79 | const shell = String.raw`C:\Apps\Reasonix\versions\v1.38.9\app\Reasonix.exe`; |
| 80 | const canonical = String.raw`C:\Apps\Reasonix\Reasonix.exe`; |
| 81 | assert.equal(windowsTaskbarDetails(shell, () => true)?.relaunchCommand, `"${canonical}"`); |
| 82 | assert.equal(windowsTaskbarDetails(shell, path => path === canonical)?.appIconPath, canonical); |
| 83 | }); |
| 84 |