@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.
- package/package.json +31 -58
- package/src/vite-plugin/plugin.ts +22 -4
- package/src/build/README.md +0 -310
- package/src/client/tests/css-hmr-handler.test.ts +0 -360
- package/src/client/tests/framework-adapter.test.ts +0 -519
- package/src/client/tests/hmr-coordinator.test.ts +0 -176
- package/src/client/tests/hydration-option-parsing.test.ts +0 -107
- package/src/client/tests/lit-adapter.test.ts +0 -427
- package/src/client/tests/preact-adapter.test.ts +0 -353
- package/src/client/tests/qwik-adapter.test.ts +0 -343
- package/src/client/tests/react-adapter.test.ts +0 -317
- package/src/client/tests/solid-adapter.test.ts +0 -396
- package/src/client/tests/svelte-adapter.test.ts +0 -387
- package/src/client/tests/vue-adapter.test.ts +0 -407
- package/src/components/tests/component-analyzer.test.ts +0 -96
- package/src/components/tests/component-detection.test.ts +0 -347
- package/src/components/tests/persistent-islands.test.ts +0 -398
- package/src/core/components/tests/enhanced-framework-detector.test.ts +0 -577
- package/src/core/components/tests/framework-registry.test.ts +0 -465
- package/src/core/integrations/README.md +0 -282
- package/src/core/layout/tests/enhanced-layout-resolver.test.ts +0 -477
- package/src/core/layout/tests/layout-cache-optimization.test.ts +0 -149
- package/src/core/layout/tests/layout-composer.test.ts +0 -486
- package/src/core/layout/tests/layout-data-loader.test.ts +0 -443
- package/src/core/layout/tests/layout-discovery.test.ts +0 -253
- package/src/core/layout/tests/layout-matcher.test.ts +0 -480
- package/src/core/modules/tests/framework-module-resolver.test.ts +0 -263
- package/src/core/modules/tests/module-resolution-integration.test.ts +0 -117
- package/src/islands/discovery/tests/island-discovery.test.ts +0 -881
- package/src/middleware/__tests__/discovery.test.ts +0 -107
- package/src/types/tests/layout-types.test.ts +0 -197
- 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
|
-
});
|