diff options
Diffstat (limited to 'packages/main/src/infrastructure/electron/impl/__tests__/lockWebContentsToFile.test.ts')
-rw-r--r-- | packages/main/src/infrastructure/electron/impl/__tests__/lockWebContentsToFile.test.ts | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/packages/main/src/infrastructure/electron/impl/__tests__/lockWebContentsToFile.test.ts b/packages/main/src/infrastructure/electron/impl/__tests__/lockWebContentsToFile.test.ts new file mode 100644 index 0000000..6332db7 --- /dev/null +++ b/packages/main/src/infrastructure/electron/impl/__tests__/lockWebContentsToFile.test.ts | |||
@@ -0,0 +1,173 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import { URL } from 'node:url'; | ||
22 | |||
23 | import { jest } from '@jest/globals'; | ||
24 | import { fake } from '@sophie/test-utils'; | ||
25 | import type { Event, HandlerDetails, WebContents } from 'electron'; | ||
26 | import { mocked } from 'jest-mock'; | ||
27 | |||
28 | import type Resources from '../../../resources/Resources.js'; | ||
29 | import lockWebContentsToFile from '../lockWebContentsToFile.js'; | ||
30 | |||
31 | type WillNavigateHandler = (event: Event, url: string) => void; | ||
32 | |||
33 | const filePrefix = | ||
34 | 'file:///opt/sophie/resources/app.asar/packages/renderer/dist/'; | ||
35 | |||
36 | function createFakeResources(prefix: string): Resources { | ||
37 | return fake<Resources>({ | ||
38 | getRendererURL(path: string) { | ||
39 | return new URL(path, prefix).toString(); | ||
40 | }, | ||
41 | }); | ||
42 | } | ||
43 | |||
44 | function createAbortedError(): Error { | ||
45 | const error = new Error('Aborted error'); | ||
46 | Object.assign(error, { | ||
47 | errno: -3, | ||
48 | code: 'ERR_ABORTED', | ||
49 | }); | ||
50 | return error; | ||
51 | } | ||
52 | |||
53 | describe('when loadURL does not throw', () => { | ||
54 | let willNavigate: WillNavigateHandler | undefined; | ||
55 | |||
56 | let windowOpenHandler: | ||
57 | | ((details: HandlerDetails) => { action: 'allow' | 'deny' }) | ||
58 | | undefined; | ||
59 | |||
60 | const urlToLoad = `${filePrefix}index.html`; | ||
61 | |||
62 | const fakeResources = createFakeResources(filePrefix); | ||
63 | |||
64 | const fakeWebContents = fake<WebContents>({ | ||
65 | setWindowOpenHandler(handler) { | ||
66 | windowOpenHandler = handler; | ||
67 | }, | ||
68 | on(event, listener) { | ||
69 | if (event === 'will-navigate') { | ||
70 | willNavigate = listener as WillNavigateHandler; | ||
71 | } | ||
72 | return this as WebContents; | ||
73 | }, | ||
74 | loadURL: jest.fn<WebContents['loadURL']>(), | ||
75 | }); | ||
76 | |||
77 | const event: Event = { | ||
78 | preventDefault: jest.fn(), | ||
79 | }; | ||
80 | |||
81 | beforeEach(async () => { | ||
82 | windowOpenHandler = undefined; | ||
83 | willNavigate = undefined; | ||
84 | mocked(fakeWebContents.loadURL).mockResolvedValueOnce(); | ||
85 | await lockWebContentsToFile(fakeResources, 'index.html', fakeWebContents); | ||
86 | }); | ||
87 | |||
88 | test('loads the specified file', () => { | ||
89 | expect(fakeWebContents.loadURL).toHaveBeenCalledWith(urlToLoad); | ||
90 | }); | ||
91 | |||
92 | test('sets up will navigate and window open listeners', () => { | ||
93 | expect(willNavigate).toBeDefined(); | ||
94 | expect(windowOpenHandler).toBeDefined(); | ||
95 | }); | ||
96 | |||
97 | test('prevents opening a window', () => { | ||
98 | const { action } = windowOpenHandler!({ | ||
99 | url: 'https://example.com', | ||
100 | frameName: 'newWindow', | ||
101 | features: '', | ||
102 | disposition: 'default', | ||
103 | referrer: { | ||
104 | url: urlToLoad, | ||
105 | policy: 'default', | ||
106 | }, | ||
107 | }); | ||
108 | expect(action).toBe('deny'); | ||
109 | }); | ||
110 | |||
111 | test('allows navigation to the loaded URL', () => { | ||
112 | willNavigate!(event, urlToLoad); | ||
113 | expect(event.preventDefault).not.toHaveBeenCalled(); | ||
114 | }); | ||
115 | |||
116 | test('does not allow navigation to another URL', () => { | ||
117 | willNavigate!( | ||
118 | event, | ||
119 | 'file:///opt/sophie/resources/app.asar/packages/renderer/not-allowed.html', | ||
120 | ); | ||
121 | expect(event.preventDefault).toHaveBeenCalled(); | ||
122 | }); | ||
123 | }); | ||
124 | |||
125 | describe('when loadURL throws', () => { | ||
126 | const fakeWebContents = fake<WebContents>({ | ||
127 | setWindowOpenHandler: jest.fn<WebContents['setWindowOpenHandler']>(), | ||
128 | on: jest.fn<() => WebContents>(), | ||
129 | loadURL: jest.fn<WebContents['loadURL']>(), | ||
130 | }); | ||
131 | |||
132 | describe('when the URL points at a file', () => { | ||
133 | const fakeResources = createFakeResources('http://localhost:3000'); | ||
134 | |||
135 | test('swallows ERR_ABORTED errors', async () => { | ||
136 | const error = createAbortedError(); | ||
137 | mocked(fakeWebContents.loadURL).mockRejectedValueOnce(error); | ||
138 | await expect( | ||
139 | lockWebContentsToFile(fakeResources, 'index.html', fakeWebContents), | ||
140 | ).resolves.not.toThrow(); | ||
141 | }); | ||
142 | |||
143 | test('passes through other errors', async () => { | ||
144 | mocked(fakeWebContents.loadURL).mockRejectedValueOnce( | ||
145 | new Error('other error'), | ||
146 | ); | ||
147 | await expect( | ||
148 | lockWebContentsToFile(fakeResources, 'index.html', fakeWebContents), | ||
149 | ).rejects.toBeInstanceOf(Error); | ||
150 | }); | ||
151 | }); | ||
152 | |||
153 | describe('when the URL points at a local server', () => { | ||
154 | const fakeResources = createFakeResources(filePrefix); | ||
155 | |||
156 | test('passes through ERR_ABORTED errors', async () => { | ||
157 | const error = createAbortedError(); | ||
158 | mocked(fakeWebContents.loadURL).mockRejectedValueOnce(error); | ||
159 | await expect( | ||
160 | lockWebContentsToFile(fakeResources, 'index.html', fakeWebContents), | ||
161 | ).rejects.toBeInstanceOf(Error); | ||
162 | }); | ||
163 | |||
164 | test('passes through other errors', async () => { | ||
165 | mocked(fakeWebContents.loadURL).mockRejectedValueOnce( | ||
166 | new Error('other error'), | ||
167 | ); | ||
168 | await expect( | ||
169 | lockWebContentsToFile(fakeResources, 'index.html', fakeWebContents), | ||
170 | ).rejects.toBeInstanceOf(Error); | ||
171 | }); | ||
172 | }); | ||
173 | }); | ||