@useavalon/avalon 0.1.4 → 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 (32) hide show
  1. package/package.json +31 -58
  2. package/src/vite-plugin/plugin.ts +22 -4
  3. package/src/build/README.md +0 -310
  4. package/src/client/tests/css-hmr-handler.test.ts +0 -360
  5. package/src/client/tests/framework-adapter.test.ts +0 -519
  6. package/src/client/tests/hmr-coordinator.test.ts +0 -176
  7. package/src/client/tests/hydration-option-parsing.test.ts +0 -107
  8. package/src/client/tests/lit-adapter.test.ts +0 -427
  9. package/src/client/tests/preact-adapter.test.ts +0 -353
  10. package/src/client/tests/qwik-adapter.test.ts +0 -343
  11. package/src/client/tests/react-adapter.test.ts +0 -317
  12. package/src/client/tests/solid-adapter.test.ts +0 -396
  13. package/src/client/tests/svelte-adapter.test.ts +0 -387
  14. package/src/client/tests/vue-adapter.test.ts +0 -407
  15. package/src/components/tests/component-analyzer.test.ts +0 -96
  16. package/src/components/tests/component-detection.test.ts +0 -347
  17. package/src/components/tests/persistent-islands.test.ts +0 -398
  18. package/src/core/components/tests/enhanced-framework-detector.test.ts +0 -577
  19. package/src/core/components/tests/framework-registry.test.ts +0 -465
  20. package/src/core/integrations/README.md +0 -282
  21. package/src/core/layout/tests/enhanced-layout-resolver.test.ts +0 -477
  22. package/src/core/layout/tests/layout-cache-optimization.test.ts +0 -149
  23. package/src/core/layout/tests/layout-composer.test.ts +0 -486
  24. package/src/core/layout/tests/layout-data-loader.test.ts +0 -443
  25. package/src/core/layout/tests/layout-discovery.test.ts +0 -253
  26. package/src/core/layout/tests/layout-matcher.test.ts +0 -480
  27. package/src/core/modules/tests/framework-module-resolver.test.ts +0 -263
  28. package/src/core/modules/tests/module-resolution-integration.test.ts +0 -117
  29. package/src/islands/discovery/tests/island-discovery.test.ts +0 -881
  30. package/src/middleware/__tests__/discovery.test.ts +0 -107
  31. package/src/types/tests/layout-types.test.ts +0 -197
  32. package/src/vite-plugin/tests/image-optimization.test.ts +0 -54
@@ -1,881 +0,0 @@
1
- import { mkdir, writeFile, rm } from 'node:fs/promises';
2
- /**
3
- * Tests for Island Discovery System
4
- *
5
- * Verifies the core discovery functionality for nested islands support.
6
- */
7
-
8
- import { describe, it, expect } from 'vitest';
9
- import { join, resolve } from 'node:path';
10
-
11
- import {
12
- discoverIslandDirectories,
13
- discoverIslandsInDirectory,
14
- discoverAllIslands,
15
- getQualifiedIslandName,
16
- parseQualifiedIslandName,
17
- } from "../scanner.ts";
18
- import { IslandRegistry, createIslandRegistry } from "../registry.ts";
19
- import { IslandResolver, createIslandResolver } from "../resolver.ts";
20
- import type { IslandDirectory, DiscoveredIsland } from "../types.ts";
21
-
22
- // Test fixtures directory
23
- const TEST_DIR = resolve("./test-islands-discovery-temp");
24
-
25
- async function setupTestDirectory(): Promise<void> {
26
- // Clean up if exists
27
- try {
28
- await rm(TEST_DIR, { recursive: true });
29
- } catch {
30
- // Ignore if doesn't exist
31
- }
32
-
33
- // Create test directory structure
34
- await mkdir(join(TEST_DIR, "src", "islands"), { recursive: true });
35
- await mkdir(join(TEST_DIR, "src", "modules", "auth", "islands"), { recursive: true });
36
- await mkdir(join(TEST_DIR, "src", "modules", "dashboard", "islands"), { recursive: true });
37
- await mkdir(join(TEST_DIR, "src", "features", "blog", "islands"), { recursive: true });
38
- }
39
-
40
- async function cleanupTestDirectory(): Promise<void> {
41
- try {
42
- await rm(TEST_DIR, { recursive: true });
43
- } catch {
44
- // Ignore cleanup errors
45
- }
46
- }
47
-
48
- async function createTestIsland(
49
- relativePath: string,
50
- content = "export default function Component() { return null; }"
51
- ): Promise<void> {
52
- const filePath = join(TEST_DIR, relativePath);
53
- await writeFile(filePath, content);
54
- }
55
-
56
- // ============================================================================
57
- // Scanner Tests
58
- // ============================================================================
59
-
60
- describe('Island Discovery - discovers default islands directory', () => {
61
- it('Island Discovery - discovers default islands directory', async () => {
62
- await setupTestDirectory();
63
- try {
64
- const directories = await discoverIslandDirectories(TEST_DIR);
65
-
66
- // Should find the default islands directory
67
- const defaultDir = directories.find(d => d.isDefault);
68
- expect(defaultDir).toBeDefined();
69
- expect(defaultDir!.relativePath).toEqual("islands");
70
- expect(defaultDir!.namespace).toEqual("");
71
- } finally {
72
- await cleanupTestDirectory();
73
- }
74
- });
75
- });
76
-
77
- describe('Island Discovery - discovers nested islands directories', () => {
78
- it('Island Discovery - discovers nested islands directories', async () => {
79
- await setupTestDirectory();
80
- try {
81
- const directories = await discoverIslandDirectories(TEST_DIR);
82
-
83
- // Should find all islands directories
84
- expect(directories.length).toEqual(4);
85
-
86
- // Check namespaces
87
- const namespaces = directories.map(d => d.namespace).sort();
88
- expect(namespaces).toEqual(["", "features/blog", "modules/auth", "modules/dashboard"]);
89
- } finally {
90
- await cleanupTestDirectory();
91
- }
92
- });
93
- });
94
-
95
- describe('Island Discovery - default directory has highest priority', () => {
96
- it('Island Discovery - default directory has highest priority', async () => {
97
- await setupTestDirectory();
98
- try {
99
- const directories = await discoverIslandDirectories(TEST_DIR);
100
-
101
- // Default directory should be first
102
- expect(directories[0].isDefault).toEqual(true);
103
- expect(directories[0].relativePath).toEqual("islands");
104
- } finally {
105
- await cleanupTestDirectory();
106
- }
107
- });
108
- });
109
-
110
- describe('Island Discovery - discovers island files in directory', () => {
111
- it('Island Discovery - discovers island files in directory', async () => {
112
- await setupTestDirectory();
113
- try {
114
- // Create test island files
115
- await createTestIsland("src/islands/Counter.tsx");
116
- await createTestIsland("src/islands/Button.tsx");
117
- await createTestIsland("src/islands/Card.vue");
118
-
119
- const directories = await discoverIslandDirectories(TEST_DIR);
120
- const defaultDir = directories.find(d => d.isDefault)!;
121
-
122
- const islands = await discoverIslandsInDirectory(defaultDir, TEST_DIR);
123
-
124
- expect(islands.length).toEqual(3);
125
-
126
- const names = islands.map(i => i.name).sort();
127
- expect(names).toEqual(["Button", "Card", "Counter"]);
128
- } finally {
129
- await cleanupTestDirectory();
130
- }
131
- });
132
- });
133
-
134
- describe('Island Discovery - detects framework from file extension', () => {
135
- it('Island Discovery - detects framework from file extension', async () => {
136
- await setupTestDirectory();
137
- try {
138
- await createTestIsland("src/islands/PreactCounter.tsx");
139
- await createTestIsland("src/islands/VueCounter.vue");
140
- await createTestIsland("src/islands/SvelteCounter.svelte");
141
- await createTestIsland("src/islands/SolidCounter.solid.tsx");
142
- await createTestIsland("src/islands/LitCounter.lit.ts");
143
-
144
- const directories = await discoverIslandDirectories(TEST_DIR);
145
- const defaultDir = directories.find(d => d.isDefault)!;
146
- const islands = await discoverIslandsInDirectory(defaultDir, TEST_DIR);
147
-
148
- const frameworks = new Map(islands.map(i => [i.name, i.framework]));
149
-
150
- expect(frameworks.get("PreactCounter")).toEqual("preact");
151
- expect(frameworks.get("VueCounter")).toEqual("vue");
152
- expect(frameworks.get("SvelteCounter")).toEqual("svelte");
153
- expect(frameworks.get("SolidCounter")).toEqual("solid");
154
- expect(frameworks.get("LitCounter")).toEqual("lit");
155
- } finally {
156
- await cleanupTestDirectory();
157
- }
158
- });
159
- });
160
-
161
- describe('Island Discovery - discovers all islands across directories', () => {
162
- it('Island Discovery - discovers all islands across directories', async () => {
163
- await setupTestDirectory();
164
- try {
165
- // Create islands in different directories
166
- await createTestIsland("src/islands/Counter.tsx");
167
- await createTestIsland("src/modules/auth/islands/LoginForm.tsx");
168
- await createTestIsland("src/modules/dashboard/islands/Chart.tsx");
169
- await createTestIsland("src/features/blog/islands/PostCard.tsx");
170
-
171
- const allIslands = await discoverAllIslands(TEST_DIR);
172
-
173
- expect(allIslands.length).toEqual(4);
174
-
175
- const names = allIslands.map(i => i.name).sort();
176
- expect(names).toEqual(["Chart", "Counter", "LoginForm", "PostCard"]);
177
- } finally {
178
- await cleanupTestDirectory();
179
- }
180
- });
181
- });
182
-
183
- // ============================================================================
184
- // Qualified Name Tests
185
- // ============================================================================
186
-
187
- describe('Island Discovery - qualified name for default directory', () => {
188
- it('Island Discovery - qualified name for default directory', async () => {
189
- await setupTestDirectory();
190
- try {
191
- await createTestIsland("src/islands/Counter.tsx");
192
-
193
- const allIslands = await discoverAllIslands(TEST_DIR);
194
- const counter = allIslands.find(i => i.name === "Counter")!;
195
-
196
- const qualifiedName = getQualifiedIslandName(counter);
197
- expect(qualifiedName).toEqual("Counter");
198
- } finally {
199
- await cleanupTestDirectory();
200
- }
201
- });
202
- });
203
-
204
- describe('Island Discovery - qualified name for nested directory', () => {
205
- it('Island Discovery - qualified name for nested directory', async () => {
206
- await setupTestDirectory();
207
- try {
208
- await createTestIsland("src/modules/auth/islands/LoginForm.tsx");
209
-
210
- const allIslands = await discoverAllIslands(TEST_DIR);
211
- const loginForm = allIslands.find(i => i.name === "LoginForm")!;
212
-
213
- const qualifiedName = getQualifiedIslandName(loginForm);
214
- expect(qualifiedName).toEqual("modules/auth/LoginForm");
215
- } finally {
216
- await cleanupTestDirectory();
217
- }
218
- });
219
- });
220
-
221
- describe('Island Discovery - parse qualified name', () => {
222
- it('Island Discovery - parse qualified name', () => {
223
- // Simple name
224
- let result = parseQualifiedIslandName("Counter");
225
- expect(result.namespace).toEqual("");
226
- expect(result.name).toEqual("Counter");
227
-
228
- // Qualified name
229
- result = parseQualifiedIslandName("modules/auth/LoginForm");
230
- expect(result.namespace).toEqual("modules/auth");
231
- expect(result.name).toEqual("LoginForm");
232
-
233
- // Deeply nested
234
- result = parseQualifiedIslandName("features/blog/posts/PostCard");
235
- expect(result.namespace).toEqual("features/blog/posts");
236
- expect(result.name).toEqual("PostCard");
237
- });
238
- });
239
-
240
- // ============================================================================
241
- // Registry Tests
242
- // ============================================================================
243
-
244
- describe('Island Registry - registers and resolves islands', () => {
245
- it('Island Registry - registers and resolves islands', async () => {
246
- await setupTestDirectory();
247
- try {
248
- await createTestIsland("src/islands/Counter.tsx");
249
- await createTestIsland("src/modules/auth/islands/LoginForm.tsx");
250
-
251
- const registry = await createIslandRegistry(TEST_DIR);
252
-
253
- expect(registry.size).toEqual(2);
254
-
255
- // Resolve by name
256
- const counter = registry.resolve("Counter");
257
- expect(counter).toBeDefined();
258
- expect(counter!.name).toEqual("Counter");
259
-
260
- // Resolve by qualified name
261
- const loginForm = registry.resolve("modules/auth/LoginForm");
262
- expect(loginForm).toBeDefined();
263
- expect(loginForm!.name).toEqual("LoginForm");
264
- } finally {
265
- await cleanupTestDirectory();
266
- }
267
- });
268
- });
269
-
270
- describe('Island Registry - detects collisions', () => {
271
- it('Island Registry - detects collisions', async () => {
272
- await setupTestDirectory();
273
- try {
274
- // Create islands with same name in different directories
275
- await createTestIsland("src/islands/Counter.tsx");
276
- await createTestIsland("src/modules/auth/islands/Counter.tsx");
277
- await createTestIsland("src/modules/dashboard/islands/Counter.tsx");
278
-
279
- const registry = await createIslandRegistry(TEST_DIR);
280
- const collisions = registry.detectCollisions();
281
-
282
- expect(collisions.length).toEqual(1);
283
- expect(collisions[0].name).toEqual("Counter");
284
- expect(collisions[0].islands.length).toEqual(3);
285
- } finally {
286
- await cleanupTestDirectory();
287
- }
288
- });
289
- });
290
-
291
- describe('Island Registry - resolves collisions with priority', () => {
292
- it('Island Registry - resolves collisions with priority', async () => {
293
- await setupTestDirectory();
294
- try {
295
- // Create islands with same name
296
- await createTestIsland("src/islands/Counter.tsx");
297
- await createTestIsland("src/modules/auth/islands/Counter.tsx");
298
-
299
- const registry = await createIslandRegistry(TEST_DIR);
300
-
301
- // Default directory should win
302
- const counter = registry.resolve("Counter");
303
- expect(counter).toBeDefined();
304
- expect(counter!.directory.isDefault).toEqual(true);
305
- } finally {
306
- await cleanupTestDirectory();
307
- }
308
- });
309
- });
310
-
311
- describe('Island Registry - finds all islands by name', () => {
312
- it('Island Registry - finds all islands by name', async () => {
313
- await setupTestDirectory();
314
- try {
315
- await createTestIsland("src/islands/Counter.tsx");
316
- await createTestIsland("src/modules/auth/islands/Counter.tsx");
317
-
318
- const registry = await createIslandRegistry(TEST_DIR);
319
- const matches = registry.findByName("Counter");
320
-
321
- expect(matches.length).toEqual(2);
322
- } finally {
323
- await cleanupTestDirectory();
324
- }
325
- });
326
- });
327
-
328
- // ============================================================================
329
- // Resolver Tests
330
- // ============================================================================
331
-
332
- describe('Island Resolver - resolves by name', () => {
333
- it('Island Resolver - resolves by name', async () => {
334
- await setupTestDirectory();
335
- try {
336
- await createTestIsland("src/islands/Counter.tsx");
337
-
338
- const registry = await createIslandRegistry(TEST_DIR);
339
- const resolver = createIslandResolver(registry, TEST_DIR);
340
-
341
- const result = resolver.resolve("Counter");
342
- expect(result).toBeDefined();
343
- expect(result!.island.name).toEqual("Counter");
344
- expect(result!.ambiguous).toEqual(false);
345
- } finally {
346
- await cleanupTestDirectory();
347
- }
348
- });
349
- });
350
-
351
- describe('Island Resolver - resolves by qualified name', () => {
352
- it('Island Resolver - resolves by qualified name', async () => {
353
- await setupTestDirectory();
354
- try {
355
- await createTestIsland("src/modules/auth/islands/LoginForm.tsx");
356
-
357
- const registry = await createIslandRegistry(TEST_DIR);
358
- const resolver = createIslandResolver(registry, TEST_DIR);
359
-
360
- const result = resolver.resolve("modules/auth/LoginForm");
361
- expect(result).toBeDefined();
362
- expect(result!.island.name).toEqual("LoginForm");
363
- expect(result!.island.namespace).toEqual("modules/auth");
364
- } finally {
365
- await cleanupTestDirectory();
366
- }
367
- });
368
- });
369
-
370
- describe('Island Resolver - marks ambiguous resolutions', () => {
371
- it('Island Resolver - marks ambiguous resolutions', async () => {
372
- await setupTestDirectory();
373
- try {
374
- await createTestIsland("src/islands/Counter.tsx");
375
- await createTestIsland("src/modules/auth/islands/Counter.tsx");
376
-
377
- const registry = await createIslandRegistry(TEST_DIR);
378
- const resolver = createIslandResolver(registry, TEST_DIR);
379
-
380
- const result = resolver.resolve("Counter");
381
- expect(result).toBeDefined();
382
- expect(result!.ambiguous).toEqual(true);
383
- expect(result!.alternatives).toBeDefined();
384
- expect(result!.alternatives!.length).toEqual(1);
385
- } finally {
386
- await cleanupTestDirectory();
387
- }
388
- });
389
- });
390
-
391
- describe('Island Resolver - generates import paths', () => {
392
- it('Island Resolver - generates import paths', async () => {
393
- await setupTestDirectory();
394
- try {
395
- await createTestIsland("src/islands/Counter.tsx");
396
-
397
- const registry = await createIslandRegistry(TEST_DIR);
398
- const resolver = createIslandResolver(registry, TEST_DIR);
399
-
400
- const result = resolver.resolve("Counter");
401
- expect(result).toBeDefined();
402
-
403
- // Import path should be relative
404
- expect(result!.importPath.includes("Counter.tsx")).toBeTruthy();
405
- } finally {
406
- await cleanupTestDirectory();
407
- }
408
- });
409
- });
410
-
411
- describe('Island Resolver - suggests qualified names for disambiguation', () => {
412
- it('Island Resolver - suggests qualified names for disambiguation', async () => {
413
- await setupTestDirectory();
414
- try {
415
- await createTestIsland("src/islands/Counter.tsx");
416
- await createTestIsland("src/modules/auth/islands/Counter.tsx");
417
-
418
- const registry = await createIslandRegistry(TEST_DIR);
419
- const resolver = createIslandResolver(registry, TEST_DIR);
420
-
421
- const suggestions = resolver.suggestQualifiedNames("Counter");
422
-
423
- expect(suggestions.length).toEqual(2);
424
- expect(suggestions.includes("Counter")).toBeTruthy();
425
- expect(suggestions.includes("modules/auth/Counter")).toBeTruthy();
426
- } finally {
427
- await cleanupTestDirectory();
428
- }
429
- });
430
- });
431
-
432
- describe('Island Resolver - provides resolution order documentation', () => {
433
- it('Island Resolver - provides resolution order documentation', async () => {
434
- await setupTestDirectory();
435
- try {
436
- const registry = await createIslandRegistry(TEST_DIR);
437
- const resolver = createIslandResolver(registry, TEST_DIR);
438
-
439
- const order = resolver.getResolutionOrder();
440
-
441
- expect(order.length > 0).toBeTruthy();
442
- expect(order.some(line => line.includes("default"))).toBeTruthy();
443
- } finally {
444
- await cleanupTestDirectory();
445
- }
446
- });
447
- });
448
-
449
- // ============================================================================
450
- // Edge Cases
451
- // ============================================================================
452
-
453
- describe('Island Discovery - handles empty directories', () => {
454
- it('Island Discovery - handles empty directories', async () => {
455
- await setupTestDirectory();
456
- try {
457
- // Don't create any island files
458
- const allIslands = await discoverAllIslands(TEST_DIR);
459
- expect(allIslands.length).toEqual(0);
460
- } finally {
461
- await cleanupTestDirectory();
462
- }
463
- });
464
- });
465
-
466
- describe('Island Discovery - handles nonexistent directory', () => {
467
- it('Island Discovery - handles nonexistent directory', async () => {
468
- const nonexistentDir = "/tmp/nonexistent_islands_" + Date.now();
469
-
470
- const directories = await discoverIslandDirectories(nonexistentDir);
471
- expect(directories.length).toEqual(0);
472
- });
473
- });
474
-
475
- describe('Island Discovery - ignores unsupported file extensions', () => {
476
- it('Island Discovery - ignores unsupported file extensions', async () => {
477
- await setupTestDirectory();
478
- try {
479
- await createTestIsland("src/islands/Counter.tsx");
480
- await createTestIsland("src/islands/styles.css", "/* CSS */");
481
- await createTestIsland("src/islands/README.md", "# README");
482
-
483
- const allIslands = await discoverAllIslands(TEST_DIR);
484
-
485
- // Should only find the .tsx file
486
- expect(allIslands.length).toEqual(1);
487
- expect(allIslands[0].name).toEqual("Counter");
488
- } finally {
489
- await cleanupTestDirectory();
490
- }
491
- });
492
- });
493
-
494
- describe('Island Discovery - excludes node_modules', () => {
495
- it('Island Discovery - excludes node_modules', async () => {
496
- await setupTestDirectory();
497
- try {
498
- // Create islands in node_modules (should be excluded)
499
- await mkdir(join(TEST_DIR, "src", "node_modules", "some-package", "islands"), { recursive: true });
500
- await createTestIsland("src/node_modules/some-package/islands/Counter.tsx");
501
-
502
- // Create a valid island
503
- await createTestIsland("src/islands/Button.tsx");
504
-
505
- const allIslands = await discoverAllIslands(TEST_DIR);
506
-
507
- // Should only find Button, not the one in node_modules
508
- expect(allIslands.length).toEqual(1);
509
- expect(allIslands[0].name).toEqual("Button");
510
- } finally {
511
- await cleanupTestDirectory();
512
- }
513
- });
514
- });
515
-
516
- // ============================================================================
517
- // Validator Tests
518
- // ============================================================================
519
-
520
- import {
521
- IslandValidator,
522
- createIslandValidator,
523
- validateAllIslands,
524
- formatValidationError,
525
- formatValidationWarning,
526
- formatCircularDependency,
527
- formatValidationResult,
528
- } from "../validator.ts";
529
-
530
- describe('Island Validator - validates valid component', () => {
531
- it('Island Validator - validates valid component', async () => {
532
- await setupTestDirectory();
533
- try {
534
- await createTestIsland("src/islands/Counter.tsx", `
535
- export default function Counter() {
536
- return <div>Counter</div>;
537
- }
538
- `);
539
-
540
- const validator = createIslandValidator(TEST_DIR);
541
- const filePath = join(TEST_DIR, "src/islands/Counter.tsx");
542
- const result = await validator.validateComponent(filePath);
543
-
544
- expect(result.valid).toEqual(true);
545
- expect(result.errors.length).toEqual(0);
546
- } finally {
547
- await cleanupTestDirectory();
548
- }
549
- });
550
- });
551
-
552
- describe('Island Validator - detects missing export', () => {
553
- it('Island Validator - detects missing export', async () => {
554
- await setupTestDirectory();
555
- try {
556
- await createTestIsland("src/islands/NoExport.tsx", `
557
- function NoExport() {
558
- return <div>No Export</div>;
559
- }
560
- `);
561
-
562
- const validator = createIslandValidator(TEST_DIR);
563
- const filePath = join(TEST_DIR, "src/islands/NoExport.tsx");
564
- const result = await validator.validateComponent(filePath);
565
-
566
- expect(result.valid).toEqual(false);
567
- expect(result.errors.length).toEqual(1);
568
- expect(result.errors[0].type).toEqual("invalid-export");
569
- } finally {
570
- await cleanupTestDirectory();
571
- }
572
- });
573
- });
574
-
575
- describe('Island Validator - validates Vue component', () => {
576
- it('Island Validator - validates Vue component', async () => {
577
- await setupTestDirectory();
578
- try {
579
- await createTestIsland("src/islands/VueCounter.vue", `
580
- <template>
581
- <div>{{ count }}</div>
582
- </template>
583
- <script setup>
584
- import { ref } from 'vue';
585
- const count = ref(0);
586
- </script>
587
- `);
588
-
589
- const validator = createIslandValidator(TEST_DIR);
590
- const filePath = join(TEST_DIR, "src/islands/VueCounter.vue");
591
- const result = await validator.validateComponent(filePath);
592
-
593
- expect(result.valid).toEqual(true);
594
- expect(result.errors.length).toEqual(0);
595
- } finally {
596
- await cleanupTestDirectory();
597
- }
598
- });
599
- });
600
-
601
- describe('Island Validator - validates Svelte component', () => {
602
- it('Island Validator - validates Svelte component', async () => {
603
- await setupTestDirectory();
604
- try {
605
- await createTestIsland("src/islands/SvelteCounter.svelte", `
606
- <script>
607
- let count = 0;
608
- </script>
609
- <button on:click={() => count++}>{count}</button>
610
- `);
611
-
612
- const validator = createIslandValidator(TEST_DIR);
613
- const filePath = join(TEST_DIR, "src/islands/SvelteCounter.svelte");
614
- const result = await validator.validateComponent(filePath);
615
-
616
- expect(result.valid).toEqual(true);
617
- expect(result.errors.length).toEqual(0);
618
- } finally {
619
- await cleanupTestDirectory();
620
- }
621
- });
622
- });
623
-
624
- describe('Island Validator - warns on lowercase component name', () => {
625
- it('Island Validator - warns on lowercase component name', async () => {
626
- await setupTestDirectory();
627
- try {
628
- await createTestIsland("src/islands/counter.tsx", `
629
- export default function counter() {
630
- return <div>Counter</div>;
631
- }
632
- `);
633
-
634
- const validator = createIslandValidator(TEST_DIR);
635
- const filePath = join(TEST_DIR, "src/islands/counter.tsx");
636
- const result = await validator.validateComponent(filePath);
637
-
638
- // Should pass but with warning
639
- expect(result.valid).toEqual(true);
640
- expect(result.warnings.length).toEqual(1);
641
- expect(result.warnings[0].type).toEqual("deprecated-pattern");
642
- expect(result.warnings[0].message.includes("PascalCase")).toBeTruthy();
643
- } finally {
644
- await cleanupTestDirectory();
645
- }
646
- });
647
- });
648
-
649
- describe('Island Validator - validates Lit component', () => {
650
- it('Island Validator - validates Lit component', async () => {
651
- await setupTestDirectory();
652
- try {
653
- await createTestIsland("src/islands/LitCounter.lit.ts", `
654
- import { LitElement, html } from 'lit';
655
- import { customElement } from 'lit/decorators.js';
656
-
657
- @customElement('lit-counter')
658
- export class LitCounter extends LitElement {
659
- render() {
660
- return html\`<div>Counter</div>\`;
661
- }
662
- }
663
- `);
664
-
665
- const validator = createIslandValidator(TEST_DIR);
666
- const filePath = join(TEST_DIR, "src/islands/LitCounter.lit.ts");
667
- const result = await validator.validateComponent(filePath);
668
-
669
- expect(result.valid).toEqual(true);
670
- expect(result.errors.length).toEqual(0);
671
- } finally {
672
- await cleanupTestDirectory();
673
- }
674
- });
675
- });
676
-
677
- describe('Island Validator - validates directory with empty warning', () => {
678
- it('Island Validator - validates directory with empty warning', async () => {
679
- await setupTestDirectory();
680
- try {
681
- const directories = await discoverIslandDirectories(TEST_DIR);
682
- const defaultDir = directories.find(d => d.isDefault)!;
683
-
684
- const validator = createIslandValidator(TEST_DIR);
685
- const result = await validator.validateDirectory(defaultDir);
686
-
687
- expect(result.valid).toEqual(true);
688
- expect(result.warnings.length).toEqual(1);
689
- expect(result.warnings[0].type).toEqual("empty-directory");
690
- } finally {
691
- await cleanupTestDirectory();
692
- }
693
- });
694
- });
695
-
696
- describe('Island Validator - validates naming convention', () => {
697
- it('Island Validator - validates naming convention', () => {
698
- const validator = createIslandValidator("/tmp");
699
-
700
- // Valid PascalCase
701
- let result = validator.validateNamingConvention("Counter", "/tmp/Counter.tsx");
702
- expect(result.valid).toEqual(true);
703
- expect(result.errors.length).toEqual(0);
704
- expect(result.warnings.length).toEqual(0);
705
-
706
- // Valid PascalCase with numbers
707
- result = validator.validateNamingConvention("Counter2", "/tmp/Counter2.tsx");
708
- expect(result.valid).toEqual(true);
709
-
710
- // Invalid - lowercase
711
- result = validator.validateNamingConvention("counter", "/tmp/counter.tsx");
712
- expect(result.valid).toEqual(true); // Still valid, just warning
713
- expect(result.warnings.length).toEqual(1);
714
-
715
- // Invalid - empty
716
- result = validator.validateNamingConvention("", "/tmp/.tsx");
717
- expect(result.valid).toEqual(false);
718
- expect(result.errors.length).toEqual(1);
719
- });
720
- });
721
-
722
- describe('Island Validator - detects circular dependencies', () => {
723
- it('Island Validator - detects circular dependencies', async () => {
724
- await setupTestDirectory();
725
- try {
726
- // Create islands with circular dependency: A -> B -> C -> A
727
- await createTestIsland("src/islands/ComponentA.tsx", `
728
- import ComponentB from './ComponentB';
729
- export default function ComponentA() {
730
- return <ComponentB />;
731
- }
732
- `);
733
- await createTestIsland("src/islands/ComponentB.tsx", `
734
- import ComponentC from './ComponentC';
735
- export default function ComponentB() {
736
- return <ComponentC />;
737
- }
738
- `);
739
- await createTestIsland("src/islands/ComponentC.tsx", `
740
- import ComponentA from './ComponentA';
741
- export default function ComponentC() {
742
- return <ComponentA />;
743
- }
744
- `);
745
-
746
- const allIslands = await discoverAllIslands(TEST_DIR);
747
- const validator = createIslandValidator(TEST_DIR);
748
- const cycles = await validator.detectCircularDependencies(allIslands);
749
-
750
- expect(cycles.length).toEqual(1);
751
- expect(cycles[0].cycle.length).toEqual(4); // A -> B -> C -> A (4 nodes including repeat)
752
- } finally {
753
- await cleanupTestDirectory();
754
- }
755
- });
756
- });
757
-
758
- describe('Island Validator - no circular dependencies for independent islands', () => {
759
- it('Island Validator - no circular dependencies for independent islands', async () => {
760
- await setupTestDirectory();
761
- try {
762
- await createTestIsland("src/islands/Counter.tsx", `
763
- export default function Counter() {
764
- return <div>Counter</div>;
765
- }
766
- `);
767
- await createTestIsland("src/islands/Button.tsx", `
768
- export default function Button() {
769
- return <button>Click</button>;
770
- }
771
- `);
772
-
773
- const allIslands = await discoverAllIslands(TEST_DIR);
774
- const validator = createIslandValidator(TEST_DIR);
775
- const cycles = await validator.detectCircularDependencies(allIslands);
776
-
777
- expect(cycles.length).toEqual(0);
778
- } finally {
779
- await cleanupTestDirectory();
780
- }
781
- });
782
- });
783
-
784
- describe('Island Validator - formatValidationError includes file path', () => {
785
- it('Island Validator - formatValidationError includes file path', () => {
786
- const error = {
787
- type: "invalid-export" as const,
788
- message: "No default export",
789
- filePath: "/project/src/islands/Counter.tsx",
790
- line: 1,
791
- column: 1,
792
- suggestion: "Add export default",
793
- };
794
-
795
- const formatted = formatValidationError(error, "/project");
796
-
797
- expect(formatted.includes("src/islands/Counter.tsx")).toBeTruthy();
798
- expect(formatted.includes("No default export")).toBeTruthy();
799
- expect(formatted.includes("Add export default")).toBeTruthy();
800
- });
801
- });
802
-
803
- describe('Island Validator - formatValidationWarning includes suggestion', () => {
804
- it('Island Validator - formatValidationWarning includes suggestion', () => {
805
- const warning = {
806
- type: "deprecated-pattern" as const,
807
- message: "Use PascalCase",
808
- filePath: "/project/src/islands/counter.tsx",
809
- suggestion: "Rename to Counter",
810
- };
811
-
812
- const formatted = formatValidationWarning(warning, "/project");
813
-
814
- expect(formatted.includes("Use PascalCase")).toBeTruthy();
815
- expect(formatted.includes("Rename to Counter")).toBeTruthy();
816
- });
817
- });
818
-
819
- describe('Island Validator - formatCircularDependency shows chain', () => {
820
- it('Island Validator - formatCircularDependency shows chain', () => {
821
- const circular = {
822
- cycle: ["/project/src/A.tsx", "/project/src/B.tsx", "/project/src/A.tsx"],
823
- description: "Circular dependency",
824
- };
825
-
826
- const formatted = formatCircularDependency(circular, "/project");
827
-
828
- expect(formatted.includes("src/A.tsx")).toBeTruthy();
829
- expect(formatted.includes("src/B.tsx")).toBeTruthy();
830
- expect(formatted.includes("Circular dependency")).toBeTruthy();
831
- });
832
- });
833
-
834
- describe('Island Validator - formatValidationResult shows summary', () => {
835
- it('Island Validator - formatValidationResult shows summary', () => {
836
- const result = {
837
- valid: false,
838
- errors: [{
839
- type: "invalid-export" as const,
840
- message: "No export",
841
- filePath: "/project/src/Counter.tsx",
842
- }],
843
- warnings: [{
844
- type: "deprecated-pattern" as const,
845
- message: "Use PascalCase",
846
- }],
847
- };
848
-
849
- const formatted = formatValidationResult(result, "/project");
850
-
851
- expect(formatted.includes("1 error")).toBeTruthy();
852
- expect(formatted.includes("1 warning")).toBeTruthy();
853
- expect(formatted.includes("Validation failed")).toBeTruthy();
854
- });
855
- });
856
-
857
- describe('Island Validator - validateAllIslands combines results', () => {
858
- it('Island Validator - validateAllIslands combines results', async () => {
859
- await setupTestDirectory();
860
- try {
861
- await createTestIsland("src/islands/ValidCounter.tsx", `
862
- export default function ValidCounter() {
863
- return <div>Counter</div>;
864
- }
865
- `);
866
- await createTestIsland("src/islands/InvalidNoExport.tsx", `
867
- function InvalidNoExport() {
868
- return <div>No Export</div>;
869
- }
870
- `);
871
-
872
- const allIslands = await discoverAllIslands(TEST_DIR);
873
- const result = await validateAllIslands(allIslands, TEST_DIR);
874
-
875
- expect(result.valid).toEqual(false);
876
- expect(result.errors.length).toEqual(1);
877
- } finally {
878
- await cleanupTestDirectory();
879
- }
880
- });
881
- });