@pyreon/compiler 0.24.5 → 0.24.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 +11 -13
- package/src/defer-inline.ts +0 -686
- package/src/event-names.ts +0 -65
- package/src/index.ts +0 -61
- package/src/island-audit.ts +0 -675
- package/src/jsx.ts +0 -2792
- package/src/load-native.ts +0 -156
- package/src/lpih.ts +0 -270
- package/src/manifest.ts +0 -280
- package/src/project-scanner.ts +0 -214
- package/src/pyreon-intercept.ts +0 -1029
- package/src/react-intercept.ts +0 -1217
- package/src/reactivity-lens.ts +0 -190
- package/src/ssg-audit.ts +0 -513
- package/src/test-audit.ts +0 -435
- package/src/tests/backend-parity-r7-r9.test.ts +0 -91
- package/src/tests/backend-prop-derived-callback-divergence.test.ts +0 -74
- package/src/tests/collapse-bail-census.test.ts +0 -330
- package/src/tests/collapse-key-source-hygiene.test.ts +0 -88
- package/src/tests/component-child-no-wrap.test.ts +0 -204
- package/src/tests/defer-inline.test.ts +0 -387
- package/src/tests/depth-stress.test.ts +0 -16
- package/src/tests/detector-tag-consistency.test.ts +0 -101
- package/src/tests/dynamic-collapse-detector.test.ts +0 -164
- package/src/tests/dynamic-collapse-emit.test.ts +0 -192
- package/src/tests/dynamic-collapse-scan.test.ts +0 -111
- package/src/tests/element-valued-const-child.test.ts +0 -61
- package/src/tests/falsy-child-characterization.test.ts +0 -48
- package/src/tests/island-audit.test.ts +0 -524
- package/src/tests/jsx.test.ts +0 -2908
- package/src/tests/load-native.test.ts +0 -53
- package/src/tests/lpih.test.ts +0 -404
- package/src/tests/malformed-input-resilience.test.ts +0 -50
- package/src/tests/manifest-snapshot.test.ts +0 -55
- package/src/tests/native-equivalence.test.ts +0 -924
- package/src/tests/partial-collapse-detector.test.ts +0 -121
- package/src/tests/partial-collapse-emit.test.ts +0 -104
- package/src/tests/partial-collapse-robustness.test.ts +0 -53
- package/src/tests/project-scanner.test.ts +0 -269
- package/src/tests/prop-derived-shadow.test.ts +0 -96
- package/src/tests/pure-call-reactive-args.test.ts +0 -50
- package/src/tests/pyreon-intercept.test.ts +0 -816
- package/src/tests/r13-callback-stmt-equivalence.test.ts +0 -58
- package/src/tests/r14-ssr-mode-parity.test.ts +0 -51
- package/src/tests/r15-elemconst-propderived.test.ts +0 -47
- package/src/tests/r19-defer-inline-robust.test.ts +0 -54
- package/src/tests/r20-backend-equivalence-sweep.test.ts +0 -50
- package/src/tests/react-intercept.test.ts +0 -1104
- package/src/tests/reactivity-lens.test.ts +0 -170
- package/src/tests/rocketstyle-collapse.test.ts +0 -208
- package/src/tests/runtime/control-flow.test.ts +0 -159
- package/src/tests/runtime/dom-properties.test.ts +0 -138
- package/src/tests/runtime/events.test.ts +0 -301
- package/src/tests/runtime/harness.ts +0 -94
- package/src/tests/runtime/pr-352-shapes.test.ts +0 -121
- package/src/tests/runtime/reactive-props.test.ts +0 -81
- package/src/tests/runtime/signals.test.ts +0 -129
- package/src/tests/runtime/whitespace.test.ts +0 -106
- package/src/tests/signal-autocall-shadow.test.ts +0 -86
- package/src/tests/sourcemap-fidelity.test.ts +0 -77
- package/src/tests/ssg-audit.test.ts +0 -402
- package/src/tests/static-text-baking.test.ts +0 -64
- package/src/tests/test-audit.test.ts +0 -549
- package/src/tests/transform-state-isolation.test.ts +0 -49
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fixture-based tests for `auditIslands` (PR C of the islands DX
|
|
3
|
-
* roadmap). Each test builds a synthetic monorepo at a tmp path with
|
|
4
|
-
* `packages/` + `examples/` subdirs containing minimal `island()` /
|
|
5
|
-
* `hydrateIslands()` source — then runs the audit and asserts the
|
|
6
|
-
* exact findings.
|
|
7
|
-
*
|
|
8
|
-
* Bisect-verification is done within the suite itself: for each of
|
|
9
|
-
* the 5 finding types, one test asserts the finding fires given the
|
|
10
|
-
* "broken" shape, AND a parallel test asserts NO finding fires given
|
|
11
|
-
* the "fixed" shape. If a future contributor disables the detector
|
|
12
|
-
* by mistake, the broken-shape test fails immediately.
|
|
13
|
-
*/
|
|
14
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
15
|
-
import { tmpdir } from 'node:os'
|
|
16
|
-
import { dirname, join } from 'node:path'
|
|
17
|
-
import { auditIslands, formatIslandAudit, type IslandFindingCode } from '../island-audit'
|
|
18
|
-
|
|
19
|
-
interface Fixture {
|
|
20
|
-
root: string
|
|
21
|
-
write: (relPath: string, body: string) => void
|
|
22
|
-
cleanup: () => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function makeFixture(): Fixture {
|
|
26
|
-
const root = mkdtempSync(join(tmpdir(), 'pyreon-island-audit-fixture-'))
|
|
27
|
-
mkdirSync(join(root, 'packages'), { recursive: true })
|
|
28
|
-
return {
|
|
29
|
-
root,
|
|
30
|
-
write: (relPath, body) => {
|
|
31
|
-
// Allow the caller to write under either `packages/` or `examples/`.
|
|
32
|
-
// If the relPath doesn't start with one of those, default to packages/.
|
|
33
|
-
const top = relPath.startsWith('packages/') || relPath.startsWith('examples/')
|
|
34
|
-
? relPath
|
|
35
|
-
: `packages/${relPath}`
|
|
36
|
-
const full = join(root, top)
|
|
37
|
-
mkdirSync(dirname(full), { recursive: true })
|
|
38
|
-
writeFileSync(full, body)
|
|
39
|
-
},
|
|
40
|
-
cleanup: () => rmSync(root, { recursive: true, force: true }),
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function findingCodes(result: ReturnType<typeof auditIslands>): IslandFindingCode[] {
|
|
45
|
-
return result.findings.map((f) => f.code)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
49
|
-
// Discovery
|
|
50
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
51
|
-
|
|
52
|
-
describe('auditIslands — discovery', () => {
|
|
53
|
-
it('returns root=null when no packages/ dir exists', () => {
|
|
54
|
-
const empty = mkdtempSync(join(tmpdir(), 'pyreon-island-audit-empty-'))
|
|
55
|
-
try {
|
|
56
|
-
const r = auditIslands(empty)
|
|
57
|
-
expect(r.root).toBeNull()
|
|
58
|
-
expect(r.findings).toEqual([])
|
|
59
|
-
expect(r.summary.filesScanned).toBe(0)
|
|
60
|
-
} finally {
|
|
61
|
-
rmSync(empty, { recursive: true, force: true })
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('walks packages/ AND examples/ for source files', () => {
|
|
66
|
-
const f = makeFixture()
|
|
67
|
-
try {
|
|
68
|
-
mkdirSync(join(f.root, 'examples'), { recursive: true })
|
|
69
|
-
f.write('packages/a/src/A.tsx', `export const A = () => null`)
|
|
70
|
-
f.write('examples/x/src/X.tsx', `export const X = () => null`)
|
|
71
|
-
const r = auditIslands(f.root)
|
|
72
|
-
// Two non-test source files
|
|
73
|
-
expect(r.summary.filesScanned).toBe(2)
|
|
74
|
-
} finally {
|
|
75
|
-
f.cleanup()
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('skips test files / __tests__ / node_modules / lib / dist', () => {
|
|
80
|
-
const f = makeFixture()
|
|
81
|
-
try {
|
|
82
|
-
f.write('a/src/Real.tsx', `export const A = () => null`)
|
|
83
|
-
f.write('a/src/Real.test.tsx', `it('x', () => {})`)
|
|
84
|
-
f.write('a/src/__tests__/foo.tsx', `export const F = () => null`)
|
|
85
|
-
f.write('a/lib/built.js', `export const Built = () => null`)
|
|
86
|
-
f.write('a/dist/out.js', `export const Out = () => null`)
|
|
87
|
-
const r = auditIslands(f.root)
|
|
88
|
-
expect(r.summary.filesScanned).toBe(1)
|
|
89
|
-
} finally {
|
|
90
|
-
f.cleanup()
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
96
|
-
// duplicate-name
|
|
97
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
98
|
-
|
|
99
|
-
describe('auditIslands — duplicate-name', () => {
|
|
100
|
-
it('flags two island() declarations with the same name (broken shape)', () => {
|
|
101
|
-
const f = makeFixture()
|
|
102
|
-
try {
|
|
103
|
-
f.write(
|
|
104
|
-
'a/src/A.tsx',
|
|
105
|
-
`import { island } from '@pyreon/server'
|
|
106
|
-
const Counter = island(() => import('./Counter'), { name: 'Counter', hydrate: 'load' })
|
|
107
|
-
export { Counter }`,
|
|
108
|
-
)
|
|
109
|
-
f.write(
|
|
110
|
-
'b/src/B.tsx',
|
|
111
|
-
`import { island } from '@pyreon/server'
|
|
112
|
-
const Counter = island(() => import('./CounterB'), { name: 'Counter', hydrate: 'load' })
|
|
113
|
-
export { Counter }`,
|
|
114
|
-
)
|
|
115
|
-
// Loader-target stub files so nested-island doesn't kick in
|
|
116
|
-
f.write('a/src/Counter.tsx', `export default () => null`)
|
|
117
|
-
f.write('b/src/CounterB.tsx', `export default () => null`)
|
|
118
|
-
const r = auditIslands(f.root)
|
|
119
|
-
const codes = findingCodes(r)
|
|
120
|
-
expect(codes).toContain('duplicate-name')
|
|
121
|
-
// Two declarations → two findings (each with the other in `related`)
|
|
122
|
-
expect(codes.filter((c) => c === 'duplicate-name')).toHaveLength(2)
|
|
123
|
-
const dup = r.findings.find((finding) => finding.code === 'duplicate-name')!
|
|
124
|
-
expect(dup.related).toBeDefined()
|
|
125
|
-
expect(dup.related).toHaveLength(1)
|
|
126
|
-
// Bisect-verify message content carries the conflicting name.
|
|
127
|
-
expect(dup.message).toContain('"Counter"')
|
|
128
|
-
} finally {
|
|
129
|
-
f.cleanup()
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('does NOT flag distinct names (fixed shape)', () => {
|
|
134
|
-
const f = makeFixture()
|
|
135
|
-
try {
|
|
136
|
-
f.write(
|
|
137
|
-
'a/src/A.tsx',
|
|
138
|
-
`import { island } from '@pyreon/server'
|
|
139
|
-
const A = island(() => import('./A1'), { name: 'A', hydrate: 'load' })
|
|
140
|
-
export { A }`,
|
|
141
|
-
)
|
|
142
|
-
f.write(
|
|
143
|
-
'b/src/B.tsx',
|
|
144
|
-
`import { island } from '@pyreon/server'
|
|
145
|
-
const B = island(() => import('./B1'), { name: 'B', hydrate: 'load' })
|
|
146
|
-
export { B }`,
|
|
147
|
-
)
|
|
148
|
-
f.write('a/src/A1.tsx', `export default () => null`)
|
|
149
|
-
f.write('b/src/B1.tsx', `export default () => null`)
|
|
150
|
-
const r = auditIslands(f.root)
|
|
151
|
-
expect(findingCodes(r)).not.toContain('duplicate-name')
|
|
152
|
-
} finally {
|
|
153
|
-
f.cleanup()
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
159
|
-
// never-with-registry-entry
|
|
160
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
161
|
-
|
|
162
|
-
describe('auditIslands — never-with-registry-entry', () => {
|
|
163
|
-
it('flags a never-strategy island that is also in the registry (broken shape)', () => {
|
|
164
|
-
const f = makeFixture()
|
|
165
|
-
try {
|
|
166
|
-
f.write(
|
|
167
|
-
'a/src/Static.tsx',
|
|
168
|
-
`import { island } from '@pyreon/server'
|
|
169
|
-
const Static = island(() => import('./StaticInner'), { name: 'Static', hydrate: 'never' })
|
|
170
|
-
export { Static }`,
|
|
171
|
-
)
|
|
172
|
-
f.write('a/src/StaticInner.tsx', `export default () => null`)
|
|
173
|
-
f.write(
|
|
174
|
-
'app/src/entry-client.ts',
|
|
175
|
-
`import { hydrateIslands } from '@pyreon/server/client'
|
|
176
|
-
hydrateIslands({
|
|
177
|
-
Static: () => import('../../a/src/Static'),
|
|
178
|
-
})`,
|
|
179
|
-
)
|
|
180
|
-
const r = auditIslands(f.root)
|
|
181
|
-
const codes = findingCodes(r)
|
|
182
|
-
expect(codes).toContain('never-with-registry-entry')
|
|
183
|
-
const finding = r.findings.find((x) => x.code === 'never-with-registry-entry')!
|
|
184
|
-
expect(finding.message).toContain('Static')
|
|
185
|
-
expect(finding.message).toContain("'never'")
|
|
186
|
-
expect(finding.related).toBeDefined()
|
|
187
|
-
expect(finding.related![0]!.relPath).toContain('Static.tsx')
|
|
188
|
-
} finally {
|
|
189
|
-
f.cleanup()
|
|
190
|
-
}
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('does NOT flag a registered non-never island (fixed shape)', () => {
|
|
194
|
-
const f = makeFixture()
|
|
195
|
-
try {
|
|
196
|
-
f.write(
|
|
197
|
-
'a/src/Counter.tsx',
|
|
198
|
-
`import { island } from '@pyreon/server'
|
|
199
|
-
const Counter = island(() => import('./CounterInner'), { name: 'Counter', hydrate: 'load' })
|
|
200
|
-
export { Counter }`,
|
|
201
|
-
)
|
|
202
|
-
f.write('a/src/CounterInner.tsx', `export default () => null`)
|
|
203
|
-
f.write(
|
|
204
|
-
'app/src/entry-client.ts',
|
|
205
|
-
`import { hydrateIslands } from '@pyreon/server/client'
|
|
206
|
-
hydrateIslands({ Counter: () => import('../../a/src/Counter') })`,
|
|
207
|
-
)
|
|
208
|
-
const r = auditIslands(f.root)
|
|
209
|
-
expect(findingCodes(r)).not.toContain('never-with-registry-entry')
|
|
210
|
-
} finally {
|
|
211
|
-
f.cleanup()
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
217
|
-
// registry-mismatch
|
|
218
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
219
|
-
|
|
220
|
-
describe('auditIslands — registry-mismatch', () => {
|
|
221
|
-
it('flags a registry key that has no matching island() declaration (broken shape)', () => {
|
|
222
|
-
const f = makeFixture()
|
|
223
|
-
try {
|
|
224
|
-
f.write(
|
|
225
|
-
'a/src/Counter.tsx',
|
|
226
|
-
`import { island } from '@pyreon/server'
|
|
227
|
-
export const Counter = island(() => import('./CounterInner'), { name: 'Counter', hydrate: 'load' })`,
|
|
228
|
-
)
|
|
229
|
-
f.write('a/src/CounterInner.tsx', `export default () => null`)
|
|
230
|
-
f.write(
|
|
231
|
-
'app/src/entry-client.ts',
|
|
232
|
-
`import { hydrateIslands } from '@pyreon/server/client'
|
|
233
|
-
hydrateIslands({
|
|
234
|
-
Counter: () => import('../../a/src/Counter'),
|
|
235
|
-
Counterr: () => import('../../a/src/Counter'), // typo
|
|
236
|
-
})`,
|
|
237
|
-
)
|
|
238
|
-
const r = auditIslands(f.root)
|
|
239
|
-
const mismatches = r.findings.filter((x) => x.code === 'registry-mismatch')
|
|
240
|
-
expect(mismatches).toHaveLength(1)
|
|
241
|
-
expect(mismatches[0]!.message).toContain('Counterr')
|
|
242
|
-
} finally {
|
|
243
|
-
f.cleanup()
|
|
244
|
-
}
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
it('does NOT flag when every key matches a declared name (fixed shape)', () => {
|
|
248
|
-
const f = makeFixture()
|
|
249
|
-
try {
|
|
250
|
-
f.write(
|
|
251
|
-
'a/src/A.tsx',
|
|
252
|
-
`import { island } from '@pyreon/server'
|
|
253
|
-
export const A = island(() => import('./AInner'), { name: 'A', hydrate: 'load' })`,
|
|
254
|
-
)
|
|
255
|
-
f.write('a/src/AInner.tsx', `export default () => null`)
|
|
256
|
-
f.write(
|
|
257
|
-
'app/src/entry-client.ts',
|
|
258
|
-
`import { hydrateIslands } from '@pyreon/server/client'
|
|
259
|
-
hydrateIslands({ A: () => import('../../a/src/A') })`,
|
|
260
|
-
)
|
|
261
|
-
const r = auditIslands(f.root)
|
|
262
|
-
expect(findingCodes(r)).not.toContain('registry-mismatch')
|
|
263
|
-
} finally {
|
|
264
|
-
f.cleanup()
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
270
|
-
// nested-island
|
|
271
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
272
|
-
|
|
273
|
-
describe('auditIslands — nested-island', () => {
|
|
274
|
-
it('flags an island whose loader-target file ALSO contains an island() (broken shape)', () => {
|
|
275
|
-
const f = makeFixture()
|
|
276
|
-
try {
|
|
277
|
-
f.write(
|
|
278
|
-
'a/src/Outer.tsx',
|
|
279
|
-
`import { island } from '@pyreon/server'
|
|
280
|
-
export const Outer = island(() => import('./Inner'), { name: 'Outer', hydrate: 'load' })`,
|
|
281
|
-
)
|
|
282
|
-
f.write(
|
|
283
|
-
'a/src/Inner.tsx',
|
|
284
|
-
`import { island } from '@pyreon/server'
|
|
285
|
-
export const Inner = island(() => import('./Innermost'), { name: 'Inner', hydrate: 'load' })`,
|
|
286
|
-
)
|
|
287
|
-
f.write('a/src/Innermost.tsx', `export default () => null`)
|
|
288
|
-
f.write(
|
|
289
|
-
'app/src/entry-client.ts',
|
|
290
|
-
`import { hydrateIslands } from '@pyreon/server/client'
|
|
291
|
-
hydrateIslands({
|
|
292
|
-
Outer: () => import('../../a/src/Outer'),
|
|
293
|
-
Inner: () => import('../../a/src/Inner'),
|
|
294
|
-
})`,
|
|
295
|
-
)
|
|
296
|
-
const r = auditIslands(f.root)
|
|
297
|
-
const nested = r.findings.filter((x) => x.code === 'nested-island')
|
|
298
|
-
expect(nested.length).toBeGreaterThanOrEqual(1)
|
|
299
|
-
// The outer's finding should reference Outer's location and
|
|
300
|
-
// related-link the inner's location.
|
|
301
|
-
const outerFinding = nested.find((x) => x.location.relPath.includes('Outer.tsx'))!
|
|
302
|
-
expect(outerFinding).toBeDefined()
|
|
303
|
-
expect(outerFinding.message).toContain('"Outer"')
|
|
304
|
-
expect(outerFinding.message).toContain('"Inner"')
|
|
305
|
-
expect(outerFinding.related).toBeDefined()
|
|
306
|
-
expect(outerFinding.related![0]!.relPath).toContain('Inner.tsx')
|
|
307
|
-
} finally {
|
|
308
|
-
f.cleanup()
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
it('does NOT flag a flat island whose target has no island() (fixed shape)', () => {
|
|
313
|
-
const f = makeFixture()
|
|
314
|
-
try {
|
|
315
|
-
f.write(
|
|
316
|
-
'a/src/Counter.tsx',
|
|
317
|
-
`import { island } from '@pyreon/server'
|
|
318
|
-
export const Counter = island(() => import('./CounterImpl'), { name: 'Counter', hydrate: 'load' })`,
|
|
319
|
-
)
|
|
320
|
-
f.write('a/src/CounterImpl.tsx', `export default () => null`)
|
|
321
|
-
const r = auditIslands(f.root)
|
|
322
|
-
expect(findingCodes(r)).not.toContain('nested-island')
|
|
323
|
-
} finally {
|
|
324
|
-
f.cleanup()
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
330
|
-
// dead-island
|
|
331
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
332
|
-
|
|
333
|
-
describe('auditIslands — dead-island', () => {
|
|
334
|
-
it('flags an island whose containing file is not imported by any other source (broken shape)', () => {
|
|
335
|
-
const f = makeFixture()
|
|
336
|
-
try {
|
|
337
|
-
f.write(
|
|
338
|
-
'a/src/Orphan.tsx',
|
|
339
|
-
`import { island } from '@pyreon/server'
|
|
340
|
-
export const Orphan = island(() => import('./OrphanImpl'), { name: 'Orphan', hydrate: 'load' })`,
|
|
341
|
-
)
|
|
342
|
-
f.write('a/src/OrphanImpl.tsx', `export default () => null`)
|
|
343
|
-
// Nothing imports Orphan.tsx — explicitly orphaned
|
|
344
|
-
const r = auditIslands(f.root)
|
|
345
|
-
const dead = r.findings.filter((x) => x.code === 'dead-island')
|
|
346
|
-
expect(dead).toHaveLength(1)
|
|
347
|
-
expect(dead[0]!.message).toContain('"Orphan"')
|
|
348
|
-
} finally {
|
|
349
|
-
f.cleanup()
|
|
350
|
-
}
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
it('does NOT flag an island whose file IS imported (fixed shape — static import)', () => {
|
|
354
|
-
const f = makeFixture()
|
|
355
|
-
try {
|
|
356
|
-
f.write(
|
|
357
|
-
'a/src/Counter.tsx',
|
|
358
|
-
`import { island } from '@pyreon/server'
|
|
359
|
-
export const Counter = island(() => import('./CounterImpl'), { name: 'Counter', hydrate: 'load' })`,
|
|
360
|
-
)
|
|
361
|
-
f.write('a/src/CounterImpl.tsx', `export default () => null`)
|
|
362
|
-
f.write(
|
|
363
|
-
'a/src/index.ts',
|
|
364
|
-
`export { Counter } from './Counter'`,
|
|
365
|
-
)
|
|
366
|
-
const r = auditIslands(f.root)
|
|
367
|
-
expect(findingCodes(r)).not.toContain('dead-island')
|
|
368
|
-
} finally {
|
|
369
|
-
f.cleanup()
|
|
370
|
-
}
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
it('does NOT flag an island whose file IS imported via dynamic import (auto-registry shape)', () => {
|
|
374
|
-
const f = makeFixture()
|
|
375
|
-
try {
|
|
376
|
-
f.write(
|
|
377
|
-
'a/src/Counter.tsx',
|
|
378
|
-
`import { island } from '@pyreon/server'
|
|
379
|
-
export const Counter = island(() => import('./CounterImpl'), { name: 'Counter', hydrate: 'load' })`,
|
|
380
|
-
)
|
|
381
|
-
f.write('a/src/CounterImpl.tsx', `export default () => null`)
|
|
382
|
-
// Auto-registry-style dynamic import (mirrors what the vite-plugin
|
|
383
|
-
// emits in the virtual module). Both `app/` and `a/` are under
|
|
384
|
-
// packages/, so the relative import path is `../../a/src/Counter`.
|
|
385
|
-
f.write(
|
|
386
|
-
'app/src/entry-client.ts',
|
|
387
|
-
`import { hydrateIslandsAuto } from '@pyreon/server/client'
|
|
388
|
-
const registry = { Counter: () => import('../../a/src/Counter') }
|
|
389
|
-
hydrateIslandsAuto(registry)`,
|
|
390
|
-
)
|
|
391
|
-
const r = auditIslands(f.root)
|
|
392
|
-
expect(findingCodes(r)).not.toContain('dead-island')
|
|
393
|
-
} finally {
|
|
394
|
-
f.cleanup()
|
|
395
|
-
}
|
|
396
|
-
})
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
400
|
-
// Formatter
|
|
401
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
402
|
-
|
|
403
|
-
describe('formatIslandAudit', () => {
|
|
404
|
-
it('returns a green-light message when no findings are present', () => {
|
|
405
|
-
const f = makeFixture()
|
|
406
|
-
try {
|
|
407
|
-
const r = auditIslands(f.root)
|
|
408
|
-
const text = formatIslandAudit(r)
|
|
409
|
-
expect(text).toContain('No island findings')
|
|
410
|
-
} finally {
|
|
411
|
-
f.cleanup()
|
|
412
|
-
}
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
it('groups findings by code and lists locations', () => {
|
|
416
|
-
const f = makeFixture()
|
|
417
|
-
try {
|
|
418
|
-
f.write(
|
|
419
|
-
'a/src/A.tsx',
|
|
420
|
-
`import { island } from '@pyreon/server'
|
|
421
|
-
const A = island(() => import('./AImpl'), { name: 'Dup', hydrate: 'load' })
|
|
422
|
-
export { A }`,
|
|
423
|
-
)
|
|
424
|
-
f.write(
|
|
425
|
-
'b/src/B.tsx',
|
|
426
|
-
`import { island } from '@pyreon/server'
|
|
427
|
-
const B = island(() => import('./BImpl'), { name: 'Dup', hydrate: 'load' })
|
|
428
|
-
export { B }`,
|
|
429
|
-
)
|
|
430
|
-
f.write('a/src/AImpl.tsx', `export default () => null`)
|
|
431
|
-
f.write('b/src/BImpl.tsx', `export default () => null`)
|
|
432
|
-
const r = auditIslands(f.root)
|
|
433
|
-
const text = formatIslandAudit(r)
|
|
434
|
-
expect(text).toContain('## duplicate-name')
|
|
435
|
-
// Both file locations appear in the human-readable output
|
|
436
|
-
expect(text).toContain('A.tsx')
|
|
437
|
-
expect(text).toContain('B.tsx')
|
|
438
|
-
} finally {
|
|
439
|
-
f.cleanup()
|
|
440
|
-
}
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
it('emits machine-readable JSON when options.json is true', () => {
|
|
444
|
-
const f = makeFixture()
|
|
445
|
-
try {
|
|
446
|
-
f.write(
|
|
447
|
-
'a/src/Orphan.tsx',
|
|
448
|
-
`import { island } from '@pyreon/server'
|
|
449
|
-
export const Orphan = island(() => import('./Inner'), { name: 'Orphan', hydrate: 'load' })`,
|
|
450
|
-
)
|
|
451
|
-
f.write('a/src/Inner.tsx', `export default () => null`)
|
|
452
|
-
const r = auditIslands(f.root)
|
|
453
|
-
const text = formatIslandAudit(r, { json: true })
|
|
454
|
-
const parsed = JSON.parse(text)
|
|
455
|
-
expect(parsed).toHaveProperty('findings')
|
|
456
|
-
expect(parsed).toHaveProperty('summary')
|
|
457
|
-
expect(parsed.summary.findingsByCode).toBeDefined()
|
|
458
|
-
} finally {
|
|
459
|
-
f.cleanup()
|
|
460
|
-
}
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
it('returns a useful error when invoked outside a monorepo', () => {
|
|
464
|
-
const empty = mkdtempSync(join(tmpdir(), 'pyreon-island-audit-no-monorepo-'))
|
|
465
|
-
try {
|
|
466
|
-
const r = auditIslands(empty)
|
|
467
|
-
const text = formatIslandAudit(r)
|
|
468
|
-
expect(text).toContain('No monorepo root found')
|
|
469
|
-
} finally {
|
|
470
|
-
rmSync(empty, { recursive: true, force: true })
|
|
471
|
-
}
|
|
472
|
-
})
|
|
473
|
-
})
|
|
474
|
-
|
|
475
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
476
|
-
// Integration — clean shape produces zero findings
|
|
477
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
478
|
-
|
|
479
|
-
describe('auditIslands — integration', () => {
|
|
480
|
-
it('produces zero findings on a clean realistic islands-showcase shape', () => {
|
|
481
|
-
const f = makeFixture()
|
|
482
|
-
try {
|
|
483
|
-
// Mirror examples/islands-showcase: 5 islands, one per strategy,
|
|
484
|
-
// all with distinct names, all imported via the auto-registry.
|
|
485
|
-
const decl = (name: string, hydrate: string) =>
|
|
486
|
-
`import { island } from '@pyreon/server'
|
|
487
|
-
export const ${name} = island(() => import('./${name}Impl'), { name: '${name}', hydrate: '${hydrate}' })`
|
|
488
|
-
|
|
489
|
-
f.write('isl/src/Counter.tsx', decl('Counter', 'load'))
|
|
490
|
-
f.write('isl/src/IdleClock.tsx', decl('IdleClock', 'idle'))
|
|
491
|
-
f.write('isl/src/Visible.tsx', decl('Visible', 'visible'))
|
|
492
|
-
f.write('isl/src/Mobile.tsx', decl('Mobile', 'media((max-width: 600px))'))
|
|
493
|
-
f.write('isl/src/Static.tsx', decl('Static', 'never'))
|
|
494
|
-
f.write('isl/src/CounterImpl.tsx', `export default () => null`)
|
|
495
|
-
f.write('isl/src/IdleClockImpl.tsx', `export default () => null`)
|
|
496
|
-
f.write('isl/src/VisibleImpl.tsx', `export default () => null`)
|
|
497
|
-
f.write('isl/src/MobileImpl.tsx', `export default () => null`)
|
|
498
|
-
f.write('isl/src/StaticImpl.tsx', `export default () => null`)
|
|
499
|
-
f.write(
|
|
500
|
-
'app/src/entry-client.ts',
|
|
501
|
-
`import { hydrateIslandsAuto } from '@pyreon/server/client'
|
|
502
|
-
const registry = {
|
|
503
|
-
// Static is correctly OMITTED — it's hydrate: 'never'
|
|
504
|
-
Counter: () => import('../../isl/src/Counter'),
|
|
505
|
-
IdleClock: () => import('../../isl/src/IdleClock'),
|
|
506
|
-
Visible: () => import('../../isl/src/Visible'),
|
|
507
|
-
Mobile: () => import('../../isl/src/Mobile'),
|
|
508
|
-
}
|
|
509
|
-
hydrateIslandsAuto(registry)`,
|
|
510
|
-
)
|
|
511
|
-
// Static is ALSO referenced (server-side), so it's not dead either.
|
|
512
|
-
f.write(
|
|
513
|
-
'app/src/server.ts',
|
|
514
|
-
`import { Static } from '../../isl/src/Static'
|
|
515
|
-
export { Static }`,
|
|
516
|
-
)
|
|
517
|
-
const r = auditIslands(f.root)
|
|
518
|
-
expect(r.summary.islandsDeclared).toBe(5)
|
|
519
|
-
expect(r.findings).toEqual([])
|
|
520
|
-
} finally {
|
|
521
|
-
f.cleanup()
|
|
522
|
-
}
|
|
523
|
-
})
|
|
524
|
-
})
|