@openmrs/esm-dynamic-loading 9.0.3-pre.4533 → 9.0.3-pre.4537
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/.turbo/turbo-build.log +1 -1
- package/dist/dynamic-loading.d.ts.map +1 -1
- package/dist/dynamic-loading.js +4 -4
- package/dist/import-maps.d.ts +50 -0
- package/dist/import-maps.d.ts.map +1 -0
- package/dist/import-maps.js +246 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/route-maps.d.ts +46 -0
- package/dist/route-maps.d.ts.map +1 -0
- package/dist/route-maps.js +252 -0
- package/package.json +7 -4
- package/src/dynamic-loading.test.ts +426 -0
- package/src/dynamic-loading.ts +4 -4
- package/src/import-maps.test.ts +363 -0
- package/src/import-maps.ts +274 -0
- package/src/index.ts +2 -0
- package/src/route-maps.test.ts +481 -0
- package/src/route-maps.ts +285 -0
- package/vitest.config.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-dynamic-loading",
|
|
3
|
-
"version": "9.0.3-pre.
|
|
3
|
+
"version": "9.0.3-pre.4537",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Utilities for dynamically loading code in OpenMRS",
|
|
6
6
|
"type": "module",
|
|
@@ -57,14 +57,17 @@
|
|
|
57
57
|
"@openmrs/esm-translations": "9.x"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@openmrs/esm-globals": "9.0.3-pre.
|
|
61
|
-
"@openmrs/esm-translations": "9.0.3-pre.
|
|
60
|
+
"@openmrs/esm-globals": "9.0.3-pre.4537",
|
|
61
|
+
"@openmrs/esm-translations": "9.0.3-pre.4537",
|
|
62
62
|
"@swc/cli": "0.8.1",
|
|
63
63
|
"@swc/core": "1.15.21",
|
|
64
64
|
"@vitest/coverage-v8": "^4.1.2",
|
|
65
65
|
"concurrently": "^9.2.1",
|
|
66
|
+
"happy-dom": "^20.6.0",
|
|
67
|
+
"jsdom": "^29.0.2",
|
|
66
68
|
"rimraf": "^6.1.3",
|
|
67
|
-
"vitest": "^4.1.2"
|
|
69
|
+
"vitest": "^4.1.2",
|
|
70
|
+
"vitest-fetch-mock": "^0.4.5"
|
|
68
71
|
},
|
|
69
72
|
"stableVersion": "9.0.2"
|
|
70
73
|
}
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
mockGetCurrentPageMap,
|
|
5
|
+
mockGetImportMapOverrideMap,
|
|
6
|
+
mockResetImportMapOverrides,
|
|
7
|
+
mockDispatchToastShown,
|
|
8
|
+
mockGetCoreTranslation,
|
|
9
|
+
} = vi.hoisted(() => ({
|
|
10
|
+
mockGetCurrentPageMap: vi.fn(),
|
|
11
|
+
mockGetImportMapOverrideMap: vi.fn().mockReturnValue({ imports: {} }),
|
|
12
|
+
mockResetImportMapOverrides: vi.fn(),
|
|
13
|
+
mockDispatchToastShown: vi.fn(),
|
|
14
|
+
mockGetCoreTranslation: vi.fn((_key: string, fallback: string) => fallback),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock('./import-maps', () => ({
|
|
18
|
+
getCurrentPageMap: mockGetCurrentPageMap,
|
|
19
|
+
getImportMapOverrideMap: mockGetImportMapOverrideMap,
|
|
20
|
+
resetImportMapOverrides: mockResetImportMapOverrides,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock('@openmrs/esm-globals', () => ({
|
|
24
|
+
dispatchToastShown: mockDispatchToastShown,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('@openmrs/esm-translations', () => ({
|
|
28
|
+
getCoreTranslation: mockGetCoreTranslation,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
import { importDynamic, preloadImport, slugify } from './dynamic-loading';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Flushes pending microtasks so that async code that has been awaiting
|
|
35
|
+
* a resolved promise can continue executing.
|
|
36
|
+
*/
|
|
37
|
+
async function flushMicrotasks() {
|
|
38
|
+
for (let i = 0; i < 10; i++) {
|
|
39
|
+
await Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Waits until a `<script>` element with the given `src` appears in
|
|
45
|
+
* `document.head`, then returns it. Flushes microtasks repeatedly
|
|
46
|
+
* to allow async production code to run.
|
|
47
|
+
*/
|
|
48
|
+
async function waitForScript(src: string): Promise<HTMLScriptElement> {
|
|
49
|
+
for (let i = 0; i < 20; i++) {
|
|
50
|
+
await Promise.resolve();
|
|
51
|
+
const el = document.head.querySelector(`script[src="${src}"]`) as HTMLScriptElement | null;
|
|
52
|
+
if (el) return el;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Script with src="${src}" was not appended to <head>`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('dynamic-loading', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
localStorage.clear();
|
|
60
|
+
document.head.querySelectorAll('script').forEach((el) => el.remove());
|
|
61
|
+
(globalThis as any).__webpack_share_scopes__ = { default: {} };
|
|
62
|
+
(window as any).spaBase = '/openmrs/spa';
|
|
63
|
+
mockGetImportMapOverrideMap.mockReturnValue({ imports: {} });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.restoreAllMocks();
|
|
68
|
+
delete (globalThis as any).__webpack_share_scopes__;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('slugify', () => {
|
|
72
|
+
it('replaces slashes with underscores', () => {
|
|
73
|
+
expect(slugify('a/b/c')).toBe('a_b_c');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('replaces hyphens with underscores', () => {
|
|
77
|
+
expect(slugify('esm-foo-bar')).toBe('esm_foo_bar');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('replaces @ with underscores', () => {
|
|
81
|
+
expect(slugify('@openmrs/esm-foo')).toBe('_openmrs_esm_foo');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('handles a typical module name', () => {
|
|
85
|
+
expect(slugify('@openmrs/esm-patient-chart-app')).toBe('_openmrs_esm_patient_chart_app');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns the input unchanged when there are no special characters', () => {
|
|
89
|
+
expect(slugify('simple')).toBe('simple');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('preloadImport', () => {
|
|
94
|
+
it('throws when called with an empty string', async () => {
|
|
95
|
+
await expect(preloadImport('')).rejects.toThrow('without supplying a package');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('throws when called with a whitespace-only string', async () => {
|
|
99
|
+
await expect(preloadImport(' ')).rejects.toThrow('without supplying a package');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('throws when the package is not in the import map', async () => {
|
|
103
|
+
mockGetCurrentPageMap.mockResolvedValue({ imports: {} });
|
|
104
|
+
await expect(preloadImport('@openmrs/esm-missing')).rejects.toThrow(
|
|
105
|
+
'Could not find the package @openmrs/esm-missing',
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('resolves immediately if the package is already loaded on window', async () => {
|
|
110
|
+
const slug = '_openmrs_esm_foo';
|
|
111
|
+
(window as any)[slug] = { init: vi.fn(), get: vi.fn() };
|
|
112
|
+
|
|
113
|
+
await expect(preloadImport('@openmrs/esm-foo')).resolves.toBeUndefined();
|
|
114
|
+
|
|
115
|
+
delete (window as any)[slug];
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('creates a script element and resolves on load', async () => {
|
|
119
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
120
|
+
imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' },
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const promise = preloadImport('@openmrs/esm-foo');
|
|
124
|
+
const script = await waitForScript('http://localhost/foo.js');
|
|
125
|
+
|
|
126
|
+
expect(script.type).toBe('text/javascript');
|
|
127
|
+
expect(script.async).toBe(true);
|
|
128
|
+
|
|
129
|
+
script.dispatchEvent(new Event('load'));
|
|
130
|
+
|
|
131
|
+
await expect(promise).resolves.toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('rejects when the script fails to load', async () => {
|
|
135
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
136
|
+
imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const promise = preloadImport('@openmrs/esm-foo');
|
|
140
|
+
const script = await waitForScript('http://localhost/foo.js');
|
|
141
|
+
|
|
142
|
+
script.dispatchEvent(new ErrorEvent('error', { message: 'net::ERR_CONNECTION_REFUSED' }));
|
|
143
|
+
|
|
144
|
+
await expect(promise).rejects.toBe('net::ERR_CONNECTION_REFUSED');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('shows a toast when an overridden script fails to load', async () => {
|
|
148
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
149
|
+
imports: { '@openmrs/esm-foo': 'http://localhost:8080/foo.js' },
|
|
150
|
+
});
|
|
151
|
+
mockGetImportMapOverrideMap.mockReturnValue({
|
|
152
|
+
imports: { '@openmrs/esm-foo': 'http://localhost:8080/foo.js' },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const promise = preloadImport('@openmrs/esm-foo');
|
|
156
|
+
const script = await waitForScript('http://localhost:8080/foo.js');
|
|
157
|
+
|
|
158
|
+
script.dispatchEvent(new ErrorEvent('error', { message: 'net::ERR_CONNECTION_REFUSED' }));
|
|
159
|
+
|
|
160
|
+
await expect(promise).rejects.toBe('net::ERR_CONNECTION_REFUSED');
|
|
161
|
+
expect(mockDispatchToastShown).toHaveBeenCalledWith(
|
|
162
|
+
expect.objectContaining({
|
|
163
|
+
kind: 'error',
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('calls resetImportMapOverrides when the toast action button is clicked', async () => {
|
|
169
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
170
|
+
imports: { '@openmrs/esm-foo': 'http://localhost:8080/foo.js' },
|
|
171
|
+
});
|
|
172
|
+
mockGetImportMapOverrideMap.mockReturnValue({
|
|
173
|
+
imports: { '@openmrs/esm-foo': 'http://localhost:8080/foo.js' },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const reloadMock = vi.fn();
|
|
177
|
+
Object.defineProperty(window, 'location', {
|
|
178
|
+
value: { ...window.location, reload: reloadMock },
|
|
179
|
+
writable: true,
|
|
180
|
+
configurable: true,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const promise = preloadImport('@openmrs/esm-foo');
|
|
184
|
+
const script = await waitForScript('http://localhost:8080/foo.js');
|
|
185
|
+
|
|
186
|
+
script.dispatchEvent(new ErrorEvent('error', { message: 'fail' }));
|
|
187
|
+
await promise.catch(() => {});
|
|
188
|
+
|
|
189
|
+
const toastArg = mockDispatchToastShown.mock.calls[0][0];
|
|
190
|
+
toastArg.onActionButtonClick();
|
|
191
|
+
|
|
192
|
+
expect(mockResetImportMapOverrides).toHaveBeenCalled();
|
|
193
|
+
expect(reloadMock).toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('uses the provided import map instead of fetching one', async () => {
|
|
197
|
+
const importMap = { imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' } };
|
|
198
|
+
|
|
199
|
+
const promise = preloadImport('@openmrs/esm-foo', importMap);
|
|
200
|
+
const script = await waitForScript('http://localhost/foo.js');
|
|
201
|
+
|
|
202
|
+
script.dispatchEvent(new Event('load'));
|
|
203
|
+
|
|
204
|
+
await expect(promise).resolves.toBeNull();
|
|
205
|
+
expect(mockGetCurrentPageMap).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('prepends spaBase to relative URLs starting with ./', async () => {
|
|
209
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
210
|
+
imports: { '@openmrs/esm-foo': './foo.js' },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const promise = preloadImport('@openmrs/esm-foo');
|
|
214
|
+
const script = await waitForScript('/openmrs/spa/foo.js');
|
|
215
|
+
|
|
216
|
+
expect(script).not.toBeNull();
|
|
217
|
+
script.dispatchEvent(new Event('load'));
|
|
218
|
+
|
|
219
|
+
await expect(promise).resolves.toBeNull();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('does not reload a script that is already in the DOM and finished loading', async () => {
|
|
223
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
224
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
225
|
+
imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// First load
|
|
229
|
+
const firstPromise = preloadImport('@openmrs/esm-foo');
|
|
230
|
+
const script = await waitForScript('http://localhost/foo.js');
|
|
231
|
+
script.dispatchEvent(new Event('load'));
|
|
232
|
+
await firstPromise;
|
|
233
|
+
|
|
234
|
+
// Second load — script is in DOM but slug not on window, so it resolves with a warning
|
|
235
|
+
await expect(preloadImport('@openmrs/esm-foo')).resolves.toBeNull();
|
|
236
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('already loaded'));
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('resolves both callers when the same script is preloaded concurrently', async () => {
|
|
240
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
241
|
+
imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Two concurrent preloads for the same package
|
|
245
|
+
const first = preloadImport('@openmrs/esm-foo');
|
|
246
|
+
const second = preloadImport('@openmrs/esm-foo');
|
|
247
|
+
|
|
248
|
+
const script = await waitForScript('http://localhost/foo.js');
|
|
249
|
+
script.dispatchEvent(new Event('load'));
|
|
250
|
+
|
|
251
|
+
await expect(first).resolves.toBeNull();
|
|
252
|
+
await expect(second).resolves.toBeNull();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('rejects both callers when a concurrently-loaded script fails', async () => {
|
|
256
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
257
|
+
imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' },
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const first = preloadImport('@openmrs/esm-foo');
|
|
261
|
+
const second = preloadImport('@openmrs/esm-foo');
|
|
262
|
+
|
|
263
|
+
const script = await waitForScript('http://localhost/foo.js');
|
|
264
|
+
script.dispatchEvent(new ErrorEvent('error', { message: 'net::ERR_FAILED' }));
|
|
265
|
+
|
|
266
|
+
await expect(first).rejects.toBe('net::ERR_FAILED');
|
|
267
|
+
await expect(second).rejects.toBe('net::ERR_FAILED');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('logs an error when a script takes longer than 5 seconds to load', async () => {
|
|
271
|
+
vi.useFakeTimers();
|
|
272
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
273
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
274
|
+
imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' },
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
preloadImport('@openmrs/esm-foo');
|
|
278
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
279
|
+
|
|
280
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
281
|
+
|
|
282
|
+
vi.advanceTimersByTime(5_000);
|
|
283
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('did not load within 5 seconds'));
|
|
284
|
+
|
|
285
|
+
vi.useRealTimers();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('rejects with an empty string when the error event has no message', async () => {
|
|
289
|
+
mockGetCurrentPageMap.mockResolvedValue({
|
|
290
|
+
imports: { '@openmrs/esm-foo': 'http://localhost/foo.js' },
|
|
291
|
+
});
|
|
292
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
293
|
+
|
|
294
|
+
const promise = preloadImport('@openmrs/esm-foo');
|
|
295
|
+
const script = await waitForScript('http://localhost/foo.js');
|
|
296
|
+
|
|
297
|
+
script.dispatchEvent(new ErrorEvent('error'));
|
|
298
|
+
|
|
299
|
+
// ErrorEvent.message defaults to '' which is not nullish, so ?? doesn't trigger
|
|
300
|
+
await expect(promise).rejects.toBe('');
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('importDynamic', () => {
|
|
305
|
+
function setupFederatedModule(slug: string, moduleExports: Record<string, unknown>) {
|
|
306
|
+
(window as any)[slug] = {
|
|
307
|
+
init: vi.fn(),
|
|
308
|
+
get: vi.fn().mockResolvedValue(() => moduleExports),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
afterEach(() => {
|
|
313
|
+
delete (window as any)['_openmrs_esm_foo'];
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('returns the module exports from a federated module', async () => {
|
|
317
|
+
const moduleExports = { default: 'hello', namedExport: 42 };
|
|
318
|
+
setupFederatedModule('_openmrs_esm_foo', moduleExports);
|
|
319
|
+
|
|
320
|
+
const result = await importDynamic('@openmrs/esm-foo');
|
|
321
|
+
expect(result).toEqual(moduleExports);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('calls container.init with the default webpack share scope', async () => {
|
|
325
|
+
const initFn = vi.fn();
|
|
326
|
+
(window as any)['_openmrs_esm_foo'] = {
|
|
327
|
+
init: initFn,
|
|
328
|
+
get: vi.fn().mockResolvedValue(() => ({ default: true })),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
await importDynamic('@openmrs/esm-foo');
|
|
332
|
+
expect(initFn).toHaveBeenCalledWith(__webpack_share_scopes__.default);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('calls container.get with the specified share', async () => {
|
|
336
|
+
const getFn = vi.fn().mockResolvedValue(() => ({ default: true }));
|
|
337
|
+
(window as any)['_openmrs_esm_foo'] = { init: vi.fn(), get: getFn };
|
|
338
|
+
|
|
339
|
+
await importDynamic('@openmrs/esm-foo', './custom-share');
|
|
340
|
+
expect(getFn).toHaveBeenCalledWith('./custom-share');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('uses ./start as the default share', async () => {
|
|
344
|
+
const getFn = vi.fn().mockResolvedValue(() => ({ default: true }));
|
|
345
|
+
(window as any)['_openmrs_esm_foo'] = { init: vi.fn(), get: getFn };
|
|
346
|
+
|
|
347
|
+
await importDynamic('@openmrs/esm-foo');
|
|
348
|
+
expect(getFn).toHaveBeenCalledWith('./start');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('throws when the global is not a federated module', async () => {
|
|
352
|
+
(window as any)['_openmrs_esm_foo'] = 'not a module';
|
|
353
|
+
|
|
354
|
+
await expect(importDynamic('@openmrs/esm-foo')).rejects.toThrow('does not refer to a federated module');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('throws when the factory returns null', async () => {
|
|
358
|
+
(window as any)['_openmrs_esm_foo'] = {
|
|
359
|
+
init: vi.fn(),
|
|
360
|
+
get: vi.fn().mockResolvedValue(() => null),
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
await expect(importDynamic('@openmrs/esm-foo')).rejects.toThrow('did not return an ESM module');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('throws when the factory returns a string', async () => {
|
|
367
|
+
(window as any)['_openmrs_esm_foo'] = {
|
|
368
|
+
init: vi.fn(),
|
|
369
|
+
get: vi.fn().mockResolvedValue(() => 'not a module'),
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
await expect(importDynamic('@openmrs/esm-foo')).rejects.toThrow('did not return an ESM module');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('rejects if preloading exceeds maxLoadingTime', async () => {
|
|
376
|
+
vi.useFakeTimers();
|
|
377
|
+
mockGetCurrentPageMap.mockReturnValue(new Promise(() => {}));
|
|
378
|
+
|
|
379
|
+
const promise = importDynamic('@openmrs/esm-foo', './start', { maxLoadingTime: 100 });
|
|
380
|
+
// Attach a no-op handler so the rejection is tracked before the timer fires
|
|
381
|
+
promise.catch(() => {});
|
|
382
|
+
|
|
383
|
+
vi.advanceTimersByTime(100);
|
|
384
|
+
await flushMicrotasks();
|
|
385
|
+
|
|
386
|
+
await expect(promise).rejects.toThrow('Could not resolve requested script');
|
|
387
|
+
|
|
388
|
+
vi.useRealTimers();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('defaults maxLoadingTime to 10 minutes', async () => {
|
|
392
|
+
vi.useFakeTimers();
|
|
393
|
+
mockGetCurrentPageMap.mockReturnValue(new Promise(() => {}));
|
|
394
|
+
|
|
395
|
+
const promise = importDynamic('@openmrs/esm-foo');
|
|
396
|
+
promise.catch(() => {});
|
|
397
|
+
|
|
398
|
+
// Just under 10 minutes — should not have rejected yet
|
|
399
|
+
vi.advanceTimersByTime(599_999);
|
|
400
|
+
await flushMicrotasks();
|
|
401
|
+
|
|
402
|
+
// Advance the remaining 1ms
|
|
403
|
+
vi.advanceTimersByTime(1);
|
|
404
|
+
await flushMicrotasks();
|
|
405
|
+
|
|
406
|
+
await expect(promise).rejects.toThrow('10 minutes');
|
|
407
|
+
|
|
408
|
+
vi.useRealTimers();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('treats non-positive maxLoadingTime as the default', async () => {
|
|
412
|
+
vi.useFakeTimers();
|
|
413
|
+
mockGetCurrentPageMap.mockReturnValue(new Promise(() => {}));
|
|
414
|
+
|
|
415
|
+
const promise = importDynamic('@openmrs/esm-foo', './start', { maxLoadingTime: -1 });
|
|
416
|
+
promise.catch(() => {});
|
|
417
|
+
|
|
418
|
+
vi.advanceTimersByTime(600_000);
|
|
419
|
+
await flushMicrotasks();
|
|
420
|
+
|
|
421
|
+
await expect(promise).rejects.toThrow('10 minutes');
|
|
422
|
+
|
|
423
|
+
vi.useRealTimers();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
});
|
package/src/dynamic-loading.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** @module @category Dynamic Loading */
|
|
2
2
|
'use strict';
|
|
3
|
-
// hack to make the types defined in esm-globals available here
|
|
4
3
|
import { dispatchToastShown, type ImportMap } from '@openmrs/esm-globals';
|
|
5
4
|
import { getCoreTranslation } from '@openmrs/esm-translations';
|
|
5
|
+
import { getCurrentPageMap, getImportMapOverrideMap, resetImportMapOverrides } from './import-maps';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @internal
|
|
@@ -138,7 +138,7 @@ export async function preloadImport(jsPackage: string, importMap?: ImportMap) {
|
|
|
138
138
|
url = window.spaBase + url.substring(1);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
const isOverridden =
|
|
141
|
+
const isOverridden = jsPackage in getImportMapOverrideMap().imports;
|
|
142
142
|
try {
|
|
143
143
|
return await new Promise<void>((resolve, reject) => {
|
|
144
144
|
loadScript(url, resolve, reject);
|
|
@@ -157,7 +157,7 @@ export async function preloadImport(jsPackage: string, importMap?: ImportMap) {
|
|
|
157
157
|
),
|
|
158
158
|
actionButtonLabel: getCoreTranslation('resetOverrides', 'Reset overrides'),
|
|
159
159
|
onActionButtonClick() {
|
|
160
|
-
|
|
160
|
+
resetImportMapOverrides();
|
|
161
161
|
window.location.reload();
|
|
162
162
|
},
|
|
163
163
|
});
|
|
@@ -178,7 +178,7 @@ export async function preloadImport(jsPackage: string, importMap?: ImportMap) {
|
|
|
178
178
|
* @returns The current import map.
|
|
179
179
|
*/
|
|
180
180
|
export async function getCurrentImportMap() {
|
|
181
|
-
return
|
|
181
|
+
return getCurrentPageMap();
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
interface FederatedModule {
|