slicejs-web-framework 3.2.3 → 3.3.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 (117) hide show
  1. package/.opencode/opencode.json +13 -13
  2. package/LICENSE +21 -21
  3. package/README.md +97 -174
  4. package/Slice/Components/Structural/ContextManager/ContextManager.js +369 -369
  5. package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +420 -297
  6. package/Slice/Components/Structural/Controller/Controller.js +1131 -1131
  7. package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
  8. package/Slice/Components/Structural/Debugger/Debugger.js +1497 -1547
  9. package/Slice/Components/Structural/EventManager/EventManager.js +338 -338
  10. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +476 -361
  11. package/Slice/Components/Structural/Logger/Log.js +10 -10
  12. package/Slice/Components/Structural/Logger/Logger.js +146 -146
  13. package/Slice/Components/Structural/Router/Router.js +752 -721
  14. package/Slice/Components/Structural/StylesManager/StylesManager.js +78 -78
  15. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +84 -84
  16. package/Slice/Slice.js +542 -542
  17. package/Slice/tests/build-bundled-component-without-category.test.js +103 -103
  18. package/Slice/tests/build-js-only-visual-components.test.js +144 -144
  19. package/Slice/tests/bundle-v2-runtime-contract.test.js +728 -728
  20. package/Slice/tests/public-env-runtime-accessors.test.js +44 -44
  21. package/Slice/tests/router-loading-finally.test.js +68 -68
  22. package/api/index.js +286 -286
  23. package/api/middleware/securityMiddleware.js +252 -252
  24. package/api/tests/public-env-resolver.test.js +193 -193
  25. package/api/utils/publicEnvResolver.js +117 -117
  26. package/package.json +40 -38
  27. package/sliceConfig.schema.json +109 -109
  28. package/src/App/index.html +16 -22
  29. package/src/App/index.js +20 -23
  30. package/src/App/style.css +11 -40
  31. package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -0
  32. package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -0
  33. package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -0
  34. package/src/Components/AppComponents/AppShell/AppShell.css +10 -0
  35. package/src/Components/AppComponents/AppShell/AppShell.html +4 -0
  36. package/src/Components/AppComponents/AppShell/AppShell.js +36 -0
  37. package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -0
  38. package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -0
  39. package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -0
  40. package/src/Components/Service/FetchManager/FetchManager.js +133 -133
  41. package/src/Components/Service/IndexedDbManager/IndexedDbManager.js +141 -141
  42. package/src/Components/Service/LocalStorageManager/LocalStorageManager.js +45 -45
  43. package/src/Components/Visual/Button/Button.css +47 -47
  44. package/src/Components/Visual/Button/Button.html +5 -5
  45. package/src/Components/Visual/Button/Button.js +92 -92
  46. package/src/Components/Visual/Card/Card.css +68 -68
  47. package/src/Components/Visual/Card/Card.html +7 -7
  48. package/src/Components/Visual/Card/Card.js +107 -107
  49. package/src/Components/Visual/Checkbox/Checkbox.css +87 -87
  50. package/src/Components/Visual/Checkbox/Checkbox.html +8 -8
  51. package/src/Components/Visual/Checkbox/Checkbox.js +86 -86
  52. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.css +129 -129
  53. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.html +3 -3
  54. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.js +262 -262
  55. package/src/Components/Visual/Details/Details.css +70 -70
  56. package/src/Components/Visual/Details/Details.html +9 -9
  57. package/src/Components/Visual/Details/Details.js +76 -76
  58. package/src/Components/Visual/DropDown/DropDown.css +60 -60
  59. package/src/Components/Visual/DropDown/DropDown.html +5 -5
  60. package/src/Components/Visual/DropDown/DropDown.js +63 -63
  61. package/src/Components/Visual/Grid/Grid.css +7 -7
  62. package/src/Components/Visual/Grid/Grid.html +1 -1
  63. package/src/Components/Visual/Grid/Grid.js +57 -57
  64. package/src/Components/Visual/Icon/Icon.css +510 -510
  65. package/src/Components/Visual/Icon/Icon.js +89 -89
  66. package/src/Components/Visual/Icon/slc.json +554 -554
  67. package/src/Components/Visual/Icon/slc.styl +507 -507
  68. package/src/Components/Visual/Icon/slc.svg +1485 -1485
  69. package/src/Components/Visual/Icon/slc.symbol.svg +1058 -1058
  70. package/src/Components/Visual/Input/Input.css +91 -91
  71. package/src/Components/Visual/Input/Input.html +4 -4
  72. package/src/Components/Visual/Input/Input.js +215 -215
  73. package/src/Components/Visual/Layout/Layout.js +49 -49
  74. package/src/Components/Visual/Link/Link.css +8 -8
  75. package/src/Components/Visual/Link/Link.html +1 -1
  76. package/src/Components/Visual/Link/Link.js +63 -63
  77. package/src/Components/Visual/Loading/Loading.css +56 -56
  78. package/src/Components/Visual/Loading/Loading.html +83 -83
  79. package/src/Components/Visual/Loading/Loading.js +38 -38
  80. package/src/Components/Visual/MultiRoute/MultiRoute.js +100 -93
  81. package/src/Components/Visual/Navbar/Navbar.css +115 -115
  82. package/src/Components/Visual/Navbar/Navbar.html +44 -44
  83. package/src/Components/Visual/Navbar/Navbar.js +141 -141
  84. package/src/Components/Visual/NotFound/NotFound.css +116 -116
  85. package/src/Components/Visual/NotFound/NotFound.html +23 -23
  86. package/src/Components/Visual/NotFound/NotFound.js +16 -16
  87. package/src/Components/Visual/Route/Route.js +93 -93
  88. package/src/Components/Visual/Select/Select.css +84 -84
  89. package/src/Components/Visual/Select/Select.html +8 -8
  90. package/src/Components/Visual/Select/Select.js +195 -195
  91. package/src/Components/Visual/Switch/Switch.css +76 -76
  92. package/src/Components/Visual/Switch/Switch.html +8 -8
  93. package/src/Components/Visual/Switch/Switch.js +102 -102
  94. package/src/Components/Visual/TreeItem/TreeItem.css +36 -36
  95. package/src/Components/Visual/TreeItem/TreeItem.html +1 -1
  96. package/src/Components/Visual/TreeItem/TreeItem.js +126 -126
  97. package/src/Components/Visual/TreeView/TreeView.css +8 -8
  98. package/src/Components/Visual/TreeView/TreeView.html +1 -1
  99. package/src/Components/Visual/TreeView/TreeView.js +48 -48
  100. package/src/Components/components.js +15 -27
  101. package/src/Styles/sliceStyles.css +34 -34
  102. package/src/Themes/Dark.css +42 -42
  103. package/src/Themes/Light.css +31 -31
  104. package/src/Themes/Slice.css +47 -47
  105. package/src/routes.js +9 -15
  106. package/src/sliceConfig.json +74 -73
  107. package/types/index.d.ts +207 -207
  108. package/Slice/Components/Structural/Debugger/Debugger.css +0 -620
  109. package/src/Components/AppComponents/HomePage/HomePage.css +0 -201
  110. package/src/Components/AppComponents/HomePage/HomePage.html +0 -37
  111. package/src/Components/AppComponents/HomePage/HomePage.js +0 -210
  112. package/src/Components/AppComponents/Playground/Playground.css +0 -12
  113. package/src/Components/AppComponents/Playground/Playground.html +0 -0
  114. package/src/Components/AppComponents/Playground/Playground.js +0 -111
  115. package/src/images/Slice.js-logo.png +0 -0
  116. package/src/images/im2/Slice.js-logo.png +0 -0
  117. package/src/testing.js +0 -888
@@ -1,728 +1,728 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtemp, rm, writeFile } from 'node:fs/promises';
4
- import { tmpdir } from 'node:os';
5
- import path from 'node:path';
6
- import { register } from 'node:module';
7
- import { pathToFileURL } from 'node:url';
8
-
9
- test('validateBundleModule rejects module missing Bundling V2 exports contract', async () => {
10
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
11
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
12
- await writeFile(
13
- loaderPath,
14
- `export async function resolve(specifier, context, nextResolve) {
15
- if (specifier === '/Components/components.js') {
16
- return {
17
- shortCircuit: true,
18
- url: 'data:text/javascript,export default {};',
19
- };
20
- }
21
- return nextResolve(specifier, context);
22
- }
23
- `,
24
- 'utf8'
25
- );
26
- register(pathToFileURL(loaderPath).href);
27
-
28
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
29
- const { default: Controller } = await import(controllerModuleUrl);
30
- const controller = new Controller();
31
- const originalWindow = globalThis.window;
32
-
33
- try {
34
- globalThis.window = {
35
- __slicePendingRegistrations: [],
36
- };
37
-
38
- assert.equal(
39
- typeof controller.validateBundleModule,
40
- 'function',
41
- 'Controller must expose validateBundleModule for Bundling V2 runtime contract checks'
42
- );
43
-
44
- if (typeof controller.validateBundleModule === 'function') {
45
- await assert.rejects(
46
- () => Promise.resolve(controller.validateBundleModule({ default: {} }, 'critical.js')),
47
- /missing bundling v2 exports contract/i
48
- );
49
- }
50
- } finally {
51
- globalThis.window = originalWindow;
52
- await rm(tempDir, { recursive: true, force: true });
53
- }
54
- });
55
-
56
- test('loadBundle marks requested bundle key and metadata bundleKey as loaded', async () => {
57
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
58
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
59
- await writeFile(
60
- loaderPath,
61
- `export async function resolve(specifier, context, nextResolve) {
62
- if (specifier === '/Components/components.js') {
63
- return {
64
- shortCircuit: true,
65
- url: 'data:text/javascript,export default {};',
66
- };
67
- }
68
- return nextResolve(specifier, context);
69
- }
70
- `,
71
- 'utf8'
72
- );
73
- register(pathToFileURL(loaderPath).href);
74
-
75
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
76
- const { default: Controller } = await import(controllerModuleUrl);
77
- const controller = new Controller();
78
- const originalSlice = globalThis.slice;
79
-
80
- controller.bundleConfig = {
81
- bundles: {
82
- routes: {
83
- dashboard: {
84
- file: 'dashboard.js',
85
- },
86
- },
87
- },
88
- };
89
-
90
- controller.importBundleOnce = async () => ({});
91
- controller.validateBundleModule = async () => ({
92
- metadata: {
93
- bundleKey: 'routes.dashboard.v2',
94
- type: 'route',
95
- },
96
- registerAll: async () => {},
97
- });
98
-
99
- try {
100
- globalThis.slice = {
101
- stylesManager: {},
102
- };
103
-
104
- await controller.loadBundle('dashboard');
105
-
106
- assert.equal(controller.loadedBundles.has('dashboard'), true);
107
- assert.equal(controller.loadedBundles.has('routes.dashboard.v2'), true);
108
- assert.equal(controller.criticalBundleLoaded, false);
109
- } finally {
110
- globalThis.slice = originalSlice;
111
- await rm(tempDir, { recursive: true, force: true });
112
- }
113
- });
114
-
115
- test('loadBundle resolves dependencies first and registers vendor-shared exports once', async () => {
116
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
117
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
118
- await writeFile(
119
- loaderPath,
120
- `export async function resolve(specifier, context, nextResolve) {
121
- if (specifier === '/Components/components.js') {
122
- return {
123
- shortCircuit: true,
124
- url: 'data:text/javascript,export default {};',
125
- };
126
- }
127
- return nextResolve(specifier, context);
128
- }
129
- `,
130
- 'utf8'
131
- );
132
- register(pathToFileURL(loaderPath).href);
133
-
134
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
135
- const { default: Controller } = await import(controllerModuleUrl);
136
- const controller = new Controller();
137
- const originalSlice = globalThis.slice;
138
- const originalWindow = globalThis.window;
139
-
140
- controller.bundleConfig = {
141
- bundles: {
142
- shared: {
143
- 'vendor-shared': {
144
- file: 'slice-bundle.vendor-shared.js',
145
- },
146
- },
147
- routes: {
148
- dashboard: {
149
- file: 'slice-bundle.dashboard.js',
150
- dependencies: ['vendor-shared'],
151
- },
152
- },
153
- },
154
- };
155
-
156
- const importOrder = [];
157
- const registerOrder = [];
158
- const importsByPath = {
159
- '/bundles/slice-bundle.vendor-shared.js': {
160
- SLICE_BUNDLE_META: {
161
- bundleKey: 'vendor-shared',
162
- type: 'shared',
163
- },
164
- SLICE_SHARED_DEPS: {
165
- 'vendors/dompurify': {
166
- sanitize: () => 'ok',
167
- },
168
- },
169
- registerAll: async () => {
170
- registerOrder.push('vendor-shared');
171
- },
172
- },
173
- '/bundles/slice-bundle.dashboard.js': {
174
- SLICE_BUNDLE_META: {
175
- bundleKey: 'dashboard',
176
- type: 'route',
177
- },
178
- registerAll: async () => {
179
- registerOrder.push('dashboard');
180
- },
181
- },
182
- };
183
-
184
- controller.importBundleOnce = async (bundlePath) => {
185
- importOrder.push(bundlePath);
186
- return importsByPath[bundlePath];
187
- };
188
-
189
- try {
190
- globalThis.slice = {
191
- stylesManager: {},
192
- };
193
- globalThis.window = {
194
- __SLICE_SHARED_DEPS__: {},
195
- };
196
-
197
- await controller.loadBundle('dashboard');
198
- await controller.loadBundle('dashboard');
199
-
200
- assert.deepEqual(importOrder, ['/bundles/slice-bundle.vendor-shared.js', '/bundles/slice-bundle.dashboard.js']);
201
- assert.deepEqual(registerOrder, ['vendor-shared', 'dashboard']);
202
- assert.equal(typeof globalThis.window.__SLICE_SHARED_DEPS__['vendors/dompurify']?.sanitize, 'function');
203
- } finally {
204
- globalThis.slice = originalSlice;
205
- globalThis.window = originalWindow;
206
- await rm(tempDir, { recursive: true, force: true });
207
- }
208
- });
209
-
210
- test('loadBundle resolves vendor-shared dependency from bundles.vendorShared config shape', async () => {
211
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
212
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
213
- await writeFile(
214
- loaderPath,
215
- `export async function resolve(specifier, context, nextResolve) {
216
- if (specifier === '/Components/components.js') {
217
- return {
218
- shortCircuit: true,
219
- url: 'data:text/javascript,export default {};',
220
- };
221
- }
222
- return nextResolve(specifier, context);
223
- }
224
- `,
225
- 'utf8'
226
- );
227
- register(pathToFileURL(loaderPath).href);
228
-
229
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
230
- const { default: Controller } = await import(controllerModuleUrl);
231
- const controller = new Controller();
232
- const originalSlice = globalThis.slice;
233
- const originalWindow = globalThis.window;
234
-
235
- controller.bundleConfig = {
236
- bundles: {
237
- vendorShared: {
238
- file: 'slice-bundle.vendor-shared.js',
239
- },
240
- routes: {
241
- dashboard: {
242
- file: 'slice-bundle.dashboard.js',
243
- dependencies: ['vendor-shared'],
244
- },
245
- },
246
- },
247
- };
248
-
249
- const importOrder = [];
250
- const registerOrder = [];
251
- const importsByPath = {
252
- '/bundles/slice-bundle.vendor-shared.js': {
253
- SLICE_BUNDLE_META: {
254
- bundleKey: 'vendor-shared',
255
- type: 'shared',
256
- },
257
- SLICE_SHARED_DEPS: {
258
- 'vendors/dompurify': {
259
- sanitize: () => 'ok',
260
- },
261
- },
262
- registerAll: async () => {
263
- registerOrder.push('vendor-shared');
264
- },
265
- },
266
- '/bundles/slice-bundle.dashboard.js': {
267
- SLICE_BUNDLE_META: {
268
- bundleKey: 'dashboard',
269
- type: 'route',
270
- },
271
- registerAll: async () => {
272
- registerOrder.push('dashboard');
273
- },
274
- },
275
- };
276
-
277
- controller.importBundleOnce = async (bundlePath) => {
278
- importOrder.push(bundlePath);
279
- return importsByPath[bundlePath];
280
- };
281
-
282
- try {
283
- globalThis.slice = {
284
- stylesManager: {},
285
- };
286
- globalThis.window = {
287
- __SLICE_SHARED_DEPS__: {},
288
- };
289
-
290
- await controller.loadBundle('dashboard');
291
-
292
- assert.deepEqual(importOrder, ['/bundles/slice-bundle.vendor-shared.js', '/bundles/slice-bundle.dashboard.js']);
293
- assert.deepEqual(registerOrder, ['vendor-shared', 'dashboard']);
294
- assert.equal(typeof globalThis.window.__SLICE_SHARED_DEPS__['vendors/dompurify']?.sanitize, 'function');
295
- } finally {
296
- globalThis.slice = originalSlice;
297
- globalThis.window = originalWindow;
298
- await rm(tempDir, { recursive: true, force: true });
299
- }
300
- });
301
-
302
- test('loadBundle registers vendor-shared dependencies from registerAll return when SLICE_SHARED_DEPS export is absent', async () => {
303
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
304
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
305
- await writeFile(
306
- loaderPath,
307
- `export async function resolve(specifier, context, nextResolve) {
308
- if (specifier === '/Components/components.js') {
309
- return {
310
- shortCircuit: true,
311
- url: 'data:text/javascript,export default {};',
312
- };
313
- }
314
- return nextResolve(specifier, context);
315
- }
316
- `,
317
- 'utf8'
318
- );
319
- register(pathToFileURL(loaderPath).href);
320
-
321
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
322
- const { default: Controller } = await import(controllerModuleUrl);
323
- const controller = new Controller();
324
- const originalSlice = globalThis.slice;
325
- const originalWindow = globalThis.window;
326
-
327
- controller.bundleConfig = {
328
- bundles: {
329
- vendorShared: {
330
- file: 'slice-bundle.vendor-shared.js',
331
- },
332
- routes: {
333
- dashboard: {
334
- file: 'slice-bundle.dashboard.js',
335
- dependencies: ['vendor-shared'],
336
- },
337
- },
338
- },
339
- };
340
-
341
- const importsByPath = {
342
- '/bundles/slice-bundle.vendor-shared.js': {
343
- SLICE_BUNDLE_META: {
344
- bundleKey: 'vendor-shared',
345
- type: 'shared',
346
- },
347
- registerAll: async () => ({
348
- 'vendors/dompurify': {
349
- sanitize: () => 'ok',
350
- },
351
- }),
352
- },
353
- '/bundles/slice-bundle.dashboard.js': {
354
- SLICE_BUNDLE_META: {
355
- bundleKey: 'dashboard',
356
- type: 'route',
357
- },
358
- registerAll: async () => {},
359
- },
360
- };
361
-
362
- controller.importBundleOnce = async (bundlePath) => importsByPath[bundlePath];
363
-
364
- try {
365
- globalThis.slice = {
366
- stylesManager: {},
367
- };
368
- globalThis.window = {
369
- __SLICE_SHARED_DEPS__: {},
370
- };
371
-
372
- await controller.loadBundle('dashboard');
373
-
374
- assert.equal(typeof globalThis.window.__SLICE_SHARED_DEPS__['vendors/dompurify']?.sanitize, 'function');
375
- } finally {
376
- globalThis.slice = originalSlice;
377
- globalThis.window = originalWindow;
378
- await rm(tempDir, { recursive: true, force: true });
379
- }
380
- });
381
-
382
- test('loadBundle appends bundle hash as query param when importing bundle files', async () => {
383
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
384
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
385
- await writeFile(
386
- loaderPath,
387
- `export async function resolve(specifier, context, nextResolve) {
388
- if (specifier === '/Components/components.js') {
389
- return {
390
- shortCircuit: true,
391
- url: 'data:text/javascript,export default {};',
392
- };
393
- }
394
- return nextResolve(specifier, context);
395
- }
396
- `,
397
- 'utf8'
398
- );
399
- register(pathToFileURL(loaderPath).href);
400
-
401
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
402
- const { default: Controller } = await import(controllerModuleUrl);
403
- const controller = new Controller();
404
- const originalSlice = globalThis.slice;
405
-
406
- controller.bundleConfig = {
407
- bundles: {
408
- routes: {
409
- dashboard: {
410
- file: 'slice-bundle.dashboard.js',
411
- hash: 'abc123hash',
412
- },
413
- },
414
- },
415
- };
416
-
417
- let importedPath = null;
418
- controller.importBundleOnce = async (bundlePath) => {
419
- importedPath = bundlePath;
420
- return {
421
- SLICE_BUNDLE_META: {
422
- bundleKey: 'dashboard',
423
- type: 'route',
424
- },
425
- registerAll: async () => {},
426
- };
427
- };
428
-
429
- try {
430
- globalThis.slice = {
431
- stylesManager: {},
432
- };
433
-
434
- await controller.loadBundle('dashboard');
435
- assert.equal(importedPath, '/bundles/slice-bundle.dashboard.js?v=abc123hash');
436
- } finally {
437
- globalThis.slice = originalSlice;
438
- await rm(tempDir, { recursive: true, force: true });
439
- }
440
- });
441
-
442
- test('loadBundle dedupes concurrent and repeated alias/case requests using canonical bundle key', async () => {
443
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
444
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
445
- await writeFile(
446
- loaderPath,
447
- `export async function resolve(specifier, context, nextResolve) {
448
- if (specifier === '/Components/components.js') {
449
- return {
450
- shortCircuit: true,
451
- url: 'data:text/javascript,export default {};',
452
- };
453
- }
454
- return nextResolve(specifier, context);
455
- }
456
- `,
457
- 'utf8'
458
- );
459
- register(pathToFileURL(loaderPath).href);
460
-
461
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
462
- const { default: Controller } = await import(controllerModuleUrl);
463
- const controller = new Controller();
464
- const originalSlice = globalThis.slice;
465
-
466
- controller.bundleConfig = {
467
- bundles: {
468
- routes: {
469
- dashboard: {
470
- file: 'slice-bundle.dashboard.js',
471
- },
472
- },
473
- },
474
- };
475
-
476
- let registerCallCount = 0;
477
- controller.importBundleOnce = async () => ({
478
- SLICE_BUNDLE_META: {
479
- bundleKey: 'routes.dashboard.v2',
480
- type: 'route',
481
- },
482
- registerAll: async () => {
483
- registerCallCount += 1;
484
- await new Promise((resolve) => setTimeout(resolve, 10));
485
- },
486
- });
487
-
488
- try {
489
- globalThis.slice = {
490
- stylesManager: {},
491
- };
492
-
493
- await Promise.all([
494
- controller.loadBundle('dashboard'),
495
- controller.loadBundle('DASHBOARD'),
496
- ]);
497
-
498
- await controller.loadBundle('Dashboard');
499
-
500
- assert.equal(registerCallCount, 1);
501
- assert.equal(controller.loadedBundles.has('dashboard'), true);
502
- assert.equal(controller.loadedBundles.has('routes.dashboard.v2'), true);
503
- assert.equal(controller.bundleLoadPromises.size, 0);
504
- } finally {
505
- globalThis.slice = originalSlice;
506
- await rm(tempDir, { recursive: true, force: true });
507
- }
508
- });
509
-
510
- test('registerVendorSharedDependencies only runs for vendor-shared canonical bundle or explicit metadata flag', async () => {
511
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
512
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
513
- await writeFile(
514
- loaderPath,
515
- `export async function resolve(specifier, context, nextResolve) {
516
- if (specifier === '/Components/components.js') {
517
- return {
518
- shortCircuit: true,
519
- url: 'data:text/javascript,export default {};',
520
- };
521
- }
522
- return nextResolve(specifier, context);
523
- }
524
- `,
525
- 'utf8'
526
- );
527
- register(pathToFileURL(loaderPath).href);
528
-
529
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
530
- const { default: Controller } = await import(controllerModuleUrl);
531
- const controller = new Controller();
532
- const originalWindow = globalThis.window;
533
-
534
- try {
535
- globalThis.window = {
536
- __SLICE_SHARED_DEPS__: {},
537
- };
538
-
539
- controller.registerVendorSharedDependencies(
540
- {
541
- SLICE_SHARED_DEPS: {
542
- shouldNotLoad: true,
543
- },
544
- },
545
- {
546
- type: 'shared',
547
- bundleKey: 'app-shared',
548
- },
549
- 'app-shared'
550
- );
551
-
552
- assert.equal(globalThis.window.__SLICE_SHARED_DEPS__.shouldNotLoad, undefined);
553
-
554
- controller.registerVendorSharedDependencies(
555
- {
556
- SLICE_SHARED_DEPS: {
557
- explicitFlagDependency: true,
558
- },
559
- },
560
- {
561
- type: 'shared',
562
- bundleKey: 'app-shared',
563
- registerVendorSharedDependencies: true,
564
- },
565
- 'app-shared'
566
- );
567
-
568
- assert.equal(globalThis.window.__SLICE_SHARED_DEPS__.explicitFlagDependency, true);
569
- } finally {
570
- globalThis.window = originalWindow;
571
- await rm(tempDir, { recursive: true, force: true });
572
- }
573
- });
574
-
575
- test('Slice init fails fast with contextual error on invalid Bundling V2 contract path', async () => {
576
- const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-init-loader-'));
577
- const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
578
- await writeFile(
579
- loaderPath,
580
- `export async function resolve(specifier, context, nextResolve) {
581
- if (specifier === '/Components/components.js') {
582
- return {
583
- shortCircuit: true,
584
- url: 'data:text/javascript,export default {};',
585
- };
586
- }
587
- return nextResolve(specifier, context);
588
- }
589
- `,
590
- 'utf8'
591
- );
592
- register(pathToFileURL(loaderPath).href);
593
-
594
- const originalWindow = globalThis.window;
595
- const originalDocument = globalThis.document;
596
- const originalFetch = globalThis.fetch;
597
- const originalAlert = globalThis.alert;
598
- const originalSliceDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'slice');
599
- const originalConsoleLog = console.log;
600
- const originalConsoleWarn = console.warn;
601
- const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
602
- const { default: Controller } = await import(controllerModuleUrl);
603
- const originalLoadBundle = Controller.prototype.loadBundle;
604
-
605
- const loggedMessages = [];
606
-
607
- try {
608
- globalThis.window = {
609
- location: {
610
- pathname: '/dashboard',
611
- origin: 'http://localhost',
612
- },
613
- };
614
-
615
- Object.defineProperty(globalThis, 'slice', {
616
- configurable: true,
617
- get() {
618
- return globalThis.window?.slice;
619
- },
620
- set(value) {
621
- if (!globalThis.window) {
622
- globalThis.window = {};
623
- }
624
- globalThis.window.slice = value;
625
- },
626
- });
627
-
628
- Controller.prototype.loadBundle = async () => {
629
- throw new Error(
630
- 'Bundle "critical" missing Bundling V2 exports contract: requires SLICE_BUNDLE_META and registerAll'
631
- );
632
- };
633
-
634
- globalThis.document = {
635
- head: {
636
- appendChild() {},
637
- },
638
- body: {
639
- appendChild() {},
640
- },
641
- createElement() {
642
- return {
643
- appendChild() {},
644
- innerHTML: '',
645
- id: '',
646
- };
647
- },
648
- createTextNode() {
649
- return {};
650
- },
651
- addEventListener() {},
652
- };
653
-
654
- globalThis.alert = () => {};
655
- console.log = (...args) => {
656
- loggedMessages.push(args.map(String).join(' '));
657
- };
658
- console.warn = () => {};
659
-
660
- globalThis.fetch = async (url) => {
661
- if (url === '/sliceConfig.json') {
662
- return {
663
- ok: true,
664
- json: async () => ({
665
- paths: {
666
- routesFile: '/routes.js',
667
- components: {},
668
- },
669
- themeManager: { enabled: false },
670
- stylesManager: { requestedStyles: [] },
671
- logger: { enabled: false },
672
- debugger: { enabled: false },
673
- loading: { enabled: false },
674
- events: { enabled: false },
675
- context: { enabled: false },
676
- }),
677
- };
678
- }
679
-
680
- if (url === '/slice-env.json') {
681
- return {
682
- ok: true,
683
- json: async () => ({ mode: 'production' }),
684
- };
685
- }
686
-
687
- if (url === '/bundles/bundle.config.json') {
688
- return {
689
- ok: true,
690
- json: async () => ({
691
- production: true,
692
- bundles: {
693
- critical: { file: 'critical.js' },
694
- routes: {},
695
- },
696
- routeBundles: {},
697
- }),
698
- };
699
- }
700
-
701
- throw new Error(`Unexpected fetch URL: ${url}`);
702
- };
703
-
704
- const sliceModuleUrl = new URL(`../Slice.js?fail-fast-test=${Date.now()}`, import.meta.url).href;
705
-
706
- await assert.rejects(() => import(sliceModuleUrl), /Bundling V2 initialization failed/i);
707
-
708
- assert.equal(
709
- loggedMessages.some((message) => message.includes('Using individual component loading (no bundles found)')),
710
- false,
711
- 'init must not log fallback bundle message on Bundling V2 contract failure'
712
- );
713
- } finally {
714
- Controller.prototype.loadBundle = originalLoadBundle;
715
- globalThis.window = originalWindow;
716
- globalThis.document = originalDocument;
717
- globalThis.fetch = originalFetch;
718
- globalThis.alert = originalAlert;
719
- if (originalSliceDescriptor) {
720
- Object.defineProperty(globalThis, 'slice', originalSliceDescriptor);
721
- } else {
722
- delete globalThis.slice;
723
- }
724
- console.log = originalConsoleLog;
725
- console.warn = originalConsoleWarn;
726
- await rm(tempDir, { recursive: true, force: true });
727
- }
728
- });
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { register } from 'node:module';
7
+ import { pathToFileURL } from 'node:url';
8
+
9
+ test('validateBundleModule rejects module missing Bundling V2 exports contract', async () => {
10
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
11
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
12
+ await writeFile(
13
+ loaderPath,
14
+ `export async function resolve(specifier, context, nextResolve) {
15
+ if (specifier === '/Components/components.js') {
16
+ return {
17
+ shortCircuit: true,
18
+ url: 'data:text/javascript,export default {};',
19
+ };
20
+ }
21
+ return nextResolve(specifier, context);
22
+ }
23
+ `,
24
+ 'utf8'
25
+ );
26
+ register(pathToFileURL(loaderPath).href);
27
+
28
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
29
+ const { default: Controller } = await import(controllerModuleUrl);
30
+ const controller = new Controller();
31
+ const originalWindow = globalThis.window;
32
+
33
+ try {
34
+ globalThis.window = {
35
+ __slicePendingRegistrations: [],
36
+ };
37
+
38
+ assert.equal(
39
+ typeof controller.validateBundleModule,
40
+ 'function',
41
+ 'Controller must expose validateBundleModule for Bundling V2 runtime contract checks'
42
+ );
43
+
44
+ if (typeof controller.validateBundleModule === 'function') {
45
+ await assert.rejects(
46
+ () => Promise.resolve(controller.validateBundleModule({ default: {} }, 'critical.js')),
47
+ /missing bundling v2 exports contract/i
48
+ );
49
+ }
50
+ } finally {
51
+ globalThis.window = originalWindow;
52
+ await rm(tempDir, { recursive: true, force: true });
53
+ }
54
+ });
55
+
56
+ test('loadBundle marks requested bundle key and metadata bundleKey as loaded', async () => {
57
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
58
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
59
+ await writeFile(
60
+ loaderPath,
61
+ `export async function resolve(specifier, context, nextResolve) {
62
+ if (specifier === '/Components/components.js') {
63
+ return {
64
+ shortCircuit: true,
65
+ url: 'data:text/javascript,export default {};',
66
+ };
67
+ }
68
+ return nextResolve(specifier, context);
69
+ }
70
+ `,
71
+ 'utf8'
72
+ );
73
+ register(pathToFileURL(loaderPath).href);
74
+
75
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
76
+ const { default: Controller } = await import(controllerModuleUrl);
77
+ const controller = new Controller();
78
+ const originalSlice = globalThis.slice;
79
+
80
+ controller.bundleConfig = {
81
+ bundles: {
82
+ routes: {
83
+ dashboard: {
84
+ file: 'dashboard.js',
85
+ },
86
+ },
87
+ },
88
+ };
89
+
90
+ controller.importBundleOnce = async () => ({});
91
+ controller.validateBundleModule = async () => ({
92
+ metadata: {
93
+ bundleKey: 'routes.dashboard.v2',
94
+ type: 'route',
95
+ },
96
+ registerAll: async () => {},
97
+ });
98
+
99
+ try {
100
+ globalThis.slice = {
101
+ stylesManager: {},
102
+ };
103
+
104
+ await controller.loadBundle('dashboard');
105
+
106
+ assert.equal(controller.loadedBundles.has('dashboard'), true);
107
+ assert.equal(controller.loadedBundles.has('routes.dashboard.v2'), true);
108
+ assert.equal(controller.criticalBundleLoaded, false);
109
+ } finally {
110
+ globalThis.slice = originalSlice;
111
+ await rm(tempDir, { recursive: true, force: true });
112
+ }
113
+ });
114
+
115
+ test('loadBundle resolves dependencies first and registers vendor-shared exports once', async () => {
116
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
117
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
118
+ await writeFile(
119
+ loaderPath,
120
+ `export async function resolve(specifier, context, nextResolve) {
121
+ if (specifier === '/Components/components.js') {
122
+ return {
123
+ shortCircuit: true,
124
+ url: 'data:text/javascript,export default {};',
125
+ };
126
+ }
127
+ return nextResolve(specifier, context);
128
+ }
129
+ `,
130
+ 'utf8'
131
+ );
132
+ register(pathToFileURL(loaderPath).href);
133
+
134
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
135
+ const { default: Controller } = await import(controllerModuleUrl);
136
+ const controller = new Controller();
137
+ const originalSlice = globalThis.slice;
138
+ const originalWindow = globalThis.window;
139
+
140
+ controller.bundleConfig = {
141
+ bundles: {
142
+ shared: {
143
+ 'vendor-shared': {
144
+ file: 'slice-bundle.vendor-shared.js',
145
+ },
146
+ },
147
+ routes: {
148
+ dashboard: {
149
+ file: 'slice-bundle.dashboard.js',
150
+ dependencies: ['vendor-shared'],
151
+ },
152
+ },
153
+ },
154
+ };
155
+
156
+ const importOrder = [];
157
+ const registerOrder = [];
158
+ const importsByPath = {
159
+ '/bundles/slice-bundle.vendor-shared.js': {
160
+ SLICE_BUNDLE_META: {
161
+ bundleKey: 'vendor-shared',
162
+ type: 'shared',
163
+ },
164
+ SLICE_SHARED_DEPS: {
165
+ 'vendors/dompurify': {
166
+ sanitize: () => 'ok',
167
+ },
168
+ },
169
+ registerAll: async () => {
170
+ registerOrder.push('vendor-shared');
171
+ },
172
+ },
173
+ '/bundles/slice-bundle.dashboard.js': {
174
+ SLICE_BUNDLE_META: {
175
+ bundleKey: 'dashboard',
176
+ type: 'route',
177
+ },
178
+ registerAll: async () => {
179
+ registerOrder.push('dashboard');
180
+ },
181
+ },
182
+ };
183
+
184
+ controller.importBundleOnce = async (bundlePath) => {
185
+ importOrder.push(bundlePath);
186
+ return importsByPath[bundlePath];
187
+ };
188
+
189
+ try {
190
+ globalThis.slice = {
191
+ stylesManager: {},
192
+ };
193
+ globalThis.window = {
194
+ __SLICE_SHARED_DEPS__: {},
195
+ };
196
+
197
+ await controller.loadBundle('dashboard');
198
+ await controller.loadBundle('dashboard');
199
+
200
+ assert.deepEqual(importOrder, ['/bundles/slice-bundle.vendor-shared.js', '/bundles/slice-bundle.dashboard.js']);
201
+ assert.deepEqual(registerOrder, ['vendor-shared', 'dashboard']);
202
+ assert.equal(typeof globalThis.window.__SLICE_SHARED_DEPS__['vendors/dompurify']?.sanitize, 'function');
203
+ } finally {
204
+ globalThis.slice = originalSlice;
205
+ globalThis.window = originalWindow;
206
+ await rm(tempDir, { recursive: true, force: true });
207
+ }
208
+ });
209
+
210
+ test('loadBundle resolves vendor-shared dependency from bundles.vendorShared config shape', async () => {
211
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
212
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
213
+ await writeFile(
214
+ loaderPath,
215
+ `export async function resolve(specifier, context, nextResolve) {
216
+ if (specifier === '/Components/components.js') {
217
+ return {
218
+ shortCircuit: true,
219
+ url: 'data:text/javascript,export default {};',
220
+ };
221
+ }
222
+ return nextResolve(specifier, context);
223
+ }
224
+ `,
225
+ 'utf8'
226
+ );
227
+ register(pathToFileURL(loaderPath).href);
228
+
229
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
230
+ const { default: Controller } = await import(controllerModuleUrl);
231
+ const controller = new Controller();
232
+ const originalSlice = globalThis.slice;
233
+ const originalWindow = globalThis.window;
234
+
235
+ controller.bundleConfig = {
236
+ bundles: {
237
+ vendorShared: {
238
+ file: 'slice-bundle.vendor-shared.js',
239
+ },
240
+ routes: {
241
+ dashboard: {
242
+ file: 'slice-bundle.dashboard.js',
243
+ dependencies: ['vendor-shared'],
244
+ },
245
+ },
246
+ },
247
+ };
248
+
249
+ const importOrder = [];
250
+ const registerOrder = [];
251
+ const importsByPath = {
252
+ '/bundles/slice-bundle.vendor-shared.js': {
253
+ SLICE_BUNDLE_META: {
254
+ bundleKey: 'vendor-shared',
255
+ type: 'shared',
256
+ },
257
+ SLICE_SHARED_DEPS: {
258
+ 'vendors/dompurify': {
259
+ sanitize: () => 'ok',
260
+ },
261
+ },
262
+ registerAll: async () => {
263
+ registerOrder.push('vendor-shared');
264
+ },
265
+ },
266
+ '/bundles/slice-bundle.dashboard.js': {
267
+ SLICE_BUNDLE_META: {
268
+ bundleKey: 'dashboard',
269
+ type: 'route',
270
+ },
271
+ registerAll: async () => {
272
+ registerOrder.push('dashboard');
273
+ },
274
+ },
275
+ };
276
+
277
+ controller.importBundleOnce = async (bundlePath) => {
278
+ importOrder.push(bundlePath);
279
+ return importsByPath[bundlePath];
280
+ };
281
+
282
+ try {
283
+ globalThis.slice = {
284
+ stylesManager: {},
285
+ };
286
+ globalThis.window = {
287
+ __SLICE_SHARED_DEPS__: {},
288
+ };
289
+
290
+ await controller.loadBundle('dashboard');
291
+
292
+ assert.deepEqual(importOrder, ['/bundles/slice-bundle.vendor-shared.js', '/bundles/slice-bundle.dashboard.js']);
293
+ assert.deepEqual(registerOrder, ['vendor-shared', 'dashboard']);
294
+ assert.equal(typeof globalThis.window.__SLICE_SHARED_DEPS__['vendors/dompurify']?.sanitize, 'function');
295
+ } finally {
296
+ globalThis.slice = originalSlice;
297
+ globalThis.window = originalWindow;
298
+ await rm(tempDir, { recursive: true, force: true });
299
+ }
300
+ });
301
+
302
+ test('loadBundle registers vendor-shared dependencies from registerAll return when SLICE_SHARED_DEPS export is absent', async () => {
303
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
304
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
305
+ await writeFile(
306
+ loaderPath,
307
+ `export async function resolve(specifier, context, nextResolve) {
308
+ if (specifier === '/Components/components.js') {
309
+ return {
310
+ shortCircuit: true,
311
+ url: 'data:text/javascript,export default {};',
312
+ };
313
+ }
314
+ return nextResolve(specifier, context);
315
+ }
316
+ `,
317
+ 'utf8'
318
+ );
319
+ register(pathToFileURL(loaderPath).href);
320
+
321
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
322
+ const { default: Controller } = await import(controllerModuleUrl);
323
+ const controller = new Controller();
324
+ const originalSlice = globalThis.slice;
325
+ const originalWindow = globalThis.window;
326
+
327
+ controller.bundleConfig = {
328
+ bundles: {
329
+ vendorShared: {
330
+ file: 'slice-bundle.vendor-shared.js',
331
+ },
332
+ routes: {
333
+ dashboard: {
334
+ file: 'slice-bundle.dashboard.js',
335
+ dependencies: ['vendor-shared'],
336
+ },
337
+ },
338
+ },
339
+ };
340
+
341
+ const importsByPath = {
342
+ '/bundles/slice-bundle.vendor-shared.js': {
343
+ SLICE_BUNDLE_META: {
344
+ bundleKey: 'vendor-shared',
345
+ type: 'shared',
346
+ },
347
+ registerAll: async () => ({
348
+ 'vendors/dompurify': {
349
+ sanitize: () => 'ok',
350
+ },
351
+ }),
352
+ },
353
+ '/bundles/slice-bundle.dashboard.js': {
354
+ SLICE_BUNDLE_META: {
355
+ bundleKey: 'dashboard',
356
+ type: 'route',
357
+ },
358
+ registerAll: async () => {},
359
+ },
360
+ };
361
+
362
+ controller.importBundleOnce = async (bundlePath) => importsByPath[bundlePath];
363
+
364
+ try {
365
+ globalThis.slice = {
366
+ stylesManager: {},
367
+ };
368
+ globalThis.window = {
369
+ __SLICE_SHARED_DEPS__: {},
370
+ };
371
+
372
+ await controller.loadBundle('dashboard');
373
+
374
+ assert.equal(typeof globalThis.window.__SLICE_SHARED_DEPS__['vendors/dompurify']?.sanitize, 'function');
375
+ } finally {
376
+ globalThis.slice = originalSlice;
377
+ globalThis.window = originalWindow;
378
+ await rm(tempDir, { recursive: true, force: true });
379
+ }
380
+ });
381
+
382
+ test('loadBundle appends bundle hash as query param when importing bundle files', async () => {
383
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
384
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
385
+ await writeFile(
386
+ loaderPath,
387
+ `export async function resolve(specifier, context, nextResolve) {
388
+ if (specifier === '/Components/components.js') {
389
+ return {
390
+ shortCircuit: true,
391
+ url: 'data:text/javascript,export default {};',
392
+ };
393
+ }
394
+ return nextResolve(specifier, context);
395
+ }
396
+ `,
397
+ 'utf8'
398
+ );
399
+ register(pathToFileURL(loaderPath).href);
400
+
401
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
402
+ const { default: Controller } = await import(controllerModuleUrl);
403
+ const controller = new Controller();
404
+ const originalSlice = globalThis.slice;
405
+
406
+ controller.bundleConfig = {
407
+ bundles: {
408
+ routes: {
409
+ dashboard: {
410
+ file: 'slice-bundle.dashboard.js',
411
+ hash: 'abc123hash',
412
+ },
413
+ },
414
+ },
415
+ };
416
+
417
+ let importedPath = null;
418
+ controller.importBundleOnce = async (bundlePath) => {
419
+ importedPath = bundlePath;
420
+ return {
421
+ SLICE_BUNDLE_META: {
422
+ bundleKey: 'dashboard',
423
+ type: 'route',
424
+ },
425
+ registerAll: async () => {},
426
+ };
427
+ };
428
+
429
+ try {
430
+ globalThis.slice = {
431
+ stylesManager: {},
432
+ };
433
+
434
+ await controller.loadBundle('dashboard');
435
+ assert.equal(importedPath, '/bundles/slice-bundle.dashboard.js?v=abc123hash');
436
+ } finally {
437
+ globalThis.slice = originalSlice;
438
+ await rm(tempDir, { recursive: true, force: true });
439
+ }
440
+ });
441
+
442
+ test('loadBundle dedupes concurrent and repeated alias/case requests using canonical bundle key', async () => {
443
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
444
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
445
+ await writeFile(
446
+ loaderPath,
447
+ `export async function resolve(specifier, context, nextResolve) {
448
+ if (specifier === '/Components/components.js') {
449
+ return {
450
+ shortCircuit: true,
451
+ url: 'data:text/javascript,export default {};',
452
+ };
453
+ }
454
+ return nextResolve(specifier, context);
455
+ }
456
+ `,
457
+ 'utf8'
458
+ );
459
+ register(pathToFileURL(loaderPath).href);
460
+
461
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
462
+ const { default: Controller } = await import(controllerModuleUrl);
463
+ const controller = new Controller();
464
+ const originalSlice = globalThis.slice;
465
+
466
+ controller.bundleConfig = {
467
+ bundles: {
468
+ routes: {
469
+ dashboard: {
470
+ file: 'slice-bundle.dashboard.js',
471
+ },
472
+ },
473
+ },
474
+ };
475
+
476
+ let registerCallCount = 0;
477
+ controller.importBundleOnce = async () => ({
478
+ SLICE_BUNDLE_META: {
479
+ bundleKey: 'routes.dashboard.v2',
480
+ type: 'route',
481
+ },
482
+ registerAll: async () => {
483
+ registerCallCount += 1;
484
+ await new Promise((resolve) => setTimeout(resolve, 10));
485
+ },
486
+ });
487
+
488
+ try {
489
+ globalThis.slice = {
490
+ stylesManager: {},
491
+ };
492
+
493
+ await Promise.all([
494
+ controller.loadBundle('dashboard'),
495
+ controller.loadBundle('DASHBOARD'),
496
+ ]);
497
+
498
+ await controller.loadBundle('Dashboard');
499
+
500
+ assert.equal(registerCallCount, 1);
501
+ assert.equal(controller.loadedBundles.has('dashboard'), true);
502
+ assert.equal(controller.loadedBundles.has('routes.dashboard.v2'), true);
503
+ assert.equal(controller.bundleLoadPromises.size, 0);
504
+ } finally {
505
+ globalThis.slice = originalSlice;
506
+ await rm(tempDir, { recursive: true, force: true });
507
+ }
508
+ });
509
+
510
+ test('registerVendorSharedDependencies only runs for vendor-shared canonical bundle or explicit metadata flag', async () => {
511
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
512
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
513
+ await writeFile(
514
+ loaderPath,
515
+ `export async function resolve(specifier, context, nextResolve) {
516
+ if (specifier === '/Components/components.js') {
517
+ return {
518
+ shortCircuit: true,
519
+ url: 'data:text/javascript,export default {};',
520
+ };
521
+ }
522
+ return nextResolve(specifier, context);
523
+ }
524
+ `,
525
+ 'utf8'
526
+ );
527
+ register(pathToFileURL(loaderPath).href);
528
+
529
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
530
+ const { default: Controller } = await import(controllerModuleUrl);
531
+ const controller = new Controller();
532
+ const originalWindow = globalThis.window;
533
+
534
+ try {
535
+ globalThis.window = {
536
+ __SLICE_SHARED_DEPS__: {},
537
+ };
538
+
539
+ controller.registerVendorSharedDependencies(
540
+ {
541
+ SLICE_SHARED_DEPS: {
542
+ shouldNotLoad: true,
543
+ },
544
+ },
545
+ {
546
+ type: 'shared',
547
+ bundleKey: 'app-shared',
548
+ },
549
+ 'app-shared'
550
+ );
551
+
552
+ assert.equal(globalThis.window.__SLICE_SHARED_DEPS__.shouldNotLoad, undefined);
553
+
554
+ controller.registerVendorSharedDependencies(
555
+ {
556
+ SLICE_SHARED_DEPS: {
557
+ explicitFlagDependency: true,
558
+ },
559
+ },
560
+ {
561
+ type: 'shared',
562
+ bundleKey: 'app-shared',
563
+ registerVendorSharedDependencies: true,
564
+ },
565
+ 'app-shared'
566
+ );
567
+
568
+ assert.equal(globalThis.window.__SLICE_SHARED_DEPS__.explicitFlagDependency, true);
569
+ } finally {
570
+ globalThis.window = originalWindow;
571
+ await rm(tempDir, { recursive: true, force: true });
572
+ }
573
+ });
574
+
575
+ test('Slice init fails fast with contextual error on invalid Bundling V2 contract path', async () => {
576
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-init-loader-'));
577
+ const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
578
+ await writeFile(
579
+ loaderPath,
580
+ `export async function resolve(specifier, context, nextResolve) {
581
+ if (specifier === '/Components/components.js') {
582
+ return {
583
+ shortCircuit: true,
584
+ url: 'data:text/javascript,export default {};',
585
+ };
586
+ }
587
+ return nextResolve(specifier, context);
588
+ }
589
+ `,
590
+ 'utf8'
591
+ );
592
+ register(pathToFileURL(loaderPath).href);
593
+
594
+ const originalWindow = globalThis.window;
595
+ const originalDocument = globalThis.document;
596
+ const originalFetch = globalThis.fetch;
597
+ const originalAlert = globalThis.alert;
598
+ const originalSliceDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'slice');
599
+ const originalConsoleLog = console.log;
600
+ const originalConsoleWarn = console.warn;
601
+ const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
602
+ const { default: Controller } = await import(controllerModuleUrl);
603
+ const originalLoadBundle = Controller.prototype.loadBundle;
604
+
605
+ const loggedMessages = [];
606
+
607
+ try {
608
+ globalThis.window = {
609
+ location: {
610
+ pathname: '/dashboard',
611
+ origin: 'http://localhost',
612
+ },
613
+ };
614
+
615
+ Object.defineProperty(globalThis, 'slice', {
616
+ configurable: true,
617
+ get() {
618
+ return globalThis.window?.slice;
619
+ },
620
+ set(value) {
621
+ if (!globalThis.window) {
622
+ globalThis.window = {};
623
+ }
624
+ globalThis.window.slice = value;
625
+ },
626
+ });
627
+
628
+ Controller.prototype.loadBundle = async () => {
629
+ throw new Error(
630
+ 'Bundle "critical" missing Bundling V2 exports contract: requires SLICE_BUNDLE_META and registerAll'
631
+ );
632
+ };
633
+
634
+ globalThis.document = {
635
+ head: {
636
+ appendChild() {},
637
+ },
638
+ body: {
639
+ appendChild() {},
640
+ },
641
+ createElement() {
642
+ return {
643
+ appendChild() {},
644
+ innerHTML: '',
645
+ id: '',
646
+ };
647
+ },
648
+ createTextNode() {
649
+ return {};
650
+ },
651
+ addEventListener() {},
652
+ };
653
+
654
+ globalThis.alert = () => {};
655
+ console.log = (...args) => {
656
+ loggedMessages.push(args.map(String).join(' '));
657
+ };
658
+ console.warn = () => {};
659
+
660
+ globalThis.fetch = async (url) => {
661
+ if (url === '/sliceConfig.json') {
662
+ return {
663
+ ok: true,
664
+ json: async () => ({
665
+ paths: {
666
+ routesFile: '/routes.js',
667
+ components: {},
668
+ },
669
+ themeManager: { enabled: false },
670
+ stylesManager: { requestedStyles: [] },
671
+ logger: { enabled: false },
672
+ debugger: { enabled: false },
673
+ loading: { enabled: false },
674
+ events: { enabled: false },
675
+ context: { enabled: false },
676
+ }),
677
+ };
678
+ }
679
+
680
+ if (url === '/slice-env.json') {
681
+ return {
682
+ ok: true,
683
+ json: async () => ({ mode: 'production' }),
684
+ };
685
+ }
686
+
687
+ if (url === '/bundles/bundle.config.json') {
688
+ return {
689
+ ok: true,
690
+ json: async () => ({
691
+ production: true,
692
+ bundles: {
693
+ critical: { file: 'critical.js' },
694
+ routes: {},
695
+ },
696
+ routeBundles: {},
697
+ }),
698
+ };
699
+ }
700
+
701
+ throw new Error(`Unexpected fetch URL: ${url}`);
702
+ };
703
+
704
+ const sliceModuleUrl = new URL(`../Slice.js?fail-fast-test=${Date.now()}`, import.meta.url).href;
705
+
706
+ await assert.rejects(() => import(sliceModuleUrl), /Bundling V2 initialization failed/i);
707
+
708
+ assert.equal(
709
+ loggedMessages.some((message) => message.includes('Using individual component loading (no bundles found)')),
710
+ false,
711
+ 'init must not log fallback bundle message on Bundling V2 contract failure'
712
+ );
713
+ } finally {
714
+ Controller.prototype.loadBundle = originalLoadBundle;
715
+ globalThis.window = originalWindow;
716
+ globalThis.document = originalDocument;
717
+ globalThis.fetch = originalFetch;
718
+ globalThis.alert = originalAlert;
719
+ if (originalSliceDescriptor) {
720
+ Object.defineProperty(globalThis, 'slice', originalSliceDescriptor);
721
+ } else {
722
+ delete globalThis.slice;
723
+ }
724
+ console.log = originalConsoleLog;
725
+ console.warn = originalConsoleWarn;
726
+ await rm(tempDir, { recursive: true, force: true });
727
+ }
728
+ });