@flighthq/host-electron 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/electronApp.d.ts +4 -0
  2. package/dist/electronApp.d.ts.map +1 -0
  3. package/dist/electronApp.js +184 -0
  4. package/dist/electronApp.js.map +1 -0
  5. package/dist/electronClipboard.d.ts +4 -0
  6. package/dist/electronClipboard.d.ts.map +1 -0
  7. package/dist/electronClipboard.js +211 -0
  8. package/dist/electronClipboard.js.map +1 -0
  9. package/dist/electronDialog.d.ts +4 -0
  10. package/dist/electronDialog.d.ts.map +1 -0
  11. package/dist/electronDialog.js +85 -0
  12. package/dist/electronDialog.js.map +1 -0
  13. package/dist/electronIpc.d.ts +4 -0
  14. package/dist/electronIpc.d.ts.map +1 -0
  15. package/dist/electronIpc.js +21 -0
  16. package/dist/electronIpc.js.map +1 -0
  17. package/dist/electronMenu.d.ts +4 -0
  18. package/dist/electronMenu.d.ts.map +1 -0
  19. package/dist/electronMenu.js +59 -0
  20. package/dist/electronMenu.js.map +1 -0
  21. package/dist/electronModule.d.ts +368 -0
  22. package/dist/electronModule.d.ts.map +1 -0
  23. package/dist/electronModule.js +12 -0
  24. package/dist/electronModule.js.map +1 -0
  25. package/dist/electronNotification.d.ts +4 -0
  26. package/dist/electronNotification.d.ts.map +1 -0
  27. package/dist/electronNotification.js +134 -0
  28. package/dist/electronNotification.js.map +1 -0
  29. package/dist/electronPlatform.d.ts +4 -0
  30. package/dist/electronPlatform.d.ts.map +1 -0
  31. package/dist/electronPlatform.js +30 -0
  32. package/dist/electronPlatform.js.map +1 -0
  33. package/dist/electronPower.d.ts +4 -0
  34. package/dist/electronPower.d.ts.map +1 -0
  35. package/dist/electronPower.js +92 -0
  36. package/dist/electronPower.js.map +1 -0
  37. package/dist/electronProtocol.d.ts +4 -0
  38. package/dist/electronProtocol.d.ts.map +1 -0
  39. package/dist/electronProtocol.js +55 -0
  40. package/dist/electronProtocol.js.map +1 -0
  41. package/dist/electronRegister.d.ts +6 -0
  42. package/dist/electronRegister.d.ts.map +1 -0
  43. package/dist/electronRegister.js +62 -0
  44. package/dist/electronRegister.js.map +1 -0
  45. package/dist/electronScreen.d.ts +4 -0
  46. package/dist/electronScreen.d.ts.map +1 -0
  47. package/dist/electronScreen.js +68 -0
  48. package/dist/electronScreen.js.map +1 -0
  49. package/dist/electronShell.d.ts +4 -0
  50. package/dist/electronShell.d.ts.map +1 -0
  51. package/dist/electronShell.js +105 -0
  52. package/dist/electronShell.js.map +1 -0
  53. package/dist/electronShortcut.d.ts +4 -0
  54. package/dist/electronShortcut.d.ts.map +1 -0
  55. package/dist/electronShortcut.js +40 -0
  56. package/dist/electronShortcut.js.map +1 -0
  57. package/dist/electronStorage.d.ts +4 -0
  58. package/dist/electronStorage.d.ts.map +1 -0
  59. package/dist/electronStorage.js +75 -0
  60. package/dist/electronStorage.js.map +1 -0
  61. package/dist/electronTray.d.ts +4 -0
  62. package/dist/electronTray.d.ts.map +1 -0
  63. package/dist/electronTray.js +164 -0
  64. package/dist/electronTray.js.map +1 -0
  65. package/dist/electronUpdater.d.ts +4 -0
  66. package/dist/electronUpdater.d.ts.map +1 -0
  67. package/dist/electronUpdater.js +113 -0
  68. package/dist/electronUpdater.js.map +1 -0
  69. package/dist/electronWindow.d.ts +7 -0
  70. package/dist/electronWindow.d.ts.map +1 -0
  71. package/dist/electronWindow.js +417 -0
  72. package/dist/electronWindow.js.map +1 -0
  73. package/dist/index.d.ts +19 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +19 -0
  76. package/dist/index.js.map +1 -0
  77. package/package.json +54 -0
  78. package/src/electronApp.test.ts +141 -0
  79. package/src/electronClipboard.test.ts +86 -0
  80. package/src/electronDialog.test.ts +95 -0
  81. package/src/electronIpc.test.ts +47 -0
  82. package/src/electronMenu.test.ts +85 -0
  83. package/src/electronNotification.test.ts +97 -0
  84. package/src/electronPlatform.test.ts +28 -0
  85. package/src/electronPower.test.ts +110 -0
  86. package/src/electronProtocol.test.ts +63 -0
  87. package/src/electronRegister.test.ts +57 -0
  88. package/src/electronScreen.test.ts +92 -0
  89. package/src/electronShell.test.ts +67 -0
  90. package/src/electronShortcut.test.ts +56 -0
  91. package/src/electronStorage.test.ts +70 -0
  92. package/src/electronTray.test.ts +107 -0
  93. package/src/electronUpdater.test.ts +117 -0
  94. package/src/electronWindow.test.ts +217 -0
@@ -0,0 +1,92 @@
1
+ import type { ScreenInfo } from '@flighthq/types';
2
+
3
+ import type { ElectronApi, ElectronDisplay } from './electronModule';
4
+ import { createElectronScreenBackend } from './electronScreen';
5
+
6
+ function display(id: number, x: number): ElectronDisplay {
7
+ return {
8
+ id,
9
+ bounds: { x, y: 0, width: 1920, height: 1080 },
10
+ workArea: { x, y: 0, width: 1920, height: 1040 },
11
+ scaleFactor: 2,
12
+ };
13
+ }
14
+
15
+ function fakeElectron(): {
16
+ electron: ElectronApi;
17
+ listeners: Map<string, (() => void)[]>;
18
+ } {
19
+ const listeners = new Map<string, (() => void)[]>();
20
+ const primary = display(1, 0);
21
+ const secondary = display(2, 1920);
22
+ const electron = {
23
+ screen: {
24
+ getPrimaryDisplay: () => primary,
25
+ getAllDisplays: () => [primary, secondary],
26
+ on: (event: string, listener: () => void) => {
27
+ const list = listeners.get(event) ?? [];
28
+ list.push(listener);
29
+ listeners.set(event, list);
30
+ },
31
+ removeListener: (event: string, listener: () => void) => {
32
+ const list = listeners.get(event) ?? [];
33
+ listeners.set(
34
+ event,
35
+ list.filter((l) => l !== listener),
36
+ );
37
+ },
38
+ },
39
+ } as unknown as ElectronApi;
40
+ return { electron, listeners };
41
+ }
42
+
43
+ describe('createElectronScreenBackend', () => {
44
+ it('fills the primary screen into out', () => {
45
+ const { electron } = fakeElectron();
46
+ const backend = createElectronScreenBackend(electron);
47
+ const out = {} as ScreenInfo;
48
+ const result = backend.getPrimaryScreen(out);
49
+ expect(result).toBe(out);
50
+ expect(out).toEqual({
51
+ id: 1,
52
+ x: 0,
53
+ y: 0,
54
+ width: 1920,
55
+ height: 1080,
56
+ workWidth: 1920,
57
+ workHeight: 1040,
58
+ scaleFactor: 2,
59
+ isPrimary: true,
60
+ });
61
+ });
62
+
63
+ it('enumerates all screens marking the primary', () => {
64
+ const { electron } = fakeElectron();
65
+ const backend = createElectronScreenBackend(electron);
66
+ const out: ScreenInfo[] = [];
67
+ backend.getScreens(out);
68
+ expect(out).toHaveLength(2);
69
+ expect(out[0].id).toBe(1);
70
+ expect(out[0].isPrimary).toBe(true);
71
+ expect(out[1].id).toBe(2);
72
+ expect(out[1].isPrimary).toBe(false);
73
+ expect(out[1].x).toBe(1920);
74
+ });
75
+
76
+ it('subscribes to all change events and unsubscribes from all', () => {
77
+ const fake = fakeElectron();
78
+ const backend = createElectronScreenBackend(fake.electron);
79
+ let count = 0;
80
+ const off = backend.subscribe(() => {
81
+ count++;
82
+ });
83
+ for (const event of ['display-added', 'display-removed', 'display-metrics-changed']) {
84
+ for (const l of fake.listeners.get(event) ?? []) l();
85
+ }
86
+ expect(count).toBe(3);
87
+ off();
88
+ expect(fake.listeners.get('display-added')).toHaveLength(0);
89
+ expect(fake.listeners.get('display-removed')).toHaveLength(0);
90
+ expect(fake.listeners.get('display-metrics-changed')).toHaveLength(0);
91
+ });
92
+ });
@@ -0,0 +1,67 @@
1
+ import type { ElectronApi } from './electronModule';
2
+ import { createElectronShellBackend } from './electronShell';
3
+
4
+ function fakeElectron(shell: Partial<ElectronApi['shell']>): ElectronApi {
5
+ return { shell } as unknown as ElectronApi;
6
+ }
7
+
8
+ describe('createElectronShellBackend', () => {
9
+ it('openExternal returns true on success and false on throw', async () => {
10
+ const ok = createElectronShellBackend(fakeElectron({ openExternal: async () => {} }));
11
+ expect(await ok.openExternal('https://example.test')).toBe(true);
12
+ const bad = createElectronShellBackend(
13
+ fakeElectron({
14
+ openExternal: async () => {
15
+ throw new Error('no');
16
+ },
17
+ }),
18
+ );
19
+ expect(await bad.openExternal('https://example.test')).toBe(false);
20
+ });
21
+
22
+ it('openPath maps the empty-string success convention to true', async () => {
23
+ const ok = createElectronShellBackend(fakeElectron({ openPath: async () => '' }));
24
+ expect(await ok.openPath('/a')).toBe(true);
25
+ const err = createElectronShellBackend(fakeElectron({ openPath: async () => 'no such file' }));
26
+ expect(await err.openPath('/a')).toBe(false);
27
+ });
28
+
29
+ it('showItemInFolder returns true after revealing the path', async () => {
30
+ let revealed = '';
31
+ const backend = createElectronShellBackend(
32
+ fakeElectron({
33
+ showItemInFolder: (path: string) => {
34
+ revealed = path;
35
+ },
36
+ }),
37
+ );
38
+ expect(await backend.showItemInFolder('/a/b')).toBe(true);
39
+ expect(revealed).toBe('/a/b');
40
+ });
41
+
42
+ it('moveToTrash returns true on success and false on throw', async () => {
43
+ const ok = createElectronShellBackend(fakeElectron({ trashItem: async () => {} }));
44
+ expect(await ok.moveToTrash('/a')).toBe(true);
45
+ const bad = createElectronShellBackend(
46
+ fakeElectron({
47
+ trashItem: async () => {
48
+ throw new Error('no');
49
+ },
50
+ }),
51
+ );
52
+ expect(await bad.moveToTrash('/a')).toBe(false);
53
+ });
54
+
55
+ it('beep delegates to the shell', () => {
56
+ let beeped = false;
57
+ const backend = createElectronShellBackend(
58
+ fakeElectron({
59
+ beep: () => {
60
+ beeped = true;
61
+ },
62
+ }),
63
+ );
64
+ backend.beep();
65
+ expect(beeped).toBe(true);
66
+ });
67
+ });
@@ -0,0 +1,56 @@
1
+ import type { ElectronApi } from './electronModule';
2
+ import { createElectronShortcutBackend } from './electronShortcut';
3
+
4
+ function fakeElectron(): { electron: ElectronApi; registered: Map<string, () => void> } {
5
+ const registered = new Map<string, () => void>();
6
+ const electron = {
7
+ globalShortcut: {
8
+ register: (accelerator: string, callback: () => void) => {
9
+ registered.set(accelerator, callback);
10
+ return true;
11
+ },
12
+ unregister: (accelerator: string) => {
13
+ registered.delete(accelerator);
14
+ },
15
+ unregisterAll: () => {
16
+ registered.clear();
17
+ },
18
+ isRegistered: (accelerator: string) => registered.has(accelerator),
19
+ },
20
+ } as unknown as ElectronApi;
21
+ return { electron, registered };
22
+ }
23
+
24
+ describe('createElectronShortcutBackend', () => {
25
+ it('registers and reports a shortcut, invoking its listener', () => {
26
+ const fake = fakeElectron();
27
+ const backend = createElectronShortcutBackend(fake.electron);
28
+ let fired = 0;
29
+ expect(
30
+ backend.register('CommandOrControl+X', () => {
31
+ fired++;
32
+ }),
33
+ ).toBe(true);
34
+ expect(backend.isRegistered('CommandOrControl+X')).toBe(true);
35
+ fake.registered.get('CommandOrControl+X')?.();
36
+ expect(fired).toBe(1);
37
+ });
38
+
39
+ it('unregisters one shortcut, returning true', () => {
40
+ const fake = fakeElectron();
41
+ const backend = createElectronShortcutBackend(fake.electron);
42
+ backend.register('Ctrl+A', () => {});
43
+ expect(backend.unregister('Ctrl+A')).toBe(true);
44
+ expect(backend.isRegistered('Ctrl+A')).toBe(false);
45
+ });
46
+
47
+ it('unregisters all shortcuts', () => {
48
+ const fake = fakeElectron();
49
+ const backend = createElectronShortcutBackend(fake.electron);
50
+ backend.register('Ctrl+A', () => {});
51
+ backend.register('Ctrl+B', () => {});
52
+ backend.unregisterAll();
53
+ expect(backend.isRegistered('Ctrl+A')).toBe(false);
54
+ expect(backend.isRegistered('Ctrl+B')).toBe(false);
55
+ });
56
+ });
@@ -0,0 +1,70 @@
1
+ import type { ElectronApi } from './electronModule';
2
+ import { createElectronStorageBackend } from './electronStorage';
3
+
4
+ function fakeElectron(initial?: Record<string, string>): {
5
+ electron: ElectronApi;
6
+ written: string[];
7
+ } {
8
+ const written: string[] = [];
9
+ let fileContent: string | null = initial !== undefined ? JSON.stringify(initial) : null;
10
+ const fs = {
11
+ existsSync: () => fileContent !== null,
12
+ readFileSync: () => fileContent ?? '',
13
+ writeFileSync: (_path: string, data: string) => {
14
+ fileContent = data;
15
+ written.push(data);
16
+ },
17
+ };
18
+ const electron = {
19
+ app: { getPath: () => '/userData' },
20
+ fs,
21
+ } as unknown as ElectronApi;
22
+ return { electron, written };
23
+ }
24
+
25
+ describe('createElectronStorageBackend', () => {
26
+ it('clear removes all keys and persists', () => {
27
+ const { electron } = fakeElectron({ a: '1' });
28
+ const backend = createElectronStorageBackend(electron);
29
+ expect(backend.getItem('a')).toBe('1');
30
+ backend.clear();
31
+ expect(backend.getItem('a')).toBeNull();
32
+ expect(backend.keys()).toEqual([]);
33
+ });
34
+
35
+ it('getItem returns null for a missing key', () => {
36
+ const { electron } = fakeElectron({});
37
+ const backend = createElectronStorageBackend(electron);
38
+ expect(backend.getItem('missing')).toBeNull();
39
+ });
40
+
41
+ it('keys returns all stored keys', () => {
42
+ const { electron } = fakeElectron({ x: '1', y: '2' });
43
+ const backend = createElectronStorageBackend(electron);
44
+ expect(backend.keys().sort()).toEqual(['x', 'y']);
45
+ });
46
+
47
+ it('removeItem deletes a key and returns false for an unknown key', () => {
48
+ const { electron } = fakeElectron({ a: 'v' });
49
+ const backend = createElectronStorageBackend(electron);
50
+ expect(backend.removeItem('a')).toBe(true);
51
+ expect(backend.getItem('a')).toBeNull();
52
+ expect(backend.removeItem('a')).toBe(false);
53
+ });
54
+
55
+ it('setItem stores a value and persists it to disk', () => {
56
+ const { electron, written } = fakeElectron();
57
+ const backend = createElectronStorageBackend(electron);
58
+ expect(backend.setItem('k', 'v')).toBe(true);
59
+ expect(backend.getItem('k')).toBe('v');
60
+ expect(written.length).toBeGreaterThan(0);
61
+ expect(JSON.parse(written[written.length - 1])).toEqual({ k: 'v' });
62
+ });
63
+
64
+ it('starts fresh when the file does not exist', () => {
65
+ const { electron } = fakeElectron();
66
+ const backend = createElectronStorageBackend(electron);
67
+ expect(backend.keys()).toEqual([]);
68
+ expect(backend.getItem('any')).toBeNull();
69
+ });
70
+ });
@@ -0,0 +1,107 @@
1
+ import type { TrayEventType } from '@flighthq/types';
2
+
3
+ import type { ElectronApi, ElectronMenu, ElectronMenuItemOptions, ElectronTray } from './electronModule';
4
+ import { createElectronTrayBackend } from './electronTray';
5
+
6
+ interface FakeTray extends ElectronTray {
7
+ icon: string;
8
+ tooltip: string;
9
+ title: string;
10
+ contextMenu: ElectronMenu | null;
11
+ destroyed: boolean;
12
+ handlers: Record<string, () => void>;
13
+ }
14
+
15
+ function fakeElectron(): {
16
+ electron: ElectronApi;
17
+ trays: FakeTray[];
18
+ built: ElectronMenuItemOptions[][];
19
+ } {
20
+ const trays: FakeTray[] = [];
21
+ const built: ElectronMenuItemOptions[][] = [];
22
+ const electron = {
23
+ Tray: function (this: FakeTray, icon: string) {
24
+ this.icon = icon;
25
+ this.tooltip = '';
26
+ this.title = '';
27
+ this.contextMenu = null;
28
+ this.destroyed = false;
29
+ this.handlers = {};
30
+ this.setToolTip = (t: string) => {
31
+ this.tooltip = t;
32
+ };
33
+ this.setTitle = (t: string) => {
34
+ this.title = t;
35
+ };
36
+ this.setContextMenu = (menu: ElectronMenu | null) => {
37
+ this.contextMenu = menu;
38
+ };
39
+ this.on = (event: string, listener: () => void) => {
40
+ this.handlers[event] = listener;
41
+ };
42
+ this.destroy = () => {
43
+ this.destroyed = true;
44
+ };
45
+ trays.push(this);
46
+ },
47
+ Menu: {
48
+ buildFromTemplate: (template: ElectronMenuItemOptions[]) => {
49
+ built.push(template);
50
+ return { template } as unknown as ElectronMenu;
51
+ },
52
+ },
53
+ } as unknown as ElectronApi;
54
+ return { electron, trays, built };
55
+ }
56
+
57
+ describe('createElectronTrayBackend', () => {
58
+ it('creates a tray with icon, tooltip, and title and returns a numeric id', () => {
59
+ const { electron, trays } = fakeElectron();
60
+ const backend = createElectronTrayBackend(electron);
61
+ const id = backend.create({ icon: 'i.png', tooltip: 'hello', title: 'T' });
62
+ expect(typeof id).toBe('number');
63
+ expect(trays[0].icon).toBe('i.png');
64
+ expect(trays[0].tooltip).toBe('hello');
65
+ expect(trays[0].title).toBe('T');
66
+ });
67
+
68
+ it('destroys a tray and ignores unknown ids', () => {
69
+ const { electron, trays } = fakeElectron();
70
+ const backend = createElectronTrayBackend(electron);
71
+ const id = backend.create({});
72
+ backend.destroy(id);
73
+ expect(trays[0].destroyed).toBe(true);
74
+ expect(() => backend.destroy(999)).not.toThrow();
75
+ });
76
+
77
+ it('updates tooltip, title, and context menu', () => {
78
+ const { electron, trays, built } = fakeElectron();
79
+ const backend = createElectronTrayBackend(electron);
80
+ const id = backend.create({});
81
+ backend.setTooltip(id, 'tip');
82
+ backend.setTitle(id, 'name');
83
+ backend.setContextMenu(id, [{ id: 'x', label: 'X' }]);
84
+ expect(trays[0].tooltip).toBe('tip');
85
+ expect(trays[0].title).toBe('name');
86
+ expect(trays[0].contextMenu).not.toBeNull();
87
+ expect(built[built.length - 1][0].id).toBe('x');
88
+ });
89
+
90
+ it('forwards click events to the subscribed listener and stops after unsubscribe', () => {
91
+ const { electron, trays } = fakeElectron();
92
+ const backend = createElectronTrayBackend(electron);
93
+ const id = backend.create({});
94
+ const events: [number, TrayEventType][] = [];
95
+ const unsubscribe = backend.subscribe((event) => events.push([event.id, event.type]));
96
+ trays[0].handlers['click']();
97
+ trays[0].handlers['right-click']();
98
+ trays[0].handlers['double-click']();
99
+ unsubscribe();
100
+ trays[0].handlers['click']();
101
+ expect(events).toEqual([
102
+ [id, 'click'],
103
+ [id, 'rightClick'],
104
+ [id, 'doubleClick'],
105
+ ]);
106
+ });
107
+ });
@@ -0,0 +1,117 @@
1
+ import type { ElectronApi } from './electronModule';
2
+ import { createElectronUpdaterBackend } from './electronUpdater';
3
+
4
+ function fakeElectron(): {
5
+ electron: ElectronApi;
6
+ listeners: Map<string, Set<(...args: unknown[]) => void>>;
7
+ calls: { feedUrl?: string; checks: number; quit: number };
8
+ } {
9
+ const listeners = new Map<string, Set<(...args: unknown[]) => void>>();
10
+ const calls = { feedUrl: undefined as string | undefined, checks: 0, quit: 0 };
11
+ const electron = {
12
+ autoUpdater: {
13
+ setFeedUrl: (options: { url: string }) => {
14
+ calls.feedUrl = options.url;
15
+ },
16
+ checkForUpdates: () => {
17
+ calls.checks++;
18
+ },
19
+ quitAndInstall: () => {
20
+ calls.quit++;
21
+ },
22
+ on: (event: string, listener: (...args: unknown[]) => void) => {
23
+ if (!listeners.has(event)) listeners.set(event, new Set());
24
+ listeners.get(event)?.add(listener);
25
+ },
26
+ removeListener: (event: string, listener: (...args: unknown[]) => void) => {
27
+ listeners.get(event)?.delete(listener);
28
+ },
29
+ },
30
+ } as unknown as ElectronApi;
31
+ return { electron, listeners, calls };
32
+ }
33
+
34
+ function emit(listeners: Map<string, Set<(...args: unknown[]) => void>>, event: string, ...args: unknown[]): void {
35
+ for (const listener of listeners.get(event) ?? []) listener(...args);
36
+ }
37
+
38
+ describe('createElectronUpdaterBackend', () => {
39
+ it('drives feed URL, checks, auto-download, and quit commands', () => {
40
+ const { electron, calls } = fakeElectron();
41
+ const backend = createElectronUpdaterBackend(electron);
42
+ backend.setFeedUrl('https://feed.test');
43
+ expect(calls.feedUrl).toBe('https://feed.test');
44
+ backend.checkForUpdates();
45
+ backend.downloadUpdate();
46
+ expect(calls.checks).toBe(2);
47
+ backend.quitAndInstall();
48
+ expect(calls.quit).toBe(1);
49
+ });
50
+
51
+ it('subscribeChecking and subscribeUpdateNotAvailable forward bare events', () => {
52
+ const { electron, listeners } = fakeElectron();
53
+ const backend = createElectronUpdaterBackend(electron);
54
+ let checking = 0;
55
+ let notAvailable = 0;
56
+ const unsubA = backend.subscribeChecking(() => checking++);
57
+ const unsubB = backend.subscribeUpdateNotAvailable(() => notAvailable++);
58
+ emit(listeners, 'checking-for-update');
59
+ emit(listeners, 'update-not-available');
60
+ expect(checking).toBe(1);
61
+ expect(notAvailable).toBe(1);
62
+ unsubA();
63
+ unsubB();
64
+ emit(listeners, 'checking-for-update');
65
+ expect(checking).toBe(1);
66
+ });
67
+
68
+ it('maps update-available and update-downloaded args into UpdateInfo', () => {
69
+ const { electron, listeners } = fakeElectron();
70
+ const backend = createElectronUpdaterBackend(electron);
71
+ let available: unknown;
72
+ let downloaded: unknown;
73
+ backend.subscribeUpdateAvailable((info) => (available = info));
74
+ backend.subscribeUpdateDownloaded((info) => (downloaded = info));
75
+ emit(listeners, 'update-available', {}, 'release notes', '1.2.3', '2026-01-01');
76
+ emit(listeners, 'update-downloaded', {}, 'dl notes', '4.5.6', '2026-02-02');
77
+ expect(available).toEqual({
78
+ version: '1.2.3',
79
+ notes: 'release notes',
80
+ releaseDate: '2026-01-01',
81
+ deltaFromVersion: null,
82
+ downloadSizeBytes: -1,
83
+ isMandatory: false,
84
+ minimumOsVersion: null,
85
+ sha512: '',
86
+ stagedRolloutPercent: 100,
87
+ });
88
+ expect(downloaded).toEqual({
89
+ version: '4.5.6',
90
+ notes: 'dl notes',
91
+ releaseDate: '2026-02-02',
92
+ deltaFromVersion: null,
93
+ downloadSizeBytes: -1,
94
+ isMandatory: false,
95
+ minimumOsVersion: null,
96
+ sha512: '',
97
+ stagedRolloutPercent: 100,
98
+ });
99
+ });
100
+
101
+ it('subscribeDownloadProgress is inert for the built-in updater', () => {
102
+ const { electron, listeners } = fakeElectron();
103
+ const backend = createElectronUpdaterBackend(electron);
104
+ const unsubscribe = backend.subscribeDownloadProgress(() => {});
105
+ expect(listeners.has('download-progress')).toBe(false);
106
+ expect(() => unsubscribe()).not.toThrow();
107
+ });
108
+
109
+ it('subscribeError wraps the error payload message into an UpdaterError', () => {
110
+ const { electron, listeners } = fakeElectron();
111
+ const backend = createElectronUpdaterBackend(electron);
112
+ let message = '';
113
+ backend.subscribeError((error) => (message = error.message));
114
+ emit(listeners, 'error', new Error('boom'));
115
+ expect(message).toBe('boom');
116
+ });
117
+ });