@useavalon/avalon 0.1.5 → 0.1.6

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 (31) hide show
  1. package/package.json +31 -58
  2. package/src/build/README.md +0 -310
  3. package/src/client/tests/css-hmr-handler.test.ts +0 -360
  4. package/src/client/tests/framework-adapter.test.ts +0 -519
  5. package/src/client/tests/hmr-coordinator.test.ts +0 -176
  6. package/src/client/tests/hydration-option-parsing.test.ts +0 -107
  7. package/src/client/tests/lit-adapter.test.ts +0 -427
  8. package/src/client/tests/preact-adapter.test.ts +0 -353
  9. package/src/client/tests/qwik-adapter.test.ts +0 -343
  10. package/src/client/tests/react-adapter.test.ts +0 -317
  11. package/src/client/tests/solid-adapter.test.ts +0 -396
  12. package/src/client/tests/svelte-adapter.test.ts +0 -387
  13. package/src/client/tests/vue-adapter.test.ts +0 -407
  14. package/src/components/tests/component-analyzer.test.ts +0 -96
  15. package/src/components/tests/component-detection.test.ts +0 -347
  16. package/src/components/tests/persistent-islands.test.ts +0 -398
  17. package/src/core/components/tests/enhanced-framework-detector.test.ts +0 -577
  18. package/src/core/components/tests/framework-registry.test.ts +0 -465
  19. package/src/core/integrations/README.md +0 -282
  20. package/src/core/layout/tests/enhanced-layout-resolver.test.ts +0 -477
  21. package/src/core/layout/tests/layout-cache-optimization.test.ts +0 -149
  22. package/src/core/layout/tests/layout-composer.test.ts +0 -486
  23. package/src/core/layout/tests/layout-data-loader.test.ts +0 -443
  24. package/src/core/layout/tests/layout-discovery.test.ts +0 -253
  25. package/src/core/layout/tests/layout-matcher.test.ts +0 -480
  26. package/src/core/modules/tests/framework-module-resolver.test.ts +0 -263
  27. package/src/core/modules/tests/module-resolution-integration.test.ts +0 -117
  28. package/src/islands/discovery/tests/island-discovery.test.ts +0 -881
  29. package/src/middleware/__tests__/discovery.test.ts +0 -107
  30. package/src/types/tests/layout-types.test.ts +0 -197
  31. package/src/vite-plugin/tests/image-optimization.test.ts +0 -54
@@ -1,443 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import {
3
- LayoutDataLoader,
4
- LayoutDataLoadingError,
5
- createLayoutDataLoader,
6
- loadSingleLayoutData,
7
- mergeLayoutData,
8
- getParentLayoutData,
9
- } from '../layout-data-loader.ts';
10
- import type { LayoutContext, LayoutData, LayoutHandler, LayoutLoader } from '../../../schemas/layout.ts';
11
-
12
- function createMockLayoutHandler(path: string, loader?: LayoutLoader, priority: number = 0): LayoutHandler {
13
- return {
14
- component: () => null,
15
- loader,
16
- path,
17
- priority,
18
- };
19
- }
20
-
21
- function createMockLayoutContext(): LayoutContext {
22
- return {
23
- request: new Request('http://localhost/test'),
24
- params: { id: '123' },
25
- query: new URLSearchParams('?test=value'),
26
- state: new Map(),
27
- };
28
- }
29
-
30
- describe('LayoutDataLoader', () => {
31
- let loader: LayoutDataLoader;
32
- let mockContext: LayoutContext;
33
-
34
- beforeEach(() => {
35
- loader = new LayoutDataLoader({
36
- developmentMode: true,
37
- timeout: 1000,
38
- });
39
- mockContext = createMockLayoutContext();
40
- });
41
-
42
- describe('loadLayoutData', () => {
43
- it('should return empty array when no handlers have loaders', async () => {
44
- const handlers = [createMockLayoutHandler('/layout1'), createMockLayoutHandler('/layout2')];
45
- const results = await loader.loadLayoutData(handlers, mockContext);
46
- expect(results.length).toEqual(0);
47
- });
48
-
49
- it('should load data from single layout loader', async () => {
50
- const testData = { message: 'Hello from layout' };
51
- const mockLoader: LayoutLoader = async () => testData;
52
- const handlers = [createMockLayoutHandler('/layout1', mockLoader)];
53
-
54
- const results = await loader.loadLayoutData(handlers, mockContext);
55
-
56
- expect(results.length).toEqual(1);
57
- expect(results[0].success).toEqual(true);
58
- expect(results[0].data).toEqual(testData);
59
- expect(results[0].layoutPath).toEqual('/layout1');
60
- expect(results[0].loadingTime >= 0).toEqual(true);
61
- });
62
-
63
- it('should load data from multiple layout loaders in parallel', async () => {
64
- const testData1 = { layout1: 'data1' };
65
- const testData2 = { layout2: 'data2' };
66
-
67
- const mockLoader1: LayoutLoader = async () => {
68
- await new Promise(resolve => setTimeout(resolve, 100));
69
- return testData1;
70
- };
71
- const mockLoader2: LayoutLoader = async () => {
72
- await new Promise(resolve => setTimeout(resolve, 50));
73
- return testData2;
74
- };
75
-
76
- const handlers = [
77
- createMockLayoutHandler('/layout1', mockLoader1),
78
- createMockLayoutHandler('/layout2', mockLoader2),
79
- ];
80
-
81
- const startTime = performance.now();
82
- const results = await loader.loadLayoutData(handlers, mockContext);
83
- const totalTime = performance.now() - startTime;
84
-
85
- expect(results.length).toEqual(2);
86
- expect(results[0].success).toEqual(true);
87
- expect(results[0].data).toEqual(testData1);
88
- expect(results[1].success).toEqual(true);
89
- expect(results[1].data).toEqual(testData2);
90
-
91
- expect(totalTime < 140).toEqual(true);
92
- });
93
-
94
- it('should handle loader errors gracefully when continueOnError is true', async () => {
95
- const testData = { layout2: 'success' };
96
- const errorLoader: LayoutLoader = async () => {
97
- throw new Error('Loader failed');
98
- };
99
- const successLoader: LayoutLoader = async () => testData;
100
-
101
- const handlers = [
102
- createMockLayoutHandler('/layout1', errorLoader),
103
- createMockLayoutHandler('/layout2', successLoader),
104
- ];
105
-
106
- const results = await loader.loadLayoutData(handlers, mockContext);
107
-
108
- expect(results.length).toEqual(2);
109
- expect(results[0].success).toEqual(false);
110
- expect(results[0].error).toBeInstanceOf(LayoutDataLoadingError);
111
- expect(results[0].layoutPath).toEqual('/layout1');
112
- expect(results[1].success).toEqual(true);
113
- expect(results[1].data).toEqual(testData);
114
- });
115
-
116
- it('should fail fast when continueOnError is false', async () => {
117
- const loaderWithError = new LayoutDataLoader({
118
- continueOnError: false,
119
- enableParallelLoading: false,
120
- });
121
-
122
- const errorLoader: LayoutLoader = async () => {
123
- throw new Error('First loader failed');
124
- };
125
- const successLoader: LayoutLoader = async () => ({ success: true });
126
-
127
- const handlers = [
128
- createMockLayoutHandler('/layout1', errorLoader),
129
- createMockLayoutHandler('/layout2', successLoader),
130
- ];
131
-
132
- const results = await loaderWithError.loadLayoutData(handlers, mockContext);
133
-
134
- expect(results.length).toEqual(1);
135
- expect(results[0].success).toEqual(false);
136
- });
137
-
138
- it('should handle timeout errors', async () => {
139
- const timeoutLoader = new LayoutDataLoader({
140
- timeout: 100,
141
- });
142
-
143
- const slowLoader: LayoutLoader = async () => {
144
- await new Promise(resolve => setTimeout(resolve, 200));
145
- return { data: 'slow' };
146
- };
147
-
148
- const handlers = [createMockLayoutHandler('/slow-layout', slowLoader)];
149
- const results = await timeoutLoader.loadLayoutData(handlers, mockContext);
150
-
151
- expect(results.length).toEqual(1);
152
- expect(results[0].success).toEqual(false);
153
- expect(results[0].error?.message).toContain('timed out');
154
- });
155
-
156
- it('should retry failed loaders', async () => {
157
- let attemptCount = 0;
158
- const retryLoader: LayoutLoader = async () => {
159
- attemptCount++;
160
- if (attemptCount < 3) {
161
- throw new Error(`Attempt ${attemptCount} failed`);
162
- }
163
- return { attempt: attemptCount };
164
- };
165
-
166
- const retryingLoader = new LayoutDataLoader({
167
- maxRetries: 2,
168
- retryDelay: 10,
169
- });
170
-
171
- const handlers = [createMockLayoutHandler('/retry-layout', retryLoader)];
172
- const results = await retryingLoader.loadLayoutData(handlers, mockContext);
173
-
174
- expect(results.length).toEqual(1);
175
- expect(results[0].success).toEqual(true);
176
- expect(results[0].data.attempt).toEqual(3);
177
- expect(attemptCount).toEqual(3);
178
- });
179
-
180
- it('should load data sequentially when parallel loading is disabled', async () => {
181
- const sequentialLoader = new LayoutDataLoader({
182
- enableParallelLoading: false,
183
- });
184
-
185
- let executionOrder: string[] = [];
186
-
187
- const loader1: LayoutLoader = async () => {
188
- await new Promise(resolve => setTimeout(resolve, 50));
189
- executionOrder.push('loader1');
190
- return { order: 1 };
191
- };
192
-
193
- const loader2: LayoutLoader = async () => {
194
- await new Promise(resolve => setTimeout(resolve, 30));
195
- executionOrder.push('loader2');
196
- return { order: 2 };
197
- };
198
-
199
- const handlers = [createMockLayoutHandler('/layout1', loader1), createMockLayoutHandler('/layout2', loader2)];
200
- const results = await sequentialLoader.loadLayoutData(handlers, mockContext);
201
-
202
- expect(results.length).toEqual(2);
203
- expect(executionOrder).toEqual(['loader1', 'loader2']);
204
- expect(results[0].data.order).toEqual(1);
205
- expect(results[1].data.order).toEqual(2);
206
- });
207
- });
208
-
209
- describe('processLoadingResults', () => {
210
- it('should process successful loading results correctly', () => {
211
- const handlers = [createMockLayoutHandler('/layout1'), createMockLayoutHandler('/layout2')];
212
- const results = [
213
- { success: true, data: { layout1: 'data1' }, loadingTime: 100, layoutPath: '/layout1' },
214
- { success: true, data: { layout2: 'data2' }, loadingTime: 150, layoutPath: '/layout2' },
215
- ];
216
-
217
- const { data, errors } = loader.processLoadingResults(results, handlers);
218
-
219
- expect(data.length).toEqual(2);
220
- expect(data[0]).toEqual({ layout1: 'data1' });
221
- expect(data[1]).toEqual({ layout2: 'data2' });
222
- expect(errors.length).toEqual(0);
223
- });
224
-
225
- it('should handle failed loading results with fallback data', () => {
226
- const handlers = [createMockLayoutHandler('/layout1'), createMockLayoutHandler('/layout2')];
227
- const results = [
228
- { success: true, data: { layout1: 'data1' }, loadingTime: 100, layoutPath: '/layout1' },
229
- {
230
- success: false,
231
- data: {},
232
- error: new LayoutDataLoadingError('Failed to load', '/layout2'),
233
- loadingTime: 0,
234
- layoutPath: '/layout2',
235
- },
236
- ];
237
-
238
- const { data, errors } = loader.processLoadingResults(results, handlers);
239
-
240
- expect(data.length).toEqual(2);
241
- expect(data[0]).toEqual({ layout1: 'data1' });
242
- expect(data[1]).toEqual({});
243
- expect(errors.length).toEqual(1);
244
- expect(errors[0].layoutPath).toEqual('/layout2');
245
- expect(errors[0].errorType).toEqual('loader');
246
- });
247
-
248
- it('should handle handlers without loaders', () => {
249
- const handlers = [
250
- createMockLayoutHandler('/layout1'),
251
- createMockLayoutHandler('/layout2'),
252
- createMockLayoutHandler('/layout3'),
253
- ];
254
-
255
- const results = [
256
- { success: true, data: { layout2: 'data2' }, loadingTime: 100, layoutPath: '/layout2' },
257
- ];
258
-
259
- const { data, errors } = loader.processLoadingResults(results, handlers);
260
-
261
- expect(data.length).toEqual(3);
262
- expect(data[0]).toEqual({});
263
- expect(data[1]).toEqual({ layout2: 'data2' });
264
- expect(data[2]).toEqual({});
265
- expect(errors.length).toEqual(0);
266
- });
267
- });
268
-
269
- describe('createEnhancedContext', () => {
270
- it('should create enhanced context with parent data', () => {
271
- const parentData = [{ parent1: 'data1' }, { parent2: 'data2' }];
272
-
273
- const enhancedContext = loader.createEnhancedContext(mockContext, parentData);
274
-
275
- expect(enhancedContext.request).toEqual(mockContext.request);
276
- expect(enhancedContext.params).toEqual(mockContext.params);
277
- expect(enhancedContext.query).toEqual(mockContext.query);
278
- expect(enhancedContext.state.get('parentLayoutData')).toEqual(parentData);
279
- });
280
-
281
- it('should not modify original context', () => {
282
- const originalState = new Map(mockContext.state);
283
- const parentData = [{ parent: 'data' }];
284
-
285
- loader.createEnhancedContext(mockContext, parentData);
286
-
287
- expect(mockContext.state).toEqual(originalState);
288
- expect(mockContext.state.has('parentLayoutData')).toEqual(false);
289
- });
290
- });
291
-
292
- describe('createFallbackData', () => {
293
- it('should create fallback data for failed loaders', () => {
294
- const error = new LayoutDataLoadingError('Test error', '/test-layout');
295
- const fallbackData = loader.createFallbackData('/test-layout', error);
296
-
297
- expect(fallbackData.__layoutError).toEqual(true);
298
- expect(fallbackData.__layoutPath).toEqual('/test-layout');
299
- expect(fallbackData.__errorMessage).toEqual('Test error');
300
- expect(fallbackData.__errorType).toEqual('data-loading');
301
- expect(typeof fallbackData.__timestamp).toEqual('number');
302
- });
303
- });
304
-
305
- describe('validateLayoutData', () => {
306
- it('should validate correct layout data', () => {
307
- const validData = { key: 'value', number: 42 };
308
- const result = loader.validateLayoutData(validData, '/test');
309
- expect(result).toEqual(validData);
310
- });
311
-
312
- it('should handle null and undefined data', () => {
313
- expect(loader.validateLayoutData(null, '/test')).toEqual({});
314
- expect(loader.validateLayoutData(undefined, '/test')).toEqual({});
315
- });
316
-
317
- it('should reject non-object data', () => {
318
- expect(() => loader.validateLayoutData('string', '/test')).toThrow(
319
- 'Layout loader must return an object, got string'
320
- );
321
-
322
- expect(() => loader.validateLayoutData(42, '/test')).toThrow(
323
- 'Layout loader must return an object, got number'
324
- );
325
- });
326
-
327
- it('should reject array data', () => {
328
- expect(() => loader.validateLayoutData(['array'], '/test')).toThrow(
329
- 'Layout loader must return an object, not an array'
330
- );
331
- });
332
- });
333
- });
334
-
335
- describe('Utility Functions', () => {
336
- describe('createLayoutDataLoader', () => {
337
- it('should create loader with custom options', () => {
338
- const customLoader = createLayoutDataLoader({
339
- timeout: 2000,
340
- enableParallelLoading: false,
341
- });
342
-
343
- const options = customLoader.getOptions();
344
- expect(options.timeout).toEqual(2000);
345
- expect(options.enableParallelLoading).toEqual(false);
346
- });
347
- });
348
-
349
- describe('loadSingleLayoutData', () => {
350
- it('should load data for single layout handler', async () => {
351
- const testData = { single: 'data' };
352
- const mockLoader: LayoutLoader = async () => testData;
353
- const handler = createMockLayoutHandler('/single-layout', mockLoader);
354
- const context = createMockLayoutContext();
355
-
356
- const result = await loadSingleLayoutData(handler, context);
357
-
358
- expect(result.success).toEqual(true);
359
- expect(result.data).toEqual(testData);
360
- expect(result.layoutPath).toEqual('/single-layout');
361
- });
362
-
363
- it('should handle handler without loader', async () => {
364
- const handler = createMockLayoutHandler('/no-loader');
365
- const context = createMockLayoutContext();
366
-
367
- const result = await loadSingleLayoutData(handler, context);
368
-
369
- expect(result.success).toEqual(true);
370
- expect(result.data).toEqual({});
371
- expect(result.layoutPath).toEqual('/no-loader');
372
- });
373
- });
374
-
375
- describe('mergeLayoutData', () => {
376
- it('should merge multiple layout data objects', () => {
377
- const data1 = { key1: 'value1', shared: 'from1' };
378
- const data2 = { key2: 'value2', shared: 'from2' };
379
- const data3 = { key3: 'value3' };
380
-
381
- const merged = mergeLayoutData(data1, data2, data3);
382
-
383
- expect(merged).toEqual({
384
- key1: 'value1',
385
- key2: 'value2',
386
- key3: 'value3',
387
- shared: 'from2',
388
- });
389
- });
390
-
391
- it('should handle empty and invalid data sources', () => {
392
- const validData = { key: 'value' };
393
- const merged = mergeLayoutData(validData, null as any, undefined as any, [] as any, 'string' as any);
394
-
395
- expect(merged).toEqual({ key: 'value' });
396
- });
397
- });
398
-
399
- describe('getParentLayoutData', () => {
400
- it('should extract parent data from context', () => {
401
- const parentData = [{ parent1: 'data1' }, { parent2: 'data2' }];
402
- const context = createMockLayoutContext();
403
- context.state.set('parentLayoutData', parentData);
404
-
405
- const extracted = getParentLayoutData(context);
406
- expect(extracted).toEqual(parentData);
407
- });
408
-
409
- it('should return empty array when no parent data exists', () => {
410
- const context = createMockLayoutContext();
411
- const extracted = getParentLayoutData(context);
412
- expect(extracted).toEqual([]);
413
- });
414
-
415
- it('should return empty array when parent data is not an array', () => {
416
- const context = createMockLayoutContext();
417
- context.state.set('parentLayoutData', 'not-an-array');
418
-
419
- const extracted = getParentLayoutData(context);
420
- expect(extracted).toEqual([]);
421
- });
422
- });
423
- });
424
-
425
- describe('LayoutDataLoadingError', () => {
426
- it('should create error with layout path and original error', () => {
427
- const originalError = new Error('Original error');
428
- const layoutError = new LayoutDataLoadingError('Layout loading failed', '/test-layout', originalError);
429
-
430
- expect(layoutError.message).toEqual('Layout loading failed');
431
- expect(layoutError.layoutPath).toEqual('/test-layout');
432
- expect(layoutError.originalError).toEqual(originalError);
433
- expect(layoutError.name).toEqual('LayoutDataLoadingError');
434
- });
435
-
436
- it('should create error without original error', () => {
437
- const layoutError = new LayoutDataLoadingError('Layout loading failed', '/test-layout');
438
-
439
- expect(layoutError.message).toEqual('Layout loading failed');
440
- expect(layoutError.layoutPath).toEqual('/test-layout');
441
- expect(layoutError.originalError).toEqual(undefined);
442
- });
443
- });
@@ -1,253 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { resolve, join } from 'node:path';
3
- import { existsSync } from 'node:fs';
4
- import { mkdir, writeFile, rm } from 'node:fs/promises';
5
- import { LayoutDiscovery } from '../layout-discovery.ts';
6
- import type { LayoutDiscoveryOptions } from '../layout-types.ts';
7
-
8
- // Test fixtures directory
9
- const TEST_FIXTURES_DIR = resolve('tests/fixtures/layout-discovery');
10
-
11
- // Plain JS layout content (no JSX to avoid preact/jsx-dev-runtime at test time)
12
- const ROOT_LAYOUT_CONTENT = `
13
- export default function RootLayout(props) {
14
- return props.children;
15
- }
16
- export async function layoutLoader(ctx) {
17
- return { title: 'Root' };
18
- }
19
- `;
20
-
21
- const ADMIN_LAYOUT_CONTENT = `
22
- export default function AdminLayout(props) {
23
- return props.children;
24
- }
25
- `;
26
-
27
- async function setupFixtures() {
28
- if (existsSync(TEST_FIXTURES_DIR)) {
29
- await rm(TEST_FIXTURES_DIR, { recursive: true });
30
- }
31
- await mkdir(TEST_FIXTURES_DIR, { recursive: true });
32
- // root layout
33
- await writeFile(join(TEST_FIXTURES_DIR, '_layout.js'), ROOT_LAYOUT_CONTENT);
34
- // admin layout
35
- await mkdir(join(TEST_FIXTURES_DIR, 'admin'), { recursive: true });
36
- await writeFile(join(TEST_FIXTURES_DIR, 'admin', '_layout.js'), ADMIN_LAYOUT_CONTENT);
37
- // admin/users directory (no layout file)
38
- await mkdir(join(TEST_FIXTURES_DIR, 'admin', 'users'), { recursive: true });
39
- }
40
-
41
- async function cleanupFixtures() {
42
- if (existsSync(TEST_FIXTURES_DIR)) {
43
- await rm(TEST_FIXTURES_DIR, { recursive: true });
44
- }
45
- }
46
-
47
- function makeDiscovery(overrides: Partial<LayoutDiscoveryOptions> = {}): LayoutDiscovery {
48
- return new LayoutDiscovery({
49
- baseDirectory: TEST_FIXTURES_DIR,
50
- filePattern: '_layout.js',
51
- developmentMode: false,
52
- ...overrides,
53
- });
54
- }
55
-
56
- describe('LayoutDiscovery - constructor and options', () => {
57
- it('should expose options via getOptions()', () => {
58
- const discovery = makeDiscovery({ developmentMode: true });
59
- const opts = discovery.getOptions();
60
- expect(opts.filePattern).toEqual('_layout.js');
61
- expect(opts.developmentMode).toEqual(true);
62
- });
63
-
64
- it('should default filePattern to _layout.tsx when not provided', () => {
65
- const discovery = new LayoutDiscovery({ baseDirectory: TEST_FIXTURES_DIR });
66
- const opts = discovery.getOptions();
67
- expect(opts.filePattern).toEqual('_layout.tsx');
68
- });
69
- });
70
-
71
- describe('LayoutDiscovery - discoverLayouts', () => {
72
- beforeEach(setupFixtures);
73
- afterEach(cleanupFixtures);
74
-
75
- it('should find root layout for root path', async () => {
76
- const discovery = makeDiscovery();
77
- const routes = await discovery.discoverLayouts('/');
78
- expect(routes.length >= 1).toEqual(true);
79
- const rootRoute = routes.find(r => r.type === 'root');
80
- expect(rootRoute).toBeDefined();
81
- });
82
-
83
- it('should find root and admin layouts for /admin path', async () => {
84
- const discovery = makeDiscovery();
85
- const routes = await discovery.discoverLayouts('/admin');
86
- const types = new Set(routes.map(r => r.type));
87
- expect(types.has('root')).toEqual(true);
88
- expect(types.has('nested')).toEqual(true);
89
- });
90
-
91
- it('should find root and admin layouts for deep /admin/users path', async () => {
92
- const discovery = makeDiscovery();
93
- const routes = await discovery.discoverLayouts('/admin/users');
94
- // admin/users has no layout file, so only root + admin
95
- expect(routes.length).toEqual(2);
96
- });
97
-
98
- it('should return empty array for path with no layouts', async () => {
99
- const discovery = makeDiscovery();
100
- // Remove root layout to test empty case
101
- await rm(join(TEST_FIXTURES_DIR, '_layout.js'));
102
- await rm(join(TEST_FIXTURES_DIR, 'admin', '_layout.js'));
103
- const routes = await discovery.discoverLayouts('/some/unknown/path');
104
- expect(routes.length).toEqual(0);
105
- });
106
-
107
- it('should sort routes by priority (root first)', async () => {
108
- const discovery = makeDiscovery();
109
- const routes = await discovery.discoverLayouts('/admin');
110
- expect(routes[0].priority <= routes.at(-1)!.priority).toEqual(true);
111
- });
112
-
113
- it('should cache results on repeated calls', async () => {
114
- const discovery = makeDiscovery();
115
- const routes1 = await discovery.discoverLayouts('/admin');
116
- const routes2 = await discovery.discoverLayouts('/admin');
117
- expect(routes1).toBe(routes2); // same reference from cache
118
- });
119
-
120
- it('should assign depth 0 to root layout', async () => {
121
- const discovery = makeDiscovery();
122
- const routes = await discovery.discoverLayouts('/admin');
123
- const root = routes.find(r => r.type === 'root');
124
- expect(root?.depth).toEqual(0);
125
- });
126
-
127
- it('should assign correct depth to nested layouts', async () => {
128
- const discovery = makeDiscovery();
129
- const routes = await discovery.discoverLayouts('/admin');
130
- const nested = routes.find(r => r.type === 'nested');
131
- expect(nested?.depth).toEqual(1);
132
- });
133
- });
134
-
135
- describe('LayoutDiscovery - cache management', () => {
136
- beforeEach(setupFixtures);
137
- afterEach(cleanupFixtures);
138
-
139
- it('should report cache stats', async () => {
140
- const discovery = makeDiscovery();
141
- await discovery.discoverLayouts('/admin');
142
- const stats = discovery.getCacheStats();
143
- expect(stats.routeCacheCount >= 1).toEqual(true);
144
- });
145
-
146
- it('should clear all caches', async () => {
147
- const discovery = makeDiscovery();
148
- await discovery.discoverLayouts('/admin');
149
- discovery.clearCache();
150
- const stats = discovery.getCacheStats();
151
- expect(stats.layoutCount).toEqual(0);
152
- expect(stats.routeCacheCount).toEqual(0);
153
- });
154
-
155
- it('should clear layout cache for specific file', async () => {
156
- const discovery = makeDiscovery();
157
- await discovery.buildLayoutChain(new URL('http://localhost/admin'));
158
- const filePath = join(TEST_FIXTURES_DIR, '_layout.js');
159
- discovery.clearLayoutCache(filePath);
160
- // route cache should also be cleared
161
- const stats = discovery.getCacheStats();
162
- expect(stats.routeCacheCount).toEqual(0);
163
- });
164
- });
165
-
166
- describe('LayoutDiscovery - buildLayoutChain', () => {
167
- beforeEach(setupFixtures);
168
- afterEach(cleanupFixtures);
169
-
170
- it('should return an array of handlers', async () => {
171
- const discovery = makeDiscovery();
172
- const chain = await discovery.buildLayoutChain(new URL('http://localhost/admin'));
173
- expect(Array.isArray(chain)).toEqual(true);
174
- });
175
-
176
- it('should load root layout handler for root URL', async () => {
177
- const discovery = makeDiscovery();
178
- const chain = await discovery.buildLayoutChain(new URL('http://localhost/'));
179
- expect(chain.length >= 1).toEqual(true);
180
- expect(typeof chain[0].component).toEqual('function');
181
- });
182
-
183
- it('should include loader when layout exports layoutLoader', async () => {
184
- const discovery = makeDiscovery();
185
- const chain = await discovery.buildLayoutChain(new URL('http://localhost/'));
186
- const rootHandler = chain[0];
187
- expect(rootHandler.loader).toBeDefined();
188
- });
189
-
190
- it('should not include loader when layout has no layoutLoader export', async () => {
191
- const discovery = makeDiscovery();
192
- const chain = await discovery.buildLayoutChain(new URL('http://localhost/admin'));
193
- const adminHandler = chain.find(h => h.path.includes('admin'));
194
- expect(adminHandler?.loader).toBeUndefined();
195
- });
196
- });
197
-
198
- describe('LayoutDiscovery - buildLayoutChainWithData', () => {
199
- beforeEach(setupFixtures);
200
- afterEach(cleanupFixtures);
201
-
202
- it('should return handlers, data, and errors', async () => {
203
- const discovery = makeDiscovery();
204
- const ctx = {
205
- request: new Request('http://localhost/'),
206
- params: {},
207
- query: new URLSearchParams(),
208
- state: new Map<string, unknown>(),
209
- };
210
- const result = await discovery.buildLayoutChainWithData(new URL('http://localhost/'), ctx);
211
- expect(Array.isArray(result.handlers)).toEqual(true);
212
- expect(Array.isArray(result.data)).toEqual(true);
213
- expect(Array.isArray(result.errors)).toEqual(true);
214
- });
215
-
216
- it('should return data entry for each handler', async () => {
217
- const discovery = makeDiscovery();
218
- const ctx = {
219
- request: new Request('http://localhost/admin'),
220
- params: {},
221
- query: new URLSearchParams(),
222
- state: new Map<string, unknown>(),
223
- };
224
- const result = await discovery.buildLayoutChainWithData(new URL('http://localhost/admin'), ctx);
225
- expect(result.data.length).toEqual(result.handlers.length);
226
- });
227
-
228
- it('should populate data from layoutLoader', async () => {
229
- const discovery = makeDiscovery();
230
- const ctx = {
231
- request: new Request('http://localhost/'),
232
- params: {},
233
- query: new URLSearchParams(),
234
- state: new Map<string, unknown>(),
235
- };
236
- const result = await discovery.buildLayoutChainWithData(new URL('http://localhost/'), ctx);
237
- const rootData = result.data[0];
238
- expect(rootData).toBeDefined();
239
- expect((rootData as Record<string, unknown>).title).toEqual('Root');
240
- });
241
-
242
- it('should return empty errors array on success', async () => {
243
- const discovery = makeDiscovery();
244
- const ctx = {
245
- request: new Request('http://localhost/'),
246
- params: {},
247
- query: new URLSearchParams(),
248
- state: new Map<string, unknown>(),
249
- };
250
- const result = await discovery.buildLayoutChainWithData(new URL('http://localhost/'), ctx);
251
- expect(result.errors.length).toEqual(0);
252
- });
253
- });