@flighthq/host-electron 0.5.1-next.920.da8f530 → 0.6.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.
- package/README.md +2 -2
- package/dist/contract.d.ts +19 -24
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +18 -24
- package/dist/contract.js.map +1 -1
- package/dist/electronApp.d.ts +62 -34
- package/dist/electronApp.d.ts.map +1 -1
- package/dist/electronApp.js +175 -234
- package/dist/electronApp.js.map +1 -1
- package/dist/electronClipboard.d.ts +7 -5
- package/dist/electronClipboard.d.ts.map +1 -1
- package/dist/electronClipboard.js +91 -66
- package/dist/electronClipboard.js.map +1 -1
- package/dist/electronDefaultHostGroups.d.ts +59 -0
- package/dist/electronDefaultHostGroups.d.ts.map +1 -0
- package/dist/electronDefaultHostGroups.js +109 -0
- package/dist/electronDefaultHostGroups.js.map +1 -0
- package/dist/electronDialog.d.ts +12 -9
- package/dist/electronDialog.d.ts.map +1 -1
- package/dist/electronDialog.js +28 -21
- package/dist/electronDialog.js.map +1 -1
- package/dist/electronIpc.d.ts +13 -11
- package/dist/electronIpc.d.ts.map +1 -1
- package/dist/electronIpc.js +32 -26
- package/dist/electronIpc.js.map +1 -1
- package/dist/electronMenu.d.ts +12 -6
- package/dist/electronMenu.d.ts.map +1 -1
- package/dist/electronMenu.js +41 -30
- package/dist/electronMenu.js.map +1 -1
- package/dist/electronMenuTemplate.d.ts +2 -1
- package/dist/electronMenuTemplate.d.ts.map +1 -1
- package/dist/electronMenuTemplate.js.map +1 -1
- package/dist/electronNotification.d.ts +25 -7
- package/dist/electronNotification.d.ts.map +1 -1
- package/dist/electronNotification.js +39 -16
- package/dist/electronNotification.js.map +1 -1
- package/dist/electronPlatform.d.ts +5 -3
- package/dist/electronPlatform.d.ts.map +1 -1
- package/dist/electronPlatform.js +9 -7
- package/dist/electronPlatform.js.map +1 -1
- package/dist/electronPower.d.ts +22 -11
- package/dist/electronPower.d.ts.map +1 -1
- package/dist/electronPower.js +48 -59
- package/dist/electronPower.js.map +1 -1
- package/dist/electronProtocol.d.ts +16 -8
- package/dist/electronProtocol.d.ts.map +1 -1
- package/dist/electronProtocol.js +45 -28
- package/dist/electronProtocol.js.map +1 -1
- package/dist/electronRegister.d.ts +5 -7
- package/dist/electronRegister.d.ts.map +1 -1
- package/dist/electronRegister.js +72 -99
- package/dist/electronRegister.js.map +1 -1
- package/dist/electronScreen.d.ts +7 -6
- package/dist/electronScreen.d.ts.map +1 -1
- package/dist/electronScreen.js +17 -14
- package/dist/electronScreen.js.map +1 -1
- package/dist/electronShell.d.ts +16 -8
- package/dist/electronShell.d.ts.map +1 -1
- package/dist/electronShell.js +50 -43
- package/dist/electronShell.js.map +1 -1
- package/dist/electronShortcut.d.ts +6 -4
- package/dist/electronShortcut.d.ts.map +1 -1
- package/dist/electronShortcut.js +16 -10
- package/dist/electronShortcut.js.map +1 -1
- package/dist/electronStorage.d.ts +5 -3
- package/dist/electronStorage.d.ts.map +1 -1
- package/dist/electronStorage.js +9 -7
- package/dist/electronStorage.js.map +1 -1
- package/dist/electronTray.d.ts +38 -21
- package/dist/electronTray.d.ts.map +1 -1
- package/dist/electronTray.js +110 -76
- package/dist/electronTray.js.map +1 -1
- package/dist/electronUpdater.d.ts +6 -3
- package/dist/electronUpdater.d.ts.map +1 -1
- package/dist/electronUpdater.js +10 -7
- package/dist/electronUpdater.js.map +1 -1
- package/dist/electronWindow.d.ts +23 -7
- package/dist/electronWindow.d.ts.map +1 -1
- package/dist/electronWindow.js +234 -152
- package/dist/electronWindow.js.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/types/ElectronApi.d.ts +393 -0
- package/dist/types/ElectronApi.d.ts.map +1 -0
- package/dist/types/ElectronApi.js +20 -0
- package/dist/types/ElectronApi.js.map +1 -0
- package/dist/types/ElectronAppCapabilitiesFor.d.ts +8 -0
- package/dist/types/ElectronAppCapabilitiesFor.d.ts.map +1 -0
- package/dist/types/ElectronAppCapabilitiesFor.js +2 -0
- package/dist/types/ElectronAppCapabilitiesFor.js.map +1 -0
- package/dist/types/ElectronHost.d.ts +43 -0
- package/dist/types/ElectronHost.d.ts.map +1 -0
- package/dist/types/ElectronHost.js +2 -0
- package/dist/types/ElectronHost.js.map +1 -0
- package/dist/types/ElectronHostGroups.d.ts +85 -0
- package/dist/types/ElectronHostGroups.d.ts.map +1 -0
- package/dist/types/ElectronHostGroups.js +2 -0
- package/dist/types/ElectronHostGroups.js.map +1 -0
- package/dist/types/ElectronHostOptions.d.ts +7 -0
- package/dist/types/ElectronHostOptions.d.ts.map +1 -0
- package/dist/types/ElectronHostOptions.js +2 -0
- package/dist/types/ElectronHostOptions.js.map +1 -0
- package/dist/types/ElectronProtocolCapabilities.d.ts +3 -0
- package/dist/types/ElectronProtocolCapabilities.d.ts.map +1 -0
- package/dist/types/ElectronProtocolCapabilities.js +2 -0
- package/dist/types/ElectronProtocolCapabilities.js.map +1 -0
- package/dist/types/ElectronTrayCapabilitiesFor.d.ts +7 -0
- package/dist/types/ElectronTrayCapabilitiesFor.d.ts.map +1 -0
- package/dist/types/ElectronTrayCapabilitiesFor.js +2 -0
- package/dist/types/ElectronTrayCapabilitiesFor.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +42 -21
- package/src/electronApp.test.ts +158 -131
- package/src/electronClipboard.test.ts +40 -13
- package/src/electronDefaultHostGroups.test.ts +148 -0
- package/src/electronDialog.test.ts +55 -53
- package/src/electronHostTreeShaking.test.ts +51 -0
- package/src/electronIpc.test.ts +47 -66
- package/src/electronMenu.test.ts +61 -47
- package/src/electronMenuTemplate.test.ts +1 -1
- package/src/electronNotification.test.ts +71 -21
- package/src/electronPlatform.test.ts +17 -12
- package/src/electronPower.test.ts +112 -88
- package/src/electronProtocol.test.ts +68 -35
- package/src/electronRegister.test.ts +280 -26
- package/src/electronScreen.test.ts +34 -25
- package/src/electronShell.test.ts +99 -59
- package/src/electronShortcut.test.ts +22 -19
- package/src/electronStorage.test.ts +26 -24
- package/src/electronTray.test.ts +225 -109
- package/src/electronUpdater.test.ts +26 -19
- package/src/electronWindow.test.ts +229 -101
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
webHostAudioDecode,
|
|
3
|
+
webHostImageDecode,
|
|
4
|
+
webHostImageEncode,
|
|
5
|
+
webHostTextShaper,
|
|
6
|
+
} from '@flighthq/host-web/contract';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
electronHostAccessibilityGroup,
|
|
10
|
+
electronHostAudioGroup,
|
|
11
|
+
electronHostBitmapGroup,
|
|
12
|
+
electronHostConnectivityGroup,
|
|
13
|
+
electronHostDeviceGroup,
|
|
14
|
+
electronHostFileSystemGroup,
|
|
15
|
+
electronHostFontGroup,
|
|
16
|
+
electronHostFullscreenGroup,
|
|
17
|
+
electronHostGeolocationGroup,
|
|
18
|
+
electronHostGlGroup,
|
|
19
|
+
electronHostGlyphGroup,
|
|
20
|
+
electronHostHapticsGroup,
|
|
21
|
+
electronHostAudioDecodeGroup,
|
|
22
|
+
electronHostImageDecodeGroup,
|
|
23
|
+
electronHostImageEncodeGroup,
|
|
24
|
+
electronHostImageGroup,
|
|
25
|
+
electronHostInputGroup,
|
|
26
|
+
electronHostLifecycleGroup,
|
|
27
|
+
electronHostMediaSessionGroup,
|
|
28
|
+
electronHostMidiGroup,
|
|
29
|
+
electronHostNetGroup,
|
|
30
|
+
electronHostPermissionsGroup,
|
|
31
|
+
electronHostSensorsGroup,
|
|
32
|
+
electronHostShareGroup,
|
|
33
|
+
electronHostSocketGroup,
|
|
34
|
+
electronHostSoftKeyboardGroup,
|
|
35
|
+
electronHostStatusBarGroup,
|
|
36
|
+
electronHostCanvasGroup,
|
|
37
|
+
electronHostSurfaceGroup,
|
|
38
|
+
electronHostTextSegmentGroup,
|
|
39
|
+
electronHostCompressGroup,
|
|
40
|
+
electronHostDecompressGroup,
|
|
41
|
+
electronHostTextShaperGroup,
|
|
42
|
+
electronHostVideoGroup,
|
|
43
|
+
electronHostWgpuGroup,
|
|
44
|
+
} from './electronDefaultHostGroups.ts';
|
|
45
|
+
import type { ElectronApi } from './types/index.ts';
|
|
46
|
+
|
|
47
|
+
const electron = {} as ElectronApi;
|
|
48
|
+
|
|
49
|
+
function emptyGroup(factory: () => object): () => void {
|
|
50
|
+
return () => {
|
|
51
|
+
it('constructs an exact empty capability group', () => {
|
|
52
|
+
expect(factory()).toEqual({});
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const accessibilityGroup = emptyGroup(() => electronHostAccessibilityGroup(electron));
|
|
58
|
+
const audioGroup = emptyGroup(() => electronHostAudioGroup(electron));
|
|
59
|
+
const bitmapGroup = emptyGroup(() => electronHostBitmapGroup(electron));
|
|
60
|
+
const connectivityGroup = emptyGroup(() => electronHostConnectivityGroup(electron));
|
|
61
|
+
const deviceGroup = emptyGroup(() => electronHostDeviceGroup(electron));
|
|
62
|
+
const fileSystemGroup = emptyGroup(() => electronHostFileSystemGroup(electron));
|
|
63
|
+
const fontGroup = emptyGroup(() => electronHostFontGroup(electron));
|
|
64
|
+
const fullscreenGroup = emptyGroup(() => electronHostFullscreenGroup(electron));
|
|
65
|
+
const geolocationGroup = emptyGroup(() => electronHostGeolocationGroup(electron));
|
|
66
|
+
const glGroup = emptyGroup(() => electronHostGlGroup(electron));
|
|
67
|
+
const glyphGroup = emptyGroup(() => electronHostGlyphGroup(electron));
|
|
68
|
+
const hapticsGroup = emptyGroup(() => electronHostHapticsGroup(electron));
|
|
69
|
+
const imageGroup = emptyGroup(() => electronHostImageGroup(electron));
|
|
70
|
+
const inputGroup = emptyGroup(() => electronHostInputGroup(electron));
|
|
71
|
+
const lifecycleGroup = emptyGroup(() => electronHostLifecycleGroup(electron));
|
|
72
|
+
const mediaSessionGroup = emptyGroup(() => electronHostMediaSessionGroup(electron));
|
|
73
|
+
const midiGroup = emptyGroup(() => electronHostMidiGroup(electron));
|
|
74
|
+
const netGroup = emptyGroup(() => electronHostNetGroup(electron));
|
|
75
|
+
const permissionsGroup = emptyGroup(() => electronHostPermissionsGroup(electron));
|
|
76
|
+
const sensorsGroup = emptyGroup(() => electronHostSensorsGroup(electron));
|
|
77
|
+
const shareGroup = emptyGroup(() => electronHostShareGroup(electron));
|
|
78
|
+
const socketGroup = emptyGroup(() => electronHostSocketGroup(electron));
|
|
79
|
+
const softKeyboardGroup = emptyGroup(() => electronHostSoftKeyboardGroup(electron));
|
|
80
|
+
const statusBarGroup = emptyGroup(() => electronHostStatusBarGroup(electron));
|
|
81
|
+
const canvasGroup = emptyGroup(() => electronHostCanvasGroup(electron));
|
|
82
|
+
const surfaceGroup = emptyGroup(() => electronHostSurfaceGroup(electron));
|
|
83
|
+
const textSegmentGroup = emptyGroup(() => electronHostTextSegmentGroup(electron));
|
|
84
|
+
const compressGroup = emptyGroup(() => electronHostCompressGroup(electron));
|
|
85
|
+
const decompressGroup = emptyGroup(() => electronHostDecompressGroup(electron));
|
|
86
|
+
const videoGroup = emptyGroup(() => electronHostVideoGroup(electron));
|
|
87
|
+
const wgpuGroup = emptyGroup(() => electronHostWgpuGroup(electron));
|
|
88
|
+
|
|
89
|
+
describe('electronHostAccessibilityGroup', accessibilityGroup);
|
|
90
|
+
describe('electronHostAudioDecodeGroup', () => {
|
|
91
|
+
it('delegates to the shared web audio decode capabilities', () => {
|
|
92
|
+
expect(electronHostAudioDecodeGroup(electron)).toBe(webHostAudioDecode);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('electronHostAudioGroup', audioGroup);
|
|
96
|
+
describe('electronHostBitmapGroup', bitmapGroup);
|
|
97
|
+
describe('electronHostCanvasGroup', canvasGroup);
|
|
98
|
+
describe('electronHostCompressGroup', compressGroup);
|
|
99
|
+
describe('electronHostConnectivityGroup', connectivityGroup);
|
|
100
|
+
describe('electronHostDecompressGroup', decompressGroup);
|
|
101
|
+
describe('electronHostDeviceGroup', deviceGroup);
|
|
102
|
+
describe('electronHostFileSystemGroup', fileSystemGroup);
|
|
103
|
+
describe('electronHostFontGroup', fontGroup);
|
|
104
|
+
describe('electronHostFullscreenGroup', fullscreenGroup);
|
|
105
|
+
describe('electronHostGeolocationGroup', geolocationGroup);
|
|
106
|
+
describe('electronHostGlGroup', glGroup);
|
|
107
|
+
describe('electronHostGlyphGroup', glyphGroup);
|
|
108
|
+
describe('electronHostHapticsGroup', hapticsGroup);
|
|
109
|
+
describe('electronHostImageDecodeGroup', () => {
|
|
110
|
+
it('delegates to the shared web image decode capabilities', () => {
|
|
111
|
+
expect(electronHostImageDecodeGroup(electron)).toBe(webHostImageDecode);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('electronHostImageEncodeGroup', () => {
|
|
115
|
+
it('delegates to the shared web image encode capabilities', () => {
|
|
116
|
+
expect(electronHostImageEncodeGroup(electron)).toBe(webHostImageEncode);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe('electronHostImageGroup', imageGroup);
|
|
120
|
+
describe('electronHostInputGroup', inputGroup);
|
|
121
|
+
describe('electronHostLifecycleGroup', lifecycleGroup);
|
|
122
|
+
describe('electronHostMediaSessionGroup', mediaSessionGroup);
|
|
123
|
+
describe('electronHostMidiGroup', midiGroup);
|
|
124
|
+
describe('electronHostNetGroup', netGroup);
|
|
125
|
+
describe('electronHostPermissionsGroup', permissionsGroup);
|
|
126
|
+
describe('electronHostSensorsGroup', sensorsGroup);
|
|
127
|
+
describe('electronHostShareGroup', shareGroup);
|
|
128
|
+
describe('electronHostSocketGroup', socketGroup);
|
|
129
|
+
describe('electronHostSoftKeyboardGroup', softKeyboardGroup);
|
|
130
|
+
describe('electronHostStatusBarGroup', statusBarGroup);
|
|
131
|
+
describe('electronHostSurfaceGroup', surfaceGroup);
|
|
132
|
+
|
|
133
|
+
describe('electronHostTextSegmentGroup', textSegmentGroup);
|
|
134
|
+
describe('electronHostTextShaperGroup', () => {
|
|
135
|
+
it('fills the shaper slot with the shared web text shaper', () => {
|
|
136
|
+
const group = electronHostTextShaperGroup(electron);
|
|
137
|
+
expect(group.shaper).toBe(webHostTextShaper);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('is plain data with no Entity runtime and no symbol keys', () => {
|
|
141
|
+
const group = electronHostTextShaperGroup(electron);
|
|
142
|
+
expect(Symbol.for('EntityRuntime') in group).toBe(false);
|
|
143
|
+
expect(Object.getOwnPropertySymbols(group)).toEqual([]);
|
|
144
|
+
expect(Object.keys(group)).toEqual(['shaper']);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('electronHostVideoGroup', videoGroup);
|
|
148
|
+
describe('electronHostWgpuGroup', wgpuGroup);
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
electronHostDialog,
|
|
3
|
+
electronHostDirectoryOpenDialog,
|
|
4
|
+
electronHostFileOpenDialog,
|
|
5
|
+
electronHostFileSaveDialog,
|
|
6
|
+
electronHostMessageDialog,
|
|
7
|
+
populateElectronHostDirectoryOpenDialog,
|
|
8
|
+
populateElectronHostFileOpenDialog,
|
|
9
|
+
populateElectronHostFileSaveDialog,
|
|
10
|
+
populateElectronHostMessageDialog,
|
|
11
|
+
} from './electronDialog.ts';
|
|
1
12
|
import type {
|
|
2
13
|
ElectronApi,
|
|
3
14
|
ElectronMessageBoxOptions,
|
|
4
15
|
ElectronOpenDialogOptions,
|
|
5
16
|
ElectronSaveDialogOptions,
|
|
6
|
-
} from '
|
|
7
|
-
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
createElectronDirectoryOpenDialogBackend,
|
|
11
|
-
createElectronFileOpenDialogBackend,
|
|
12
|
-
createElectronFileSaveDialogBackend,
|
|
13
|
-
createElectronMessageDialogBackend,
|
|
14
|
-
initializeElectronDirectoryOpenDialogBackend,
|
|
15
|
-
initializeElectronFileOpenDialogBackend,
|
|
16
|
-
initializeElectronFileSaveDialogBackend,
|
|
17
|
-
initializeElectronMessageDialogBackend,
|
|
18
|
-
} from './electronDialog';
|
|
17
|
+
} from './types/index.ts';
|
|
19
18
|
|
|
20
19
|
function fakeElectron(overrides: {
|
|
21
20
|
open?: { canceled: boolean; filePaths: string[] } | Error;
|
|
@@ -48,37 +47,42 @@ function fakeElectron(overrides: {
|
|
|
48
47
|
return { electron, calls };
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
describe('
|
|
50
|
+
describe('electronHostDialog', () => {
|
|
51
|
+
it('constructs the exact dialog group', () => {
|
|
52
|
+
const dialog = electronHostDialog(fakeElectron({}).electron);
|
|
53
|
+
expect(Object.keys(dialog).sort()).toEqual(['directoryOpen', 'fileOpen', 'fileSave', 'message']);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('electronHostDirectoryOpenDialog', () => {
|
|
52
58
|
it('uses a method-tight single-directory operation', async () => {
|
|
53
59
|
const { electron, calls } = fakeElectron({ open: { canceled: false, filePaths: ['/docs'] } });
|
|
54
|
-
const result = await
|
|
60
|
+
const result = await electronHostDirectoryOpenDialog(electron).open();
|
|
55
61
|
expect(calls.openOptions).toEqual({ properties: ['openDirectory'] });
|
|
56
62
|
expect(result.outcome === 'selected' ? result.handle.path : null).toBe('/docs');
|
|
57
63
|
});
|
|
58
64
|
});
|
|
59
65
|
|
|
60
|
-
describe('
|
|
61
|
-
it('exposes independent
|
|
66
|
+
describe('electronHostFileOpenDialog', () => {
|
|
67
|
+
it('exposes independent providers', () => {
|
|
62
68
|
const electron = fakeElectron({}).electron;
|
|
63
69
|
const providers = [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
electronHostDirectoryOpenDialog(electron),
|
|
71
|
+
electronHostFileOpenDialog(electron),
|
|
72
|
+
electronHostFileSaveDialog(electron),
|
|
67
73
|
];
|
|
68
|
-
expect(providers.every((provider) => EntityRuntimeKey in provider)).toBe(true);
|
|
69
74
|
expect(new Set(providers).size).toBe(3);
|
|
70
75
|
});
|
|
71
76
|
|
|
72
77
|
it('returns selected nonempty file handles and maps common filters/multiple', async () => {
|
|
73
78
|
const { electron, calls } = fakeElectron({ open: { canceled: false, filePaths: ['/a', '/b'] } });
|
|
74
|
-
const result = await
|
|
79
|
+
const result = await electronHostFileOpenDialog(electron).open({
|
|
75
80
|
filters: [{ accept: { 'text/plain': ['.txt'], 'text/markdown': ['md'] }, name: 'Text' }],
|
|
76
81
|
multiple: true,
|
|
77
82
|
});
|
|
78
83
|
expect(result.outcome).toBe('selected');
|
|
79
84
|
if (result.outcome === 'selected') {
|
|
80
85
|
expect(result.handles.map((handle) => handle.name)).toEqual(['a', 'b']);
|
|
81
|
-
expect(result.handles.every((handle) => EntityRuntimeKey in handle)).toBe(true);
|
|
82
86
|
}
|
|
83
87
|
expect(calls.openOptions).toEqual({
|
|
84
88
|
filters: [{ extensions: ['txt', 'md'], name: 'Text' }],
|
|
@@ -88,21 +92,19 @@ describe('createElectronFileOpenDialogBackend', () => {
|
|
|
88
92
|
|
|
89
93
|
it('uses the independent single-directory operation and preserves a root basename', async () => {
|
|
90
94
|
const { electron, calls } = fakeElectron({ open: { canceled: false, filePaths: ['/'] } });
|
|
91
|
-
const result = await
|
|
95
|
+
const result = await electronHostDirectoryOpenDialog(electron).open();
|
|
92
96
|
expect(calls.openOptions).toEqual({ properties: ['openDirectory'] });
|
|
93
97
|
expect(result.outcome === 'selected' ? result.handle.name : null).toBe('/');
|
|
94
98
|
});
|
|
95
99
|
|
|
96
100
|
it('distinguishes cancel, security denial, and operation failure', async () => {
|
|
97
|
-
const cancelled = await
|
|
101
|
+
const cancelled = await electronHostFileOpenDialog(
|
|
98
102
|
fakeElectron({ open: { canceled: true, filePaths: ['/ignored'] } }).electron,
|
|
99
103
|
).open({});
|
|
100
|
-
const security = await
|
|
104
|
+
const security = await electronHostFileOpenDialog(
|
|
101
105
|
fakeElectron({ open: Object.assign(new Error('denied'), { code: 'EACCES' }) }).electron,
|
|
102
106
|
).open({});
|
|
103
|
-
const failed = await
|
|
104
|
-
{},
|
|
105
|
-
);
|
|
107
|
+
const failed = await electronHostFileOpenDialog(fakeElectron({ open: new Error('broken') }).electron).open({});
|
|
106
108
|
expect(cancelled.outcome).toBe('cancelled');
|
|
107
109
|
expect(security.outcome).toBe('security-denied');
|
|
108
110
|
expect(failed.outcome).toBe('file-open-failed');
|
|
@@ -110,81 +112,81 @@ describe('createElectronFileOpenDialogBackend', () => {
|
|
|
110
112
|
|
|
111
113
|
it('maps the common defaultName invariant and returns a selected save handle', async () => {
|
|
112
114
|
const { electron, calls } = fakeElectron({ save: { canceled: false, filePath: '/out.txt' } });
|
|
113
|
-
const result = await
|
|
115
|
+
const result = await electronHostFileSaveDialog(electron).save({ defaultName: 'suggested.txt' });
|
|
114
116
|
expect(calls.saveOptions?.defaultPath).toBe('suggested.txt');
|
|
115
117
|
expect(result.outcome === 'selected' ? result.handle.path : null).toBe('/out.txt');
|
|
116
118
|
});
|
|
117
119
|
|
|
118
120
|
it('reports runtime-unavailable when an Electron dialog method is absent', async () => {
|
|
119
121
|
const electron = { dialog: {} } as ElectronApi;
|
|
120
|
-
expect((await
|
|
121
|
-
expect((await
|
|
122
|
-
expect((await
|
|
122
|
+
expect((await electronHostDirectoryOpenDialog(electron).open()).outcome).toBe('runtime-unavailable');
|
|
123
|
+
expect((await electronHostFileOpenDialog(electron).open({})).outcome).toBe('runtime-unavailable');
|
|
124
|
+
expect((await electronHostFileSaveDialog(electron).save({})).outcome).toBe('runtime-unavailable');
|
|
123
125
|
});
|
|
124
126
|
|
|
125
127
|
it('does not open a native picker when already aborted', async () => {
|
|
126
128
|
const { electron, calls } = fakeElectron({ open: { canceled: false, filePaths: ['/ignored'] } });
|
|
127
129
|
const controller = new AbortController();
|
|
128
130
|
controller.abort();
|
|
129
|
-
await expect(
|
|
131
|
+
await expect(electronHostFileOpenDialog(electron).open({ signal: controller.signal })).resolves.toEqual({
|
|
130
132
|
outcome: 'cancelled',
|
|
131
133
|
});
|
|
132
134
|
expect(calls.openOptions).toBeUndefined();
|
|
133
135
|
});
|
|
134
136
|
});
|
|
135
137
|
|
|
136
|
-
describe('
|
|
138
|
+
describe('electronHostFileSaveDialog', () => {
|
|
137
139
|
it('maps the common default name and returns one selected handle', async () => {
|
|
138
140
|
const { electron, calls } = fakeElectron({ save: { canceled: false, filePath: '/saved.txt' } });
|
|
139
|
-
const result = await
|
|
141
|
+
const result = await electronHostFileSaveDialog(electron).save({ defaultName: 'suggested.txt' });
|
|
140
142
|
expect(calls.saveOptions?.defaultPath).toBe('suggested.txt');
|
|
141
143
|
expect(result.outcome === 'selected' ? result.handle.path : null).toBe('/saved.txt');
|
|
142
144
|
});
|
|
143
145
|
});
|
|
144
146
|
|
|
145
|
-
describe('
|
|
147
|
+
describe('electronHostMessageDialog', () => {
|
|
146
148
|
it('maps message results to button index, cancelled, and checkbox state', async () => {
|
|
147
149
|
const { electron, calls } = fakeElectron({ message: { response: 2, checkboxChecked: true } });
|
|
148
|
-
const result = await
|
|
150
|
+
const result = await electronHostMessageDialog(electron).message({ message: 'hi', kind: 'warning' });
|
|
149
151
|
expect(result).toEqual({ buttonIndex: 2, cancelled: false, checkboxChecked: true });
|
|
150
152
|
expect(calls.messageOptions?.type).toBe('warning');
|
|
151
153
|
});
|
|
152
154
|
|
|
153
155
|
it('confirm returns true only when the OK button is chosen', async () => {
|
|
154
156
|
const yes = fakeElectron({ message: { response: 0, checkboxChecked: false } });
|
|
155
|
-
expect(await
|
|
157
|
+
expect(await electronHostMessageDialog(yes.electron).confirm({ message: 'ok?' })).toBe(true);
|
|
156
158
|
const no = fakeElectron({ message: { response: 1, checkboxChecked: false } });
|
|
157
|
-
expect(await
|
|
159
|
+
expect(await electronHostMessageDialog(no.electron).confirm({ message: 'ok?' })).toBe(false);
|
|
158
160
|
expect(no.calls.messageOptions?.buttons).toEqual(['OK', 'Cancel']);
|
|
159
161
|
});
|
|
160
162
|
|
|
161
163
|
it('forwards the signal supported by Electron message boxes', async () => {
|
|
162
164
|
const { electron, calls } = fakeElectron({});
|
|
163
165
|
const signal = new AbortController().signal;
|
|
164
|
-
await
|
|
166
|
+
await electronHostMessageDialog(electron).message({ message: 'wait', signal });
|
|
165
167
|
expect(calls.messageOptions?.signal).toBe(signal);
|
|
166
168
|
});
|
|
167
169
|
});
|
|
168
|
-
describe('
|
|
169
|
-
it('is the construction initializer of
|
|
170
|
-
expect(typeof
|
|
170
|
+
describe('populateElectronHostDirectoryOpenDialog', () => {
|
|
171
|
+
it('is the construction initializer of electronHostDirectoryOpenDialog', () => {
|
|
172
|
+
expect(typeof populateElectronHostDirectoryOpenDialog).toBe('function');
|
|
171
173
|
});
|
|
172
174
|
});
|
|
173
175
|
|
|
174
|
-
describe('
|
|
175
|
-
it('is the construction initializer of
|
|
176
|
-
expect(typeof
|
|
176
|
+
describe('populateElectronHostFileOpenDialog', () => {
|
|
177
|
+
it('is the construction initializer of electronHostFileOpenDialog', () => {
|
|
178
|
+
expect(typeof populateElectronHostFileOpenDialog).toBe('function');
|
|
177
179
|
});
|
|
178
180
|
});
|
|
179
181
|
|
|
180
|
-
describe('
|
|
181
|
-
it('is the construction initializer of
|
|
182
|
-
expect(typeof
|
|
182
|
+
describe('populateElectronHostFileSaveDialog', () => {
|
|
183
|
+
it('is the construction initializer of electronHostFileSaveDialog', () => {
|
|
184
|
+
expect(typeof populateElectronHostFileSaveDialog).toBe('function');
|
|
183
185
|
});
|
|
184
186
|
});
|
|
185
187
|
|
|
186
|
-
describe('
|
|
187
|
-
it('is the construction initializer of
|
|
188
|
-
expect(typeof
|
|
188
|
+
describe('populateElectronHostMessageDialog', () => {
|
|
189
|
+
it('is the construction initializer of electronHostMessageDialog', () => {
|
|
190
|
+
expect(typeof populateElectronHostMessageDialog).toBe('function');
|
|
189
191
|
});
|
|
190
192
|
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import { build } from 'esbuild';
|
|
4
|
+
|
|
5
|
+
describe('Electron Host tree shaking', () => {
|
|
6
|
+
it('keeps a direct App leaf independent of sibling and full-host assembly', async () => {
|
|
7
|
+
const output = await bundleElectronHostExport('electronHostAppName');
|
|
8
|
+
expect(output).toContain('electronHostAppName');
|
|
9
|
+
expect(output).toContain('getName');
|
|
10
|
+
expect(output).not.toContain('getLocale');
|
|
11
|
+
expect(output).not.toContain('requestSingleInstanceLock');
|
|
12
|
+
expect(output).not.toContain('availableFormats');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('keeps the Clipboard text leaf independent of sibling clipboard providers', async () => {
|
|
16
|
+
const output = await bundleElectronHostExport('electronHostClipboardText');
|
|
17
|
+
expect(output).toContain('electronHostClipboardText');
|
|
18
|
+
expect(output).toContain('readText');
|
|
19
|
+
expect(output).toContain('writeText');
|
|
20
|
+
expect(output).not.toContain('availableFormats');
|
|
21
|
+
expect(output).not.toContain('nativeImage');
|
|
22
|
+
expect(output).not.toContain('readBookmark');
|
|
23
|
+
expect(output).not.toContain('readRTF');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function bundleElectronHostExport(name: string): Promise<string> {
|
|
28
|
+
const result = await build({
|
|
29
|
+
bundle: true,
|
|
30
|
+
format: 'esm',
|
|
31
|
+
logLevel: 'silent',
|
|
32
|
+
minify: true,
|
|
33
|
+
packages: 'external',
|
|
34
|
+
platform: 'node',
|
|
35
|
+
stdin: {
|
|
36
|
+
contents: `export { ${name} } from './index.ts';`,
|
|
37
|
+
resolveDir: fileUrlDirectory(import.meta.url),
|
|
38
|
+
sourcefile: `tree-shake-${name}.ts`,
|
|
39
|
+
},
|
|
40
|
+
treeShaking: true,
|
|
41
|
+
write: false,
|
|
42
|
+
});
|
|
43
|
+
return result.outputFiles[0].text;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fileUrlDirectory(url: string): string {
|
|
47
|
+
const directory = new URL('.', url);
|
|
48
|
+
const pathname = decodeURIComponent(directory.pathname);
|
|
49
|
+
if (directory.hostname !== '') return `//${directory.hostname}${pathname}`;
|
|
50
|
+
return /^\/[A-Za-z]:\//.test(pathname) ? pathname.slice(1) : pathname;
|
|
51
|
+
}
|
package/src/electronIpc.test.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import type { ElectronApi, ElectronIpcRenderer, ElectronIpcTarget, Entity } from '@flighthq/types/contract';
|
|
2
|
-
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
3
|
-
|
|
4
1
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
electronHostIpc,
|
|
3
|
+
electronHostIpcHandle,
|
|
4
|
+
electronHostIpcInvoke,
|
|
5
|
+
electronHostIpcMessage,
|
|
6
|
+
electronHostIpcSend,
|
|
7
|
+
electronHostIpcTargetedSend,
|
|
8
|
+
populateElectronHostIpcHandle,
|
|
9
|
+
populateElectronHostIpcInvoke,
|
|
10
|
+
populateElectronHostIpcMessage,
|
|
11
|
+
populateElectronHostIpcSend,
|
|
12
|
+
populateElectronHostIpcTargetedSend,
|
|
13
|
+
} from './electronIpc.ts';
|
|
14
|
+
import type { ElectronApi, ElectronIpcRenderer, ElectronIpcTarget } from './types/index.ts';
|
|
16
15
|
|
|
17
16
|
function fakeElectron(): {
|
|
18
17
|
channels: Map<string, Set<(event: unknown, ...args: unknown[]) => void>>;
|
|
@@ -63,10 +62,17 @@ function fakeRenderer(): {
|
|
|
63
62
|
};
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
describe('
|
|
65
|
+
describe('electronHostIpc', () => {
|
|
66
|
+
it('constructs the main-process IPC group', () => {
|
|
67
|
+
const ipc = electronHostIpc(fakeElectron().electron);
|
|
68
|
+
expect(Object.keys(ipc).sort()).toEqual(['handle', 'message', 'targetedSend']);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('electronHostIpcHandle', () => {
|
|
67
73
|
it('registers a main-process handler, drops the event, and releases it idempotently', async () => {
|
|
68
74
|
const { electron, handlers } = fakeElectron();
|
|
69
|
-
const backend =
|
|
75
|
+
const backend = electronHostIpcHandle(electron);
|
|
70
76
|
const stop = backend.handle('double', (value) => (value as number) * 2);
|
|
71
77
|
|
|
72
78
|
expect(await handlers.get('double')?.({ sender: 1 }, 4)).toBe(8);
|
|
@@ -75,37 +81,22 @@ describe('createElectronIpcHandleBackend', () => {
|
|
|
75
81
|
stop();
|
|
76
82
|
expect(handlers.has('double')).toBe(false);
|
|
77
83
|
});
|
|
78
|
-
|
|
79
|
-
it('returns an Entity', () => {
|
|
80
|
-
const backend: Entity = createElectronIpcHandleBackend(fakeElectron().electron);
|
|
81
|
-
expect(EntityRuntimeKey in backend).toBe(true);
|
|
82
|
-
});
|
|
83
84
|
});
|
|
84
85
|
|
|
85
|
-
describe('
|
|
86
|
+
describe('electronHostIpcInvoke', () => {
|
|
86
87
|
it('invokes from the renderer with spread arguments and returns the response', async () => {
|
|
87
88
|
const { invocations, renderer } = fakeRenderer();
|
|
88
|
-
const backend =
|
|
89
|
+
const backend = electronHostIpcInvoke(renderer);
|
|
89
90
|
|
|
90
91
|
await expect(backend.invoke('compute', [1, 2])).resolves.toEqual({ args: [1, 2], channel: 'compute' });
|
|
91
92
|
expect(invocations).toEqual([{ args: [1, 2], channel: 'compute' }]);
|
|
92
93
|
});
|
|
93
|
-
|
|
94
|
-
it('returns an Entity', () => {
|
|
95
|
-
const backend: Entity = createElectronIpcInvokeBackend(fakeRenderer().renderer);
|
|
96
|
-
expect(EntityRuntimeKey in backend).toBe(true);
|
|
97
|
-
});
|
|
98
94
|
});
|
|
99
95
|
|
|
100
|
-
describe('
|
|
101
|
-
it('returns an Entity in both runtime and type', () => {
|
|
102
|
-
const backend: Entity = createElectronIpcMessageBackend(fakeElectron().electron);
|
|
103
|
-
expect(EntityRuntimeKey in backend).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
96
|
+
describe('electronHostIpcMessage', () => {
|
|
106
97
|
it('delivers a renderer message with its arguments, dropping the event object', () => {
|
|
107
98
|
const { electron, channels } = fakeElectron();
|
|
108
|
-
const backend =
|
|
99
|
+
const backend = electronHostIpcMessage(electron);
|
|
109
100
|
const seen: readonly unknown[][] = [];
|
|
110
101
|
backend.subscribe('ping', (args) => (seen as unknown[][]).push([...args]));
|
|
111
102
|
for (const listener of channels.get('ping') ?? []) listener({ sender: 1 }, 'a', 2);
|
|
@@ -116,7 +107,7 @@ describe('createElectronIpcMessageBackend', () => {
|
|
|
116
107
|
// provider-level destroy: ipcMain is the caller's to tear down, not this backend's.
|
|
117
108
|
it('removes exactly its own ipcMain listener on unsubscribe', () => {
|
|
118
109
|
const { electron, channels } = fakeElectron();
|
|
119
|
-
const backend =
|
|
110
|
+
const backend = electronHostIpcMessage(electron);
|
|
120
111
|
const stopFirst = backend.subscribe('ping', () => {});
|
|
121
112
|
backend.subscribe('ping', () => {});
|
|
122
113
|
expect(channels.get('ping')?.size).toBe(2);
|
|
@@ -125,7 +116,7 @@ describe('createElectronIpcMessageBackend', () => {
|
|
|
125
116
|
});
|
|
126
117
|
|
|
127
118
|
it('remains independent from the other IPC capabilities', () => {
|
|
128
|
-
const backend =
|
|
119
|
+
const backend = electronHostIpcMessage(fakeElectron().electron);
|
|
129
120
|
expect(Object.keys(backend).filter((key) => key !== 'constructor')).toEqual(['subscribe']);
|
|
130
121
|
expect('send' in backend).toBe(false);
|
|
131
122
|
expect('invoke' in backend).toBe(false);
|
|
@@ -135,23 +126,18 @@ describe('createElectronIpcMessageBackend', () => {
|
|
|
135
126
|
});
|
|
136
127
|
});
|
|
137
128
|
|
|
138
|
-
describe('
|
|
129
|
+
describe('electronHostIpcSend', () => {
|
|
139
130
|
it('sends from the renderer with spread arguments', () => {
|
|
140
131
|
const { renderer, sent } = fakeRenderer();
|
|
141
|
-
const backend =
|
|
132
|
+
const backend = electronHostIpcSend(renderer);
|
|
142
133
|
|
|
143
134
|
backend.send('log', ['hello', 7]);
|
|
144
135
|
|
|
145
136
|
expect(sent).toEqual([{ args: ['hello', 7], channel: 'log' }]);
|
|
146
137
|
});
|
|
147
|
-
|
|
148
|
-
it('returns an Entity', () => {
|
|
149
|
-
const backend: Entity = createElectronIpcSendBackend(fakeRenderer().renderer);
|
|
150
|
-
expect(EntityRuntimeKey in backend).toBe(true);
|
|
151
|
-
});
|
|
152
138
|
});
|
|
153
139
|
|
|
154
|
-
describe('
|
|
140
|
+
describe('electronHostIpcTargetedSend', () => {
|
|
155
141
|
it('keeps the target generic and delegates to its Electron send seam', () => {
|
|
156
142
|
const sent: Array<{ readonly args: readonly unknown[]; readonly channel: string }> = [];
|
|
157
143
|
const target: ElectronIpcTarget & { readonly label: string } = {
|
|
@@ -160,44 +146,39 @@ describe('createElectronIpcTargetedSendBackend', () => {
|
|
|
160
146
|
sent.push({ args, channel });
|
|
161
147
|
},
|
|
162
148
|
};
|
|
163
|
-
const backend =
|
|
149
|
+
const backend = electronHostIpcTargetedSend<typeof target>();
|
|
164
150
|
|
|
165
151
|
backend.send(target, 'refresh', [1, 2]);
|
|
166
152
|
|
|
167
153
|
expect(sent).toEqual([{ args: [1, 2], channel: 'refresh' }]);
|
|
168
154
|
});
|
|
169
|
-
|
|
170
|
-
it('returns an Entity', () => {
|
|
171
|
-
const backend: Entity = createElectronIpcTargetedSendBackend<ElectronIpcTarget>();
|
|
172
|
-
expect(EntityRuntimeKey in backend).toBe(true);
|
|
173
|
-
});
|
|
174
155
|
});
|
|
175
|
-
describe('
|
|
176
|
-
it('is the construction initializer of
|
|
177
|
-
expect(typeof
|
|
156
|
+
describe('populateElectronHostIpcHandle', () => {
|
|
157
|
+
it('is the construction initializer of electronHostIpcHandle', () => {
|
|
158
|
+
expect(typeof populateElectronHostIpcHandle).toBe('function');
|
|
178
159
|
});
|
|
179
160
|
});
|
|
180
161
|
|
|
181
|
-
describe('
|
|
182
|
-
it('is the construction initializer of
|
|
183
|
-
expect(typeof
|
|
162
|
+
describe('populateElectronHostIpcInvoke', () => {
|
|
163
|
+
it('is the construction initializer of electronHostIpcInvoke', () => {
|
|
164
|
+
expect(typeof populateElectronHostIpcInvoke).toBe('function');
|
|
184
165
|
});
|
|
185
166
|
});
|
|
186
167
|
|
|
187
|
-
describe('
|
|
188
|
-
it('is the construction initializer of
|
|
189
|
-
expect(typeof
|
|
168
|
+
describe('populateElectronHostIpcMessage', () => {
|
|
169
|
+
it('is the construction initializer of electronHostIpcMessage', () => {
|
|
170
|
+
expect(typeof populateElectronHostIpcMessage).toBe('function');
|
|
190
171
|
});
|
|
191
172
|
});
|
|
192
173
|
|
|
193
|
-
describe('
|
|
194
|
-
it('is the construction initializer of
|
|
195
|
-
expect(typeof
|
|
174
|
+
describe('populateElectronHostIpcSend', () => {
|
|
175
|
+
it('is the construction initializer of electronHostIpcSend', () => {
|
|
176
|
+
expect(typeof populateElectronHostIpcSend).toBe('function');
|
|
196
177
|
});
|
|
197
178
|
});
|
|
198
179
|
|
|
199
|
-
describe('
|
|
200
|
-
it('is the construction initializer of
|
|
201
|
-
expect(typeof
|
|
180
|
+
describe('populateElectronHostIpcTargetedSend', () => {
|
|
181
|
+
it('is the construction initializer of electronHostIpcTargetedSend', () => {
|
|
182
|
+
expect(typeof populateElectronHostIpcTargetedSend).toBe('function');
|
|
202
183
|
});
|
|
203
184
|
});
|