aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/infrastructure/electron/impl/__tests__/lockWebContentsToFile.test.ts
diff options
context:
space:
mode:
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.ts173
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
21import { URL } from 'node:url';
22
23import { jest } from '@jest/globals';
24import { fake } from '@sophie/test-utils';
25import type { Event, HandlerDetails, WebContents } from 'electron';
26import { mocked } from 'jest-mock';
27
28import type Resources from '../../../resources/Resources.js';
29import lockWebContentsToFile from '../lockWebContentsToFile.js';
30
31type WillNavigateHandler = (event: Event, url: string) => void;
32
33const filePrefix =
34 'file:///opt/sophie/resources/app.asar/packages/renderer/dist/';
35
36function createFakeResources(prefix: string): Resources {
37 return fake<Resources>({
38 getRendererURL(path: string) {
39 return new URL(path, prefix).toString();
40 },
41 });
42}
43
44function 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
53describe('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
125describe('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});