@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.
Files changed (64) hide show
  1. package/package.json +11 -13
  2. package/src/defer-inline.ts +0 -686
  3. package/src/event-names.ts +0 -65
  4. package/src/index.ts +0 -61
  5. package/src/island-audit.ts +0 -675
  6. package/src/jsx.ts +0 -2792
  7. package/src/load-native.ts +0 -156
  8. package/src/lpih.ts +0 -270
  9. package/src/manifest.ts +0 -280
  10. package/src/project-scanner.ts +0 -214
  11. package/src/pyreon-intercept.ts +0 -1029
  12. package/src/react-intercept.ts +0 -1217
  13. package/src/reactivity-lens.ts +0 -190
  14. package/src/ssg-audit.ts +0 -513
  15. package/src/test-audit.ts +0 -435
  16. package/src/tests/backend-parity-r7-r9.test.ts +0 -91
  17. package/src/tests/backend-prop-derived-callback-divergence.test.ts +0 -74
  18. package/src/tests/collapse-bail-census.test.ts +0 -330
  19. package/src/tests/collapse-key-source-hygiene.test.ts +0 -88
  20. package/src/tests/component-child-no-wrap.test.ts +0 -204
  21. package/src/tests/defer-inline.test.ts +0 -387
  22. package/src/tests/depth-stress.test.ts +0 -16
  23. package/src/tests/detector-tag-consistency.test.ts +0 -101
  24. package/src/tests/dynamic-collapse-detector.test.ts +0 -164
  25. package/src/tests/dynamic-collapse-emit.test.ts +0 -192
  26. package/src/tests/dynamic-collapse-scan.test.ts +0 -111
  27. package/src/tests/element-valued-const-child.test.ts +0 -61
  28. package/src/tests/falsy-child-characterization.test.ts +0 -48
  29. package/src/tests/island-audit.test.ts +0 -524
  30. package/src/tests/jsx.test.ts +0 -2908
  31. package/src/tests/load-native.test.ts +0 -53
  32. package/src/tests/lpih.test.ts +0 -404
  33. package/src/tests/malformed-input-resilience.test.ts +0 -50
  34. package/src/tests/manifest-snapshot.test.ts +0 -55
  35. package/src/tests/native-equivalence.test.ts +0 -924
  36. package/src/tests/partial-collapse-detector.test.ts +0 -121
  37. package/src/tests/partial-collapse-emit.test.ts +0 -104
  38. package/src/tests/partial-collapse-robustness.test.ts +0 -53
  39. package/src/tests/project-scanner.test.ts +0 -269
  40. package/src/tests/prop-derived-shadow.test.ts +0 -96
  41. package/src/tests/pure-call-reactive-args.test.ts +0 -50
  42. package/src/tests/pyreon-intercept.test.ts +0 -816
  43. package/src/tests/r13-callback-stmt-equivalence.test.ts +0 -58
  44. package/src/tests/r14-ssr-mode-parity.test.ts +0 -51
  45. package/src/tests/r15-elemconst-propderived.test.ts +0 -47
  46. package/src/tests/r19-defer-inline-robust.test.ts +0 -54
  47. package/src/tests/r20-backend-equivalence-sweep.test.ts +0 -50
  48. package/src/tests/react-intercept.test.ts +0 -1104
  49. package/src/tests/reactivity-lens.test.ts +0 -170
  50. package/src/tests/rocketstyle-collapse.test.ts +0 -208
  51. package/src/tests/runtime/control-flow.test.ts +0 -159
  52. package/src/tests/runtime/dom-properties.test.ts +0 -138
  53. package/src/tests/runtime/events.test.ts +0 -301
  54. package/src/tests/runtime/harness.ts +0 -94
  55. package/src/tests/runtime/pr-352-shapes.test.ts +0 -121
  56. package/src/tests/runtime/reactive-props.test.ts +0 -81
  57. package/src/tests/runtime/signals.test.ts +0 -129
  58. package/src/tests/runtime/whitespace.test.ts +0 -106
  59. package/src/tests/signal-autocall-shadow.test.ts +0 -86
  60. package/src/tests/sourcemap-fidelity.test.ts +0 -77
  61. package/src/tests/ssg-audit.test.ts +0 -402
  62. package/src/tests/static-text-baking.test.ts +0 -64
  63. package/src/tests/test-audit.test.ts +0 -549
  64. package/src/tests/transform-state-isolation.test.ts +0 -49
@@ -1,192 +0,0 @@
1
- /**
2
- * PR 3 of the dynamic-prop partial-collapse build — the compiler EMIT
3
- * half: `tryRocketstyleCollapse` falls through to `tryDynamicCollapse`
4
- * when BOTH the full and the `on*`-handler-partial paths bail. Emits
5
- * `__rsCollapseDyn(html, [stride-2 classes], () => cond ? 0 : 1, () =>
6
- * __pyrMode() === "dark")` consumed by PR 1's runtime helper `_rsCollapseDyn`
7
- * (#765).
8
- *
9
- * Mirrors the existing `partial-collapse-emit.test.ts` harness exactly
10
- * (stubbed resolved-`sites` map — the resolver/plugin scan is PR 4's
11
- * gate; this proves the emit contract in isolation).
12
- *
13
- * Bisect-verify (PR body): revert the fallback chain
14
- * (`return tryPartialCollapse(...) || tryDynamicCollapse(...)` →
15
- * `return tryPartialCollapse(...)`) → the dynamic-emit specs fail
16
- * (`__rsCollapseDyn(` absent) while the FULL + PARTIAL specs still
17
- * pass (proving the dynamic fallthrough is the only delta).
18
- * Restore → all pass.
19
- */
20
- import { describe, expect, it } from 'vitest'
21
- import { rocketstyleCollapseKey, transformJSX } from '../jsx'
22
-
23
- // Per-value resolved sites — the dynamic emit looks up BOTH literals
24
- // via separate keys. `templateHtml` MUST be byte-identical across
25
- // values for the dispatcher to share one `_tpl` (cross-value template
26
- // parity bail).
27
- const TPL = '<button>Save</button>'
28
-
29
- const PRIMARY = {
30
- templateHtml: TPL,
31
- lightClass: 'pyr-pri-L',
32
- darkClass: 'pyr-pri-D',
33
- rules: ['.pyr-pri-L{color:red}', '.pyr-pri-D{color:darkred}'],
34
- ruleKey: 'bundle-pri',
35
- }
36
- const SECONDARY = {
37
- templateHtml: TPL,
38
- lightClass: 'pyr-sec-L',
39
- darkClass: 'pyr-sec-D',
40
- rules: ['.pyr-sec-L{color:blue}', '.pyr-sec-D{color:darkblue}'],
41
- ruleKey: 'bundle-sec',
42
- }
43
-
44
- function collapseOpt(
45
- candidates: string[],
46
- sites: Record<string, { templateHtml: string; lightClass: string; darkClass: string; rules: string[]; ruleKey: string }>,
47
- ) {
48
- return {
49
- collapseRocketstyle: {
50
- candidates: new Set(candidates),
51
- sites: new Map(Object.entries(sites)),
52
- mode: { name: 'useMode', source: '@pyreon/ui-core' },
53
- },
54
- }
55
- }
56
-
57
- describe('compiler — dynamic-prop collapse emission (PR 3, ternary-of-two-literals)', () => {
58
- it('emits __rsCollapseDyn with stride-2 value-major classes + cond dispatcher', () => {
59
- const truthyKey = rocketstyleCollapseKey('Button', { state: 'primary', size: 'medium' }, 'Save')
60
- const falsyKey = rocketstyleCollapseKey('Button', { state: 'secondary', size: 'medium' }, 'Save')
61
- const src =
62
- 'const x = <Button state={isPrimary ? "primary" : "secondary"} size="medium">Save</Button>'
63
- const { code } = transformJSX(
64
- src,
65
- 'A.tsx',
66
- collapseOpt(['Button'], { [truthyKey]: PRIMARY, [falsyKey]: SECONDARY }),
67
- )
68
-
69
- // Stride-2 value-major class layout: `[v0_light, v0_dark, v1_light, v1_dark]`.
70
- // v0 = consequent (cond → 0), v1 = alternate (cond → 1) — matches
71
- // `_rsCollapseDyn` doc + bisect-verified in PR 1 (#765).
72
- expect(code).toContain(
73
- '__rsCollapseDyn("<button>Save</button>", ' +
74
- '["pyr-pri-L","pyr-pri-D","pyr-sec-L","pyr-sec-D"], ' +
75
- '() => (isPrimary) ? 0 : 1, ' +
76
- '() => __pyrMode() === "dark")',
77
- )
78
- })
79
-
80
- it('imports the _rsCollapseDyn helper (and NOT the full / H helpers when not used)', () => {
81
- const truthyKey = rocketstyleCollapseKey('Button', { state: 'primary' }, 'Go')
82
- const falsyKey = rocketstyleCollapseKey('Button', { state: 'secondary' }, 'Go')
83
- const src = 'const x = <Button state={c ? "primary" : "secondary"}>Go</Button>'
84
- const { code } = transformJSX(
85
- src,
86
- 'B.tsx',
87
- collapseOpt(['Button'], { [truthyKey]: PRIMARY, [falsyKey]: SECONDARY }),
88
- )
89
- // Conditional import — dynamic-only modules pull `_rsCollapseDyn`
90
- // ONLY (tree-shake-friendly per-feature granularity).
91
- expect(code).toContain('import { _rsCollapseDyn as __rsCollapseDyn } from "@pyreon/runtime-dom";')
92
- expect(code).not.toContain('_rsCollapse as __rsCollapse')
93
- expect(code).not.toContain('_rsCollapseH as __rsCollapseH')
94
- })
95
-
96
- it('unions BOTH values\' rule bundles via injectRules (de-duped by ruleKey)', () => {
97
- const truthyKey = rocketstyleCollapseKey('Button', { state: 'primary' }, 'X')
98
- const falsyKey = rocketstyleCollapseKey('Button', { state: 'secondary' }, 'X')
99
- const src = 'const x = <Button state={c ? "primary" : "secondary"}>X</Button>'
100
- const { code } = transformJSX(
101
- src,
102
- 'C.tsx',
103
- collapseOpt(['Button'], { [truthyKey]: PRIMARY, [falsyKey]: SECONDARY }),
104
- )
105
- // Each value's rule bundle injected separately (different ruleKeys
106
- // ⇒ no dedupe). Same idempotent injectRules contract as the full
107
- // path — styler dedupes by key at runtime.
108
- expect(code).toContain('"bundle-pri"')
109
- expect(code).toContain('"bundle-sec"')
110
- expect(code).toContain('__rsSheet.injectRules(')
111
- })
112
-
113
- it('preserves complex cond source verbatim (paren-wrapped for safe re-emission)', () => {
114
- const truthyKey = rocketstyleCollapseKey('Btn', { state: 'primary' }, 'Hi')
115
- const falsyKey = rocketstyleCollapseKey('Btn', { state: 'danger' }, 'Hi')
116
- const src = 'const x = <Btn state={user.role === "admin" ? "primary" : "danger"}>Hi</Btn>'
117
- const { code } = transformJSX(
118
- src,
119
- 'D.tsx',
120
- collapseOpt(['Btn'], {
121
- [truthyKey]: { ...PRIMARY },
122
- [falsyKey]: { ...SECONDARY, lightClass: 'pyr-dng-L', darkClass: 'pyr-dng-D' },
123
- }),
124
- )
125
- // The paren-wrap (`(user.role === "admin")`) keeps the cond a single
126
- // expression — same shape as the on*-handler emit re-emits arrow bodies.
127
- expect(code).toContain('() => (user.role === "admin") ? 0 : 1')
128
- })
129
-
130
- // ── Conservative-bail discipline ───────────────────────────────────────
131
- it('BAILS when EITHER expanded site is missing from the resolved map', () => {
132
- const truthyKey = rocketstyleCollapseKey('Button', { state: 'primary' }, 'S')
133
- // Only the truthy half resolved — falsy is absent (resolver returned
134
- // null for that variant). Half-resolved ⇒ keep the normal mount.
135
- const src = 'const x = <Button state={c ? "primary" : "secondary"}>S</Button>'
136
- const { code } = transformJSX(
137
- src,
138
- 'E.tsx',
139
- collapseOpt(['Button'], { [truthyKey]: PRIMARY }),
140
- )
141
- expect(code).not.toContain('__rsCollapseDyn(')
142
- // Normal mount preserved — `<Button …>` JSX still appears (or its
143
- // standard `h()` form post-transform).
144
- })
145
-
146
- it('BAILS when the structural template diverges across values', () => {
147
- const truthyKey = rocketstyleCollapseKey('Button', { state: 'primary' }, 'D')
148
- const falsyKey = rocketstyleCollapseKey('Button', { state: 'secondary' }, 'D')
149
- const src = 'const x = <Button state={c ? "primary" : "secondary"}>D</Button>'
150
- const { code } = transformJSX(
151
- src,
152
- 'F.tsx',
153
- collapseOpt(['Button'], {
154
- [truthyKey]: { ...PRIMARY, templateHtml: '<button>D</button>' },
155
- [falsyKey]: { ...SECONDARY, templateHtml: '<button data-extra>D</button>' }, // divergent
156
- }),
157
- )
158
- expect(code).not.toContain('__rsCollapseDyn(')
159
- })
160
-
161
- it('BAILS when the dynamic site ALSO has on*-handlers (PR 3 scope: no-handler only)', () => {
162
- const truthyKey = rocketstyleCollapseKey('Button', { state: 'primary' }, 'H')
163
- const falsyKey = rocketstyleCollapseKey('Button', { state: 'secondary' }, 'H')
164
- const src =
165
- 'const x = <Button state={c ? "primary" : "secondary"} onClick={go}>H</Button>'
166
- const { code } = transformJSX(
167
- src,
168
- 'G.tsx',
169
- collapseOpt(['Button'], { [truthyKey]: PRIMARY, [falsyKey]: SECONDARY }),
170
- )
171
- expect(code).not.toContain('__rsCollapseDyn(')
172
- expect(code).not.toContain('__rsCollapseH(')
173
- })
174
-
175
- // ── Regression: FULL + on*-PARTIAL paths byte-unchanged ────────────────
176
- it('FULL-collapse path byte-unchanged: no-handler literal site emits plain __rsCollapse', () => {
177
- const key = rocketstyleCollapseKey('Button', { state: 'primary' }, 'Save')
178
- const src = 'const x = <Button state="primary">Save</Button>'
179
- const { code } = transformJSX(src, 'H.tsx', collapseOpt(['Button'], { [key]: PRIMARY }))
180
- expect(code).toContain('__rsCollapse(')
181
- expect(code).not.toContain('__rsCollapseH(')
182
- expect(code).not.toContain('__rsCollapseDyn(')
183
- })
184
-
185
- it('PARTIAL on*-handler path byte-unchanged: literal site + handler emits __rsCollapseH', () => {
186
- const key = rocketstyleCollapseKey('Button', { state: 'primary' }, 'Save')
187
- const src = 'const x = <Button state="primary" onClick={go}>Save</Button>'
188
- const { code } = transformJSX(src, 'I.tsx', collapseOpt(['Button'], { [key]: PRIMARY }))
189
- expect(code).toContain('__rsCollapseH(')
190
- expect(code).not.toContain('__rsCollapseDyn(')
191
- })
192
- })
@@ -1,111 +0,0 @@
1
- /**
2
- * PR 3 of the dynamic-prop partial-collapse build — `scanCollapsibleSites`
3
- * extension. The plugin-side scan (`@pyreon/vite-plugin`) calls this to
4
- * learn WHICH (component, props, text) tuples need resolution. For
5
- * dynamic-prop sites it must expand into TWO `CollapsibleSite` entries
6
- * (one per literal value) so the resolver pre-renders both via the
7
- * existing SSR pipeline AND the compiler emit (PR 3 `tryDynamicCollapse`)
8
- * looks up both via identical key construction.
9
- *
10
- * Key invariant: the keys this scan emits must EQUAL the keys
11
- * `tryDynamicCollapse` computes from the same JSX — same load-bearing
12
- * separation as the existing full / on*-handler scan↔emit invariants
13
- * (`scanCollapsibleSites` ↔ `detectCollapsibleShape`).
14
- */
15
- import { describe, expect, it } from 'vitest'
16
- import { rocketstyleCollapseKey, scanCollapsibleSites } from '../jsx'
17
-
18
- const COLLAPSIBLE = new Set(['@pyreon/ui-components'])
19
-
20
- describe('scanCollapsibleSites — dynamic-prop expansion (PR 3)', () => {
21
- it('expands a single ternary site into TWO CollapsibleSite entries (one per literal)', () => {
22
- const src = `
23
- import { Button } from '@pyreon/ui-components'
24
- const x = <Button state={cond ? "primary" : "secondary"} size="medium">Save</Button>
25
- `
26
- const sites = scanCollapsibleSites(src, 'A.tsx', COLLAPSIBLE)
27
- expect(sites).toHaveLength(2)
28
- const byState = new Map(sites.map((s) => [s.props.state, s]))
29
- expect(byState.get('primary')!.props).toEqual({ state: 'primary', size: 'medium' })
30
- expect(byState.get('secondary')!.props).toEqual({ state: 'secondary', size: 'medium' })
31
- expect(byState.get('primary')!.childrenText).toBe('Save')
32
- expect(byState.get('secondary')!.childrenText).toBe('Save')
33
- // Both entries share the same component / source / importedName —
34
- // only `props.state` differs.
35
- expect(byState.get('primary')!.componentName).toBe('Button')
36
- expect(byState.get('secondary')!.componentName).toBe('Button')
37
- })
38
-
39
- it('emits keys IDENTICAL to what tryDynamicCollapse will look up', () => {
40
- // The cross-detector invariant: the scan and the emit MUST agree
41
- // on the key for each expanded value. Drift would cause the emit
42
- // to miss its own pre-resolved sites and bail to normal mount.
43
- const src = `
44
- import { Button } from '@pyreon/ui-components'
45
- const x = <Button state={c ? "primary" : "secondary"}>Go</Button>
46
- `
47
- const sites = scanCollapsibleSites(src, 'B.tsx', COLLAPSIBLE)
48
- expect(sites).toHaveLength(2)
49
- const truthyKey = rocketstyleCollapseKey('Button', { state: 'primary' }, 'Go')
50
- const falsyKey = rocketstyleCollapseKey('Button', { state: 'secondary' }, 'Go')
51
- const keys = sites.map((s) => s.key).sort()
52
- expect(keys).toEqual([truthyKey, falsyKey].sort())
53
- })
54
-
55
- it('EXPANDS handler-combined dynamic sites too (handler-combined emit unlocks the 15.4% bucket)', () => {
56
- // The follow-up emit (`__rsCollapseDynH` from `tryDynamicCollapse`)
57
- // handles ternary-plus-handler sites via the combined runtime
58
- // helper. The scan emits both literal-value expansions identically
59
- // — handlers don't affect the resolver's input (componentName,
60
- // props, childrenText). Handlers are re-attached by the runtime
61
- // helper, not by the resolver.
62
- //
63
- // Previously the scan SKIPPED handler-combined sites (matching
64
- // the PR 3 emit's no-handler scope); the follow-up lifts that
65
- // restriction so the resolver pre-renders both values for
66
- // handler-bearing sites too.
67
- const src = `
68
- import { Button } from '@pyreon/ui-components'
69
- const x = <Button state={c ? "primary" : "secondary"} onClick={go}>H</Button>
70
- `
71
- const sites = scanCollapsibleSites(src, 'C.tsx', COLLAPSIBLE)
72
- expect(sites).toHaveLength(2)
73
- const byState = new Map(sites.map((s) => [s.props.state, s]))
74
- expect(byState.get('primary')!.childrenText).toBe('H')
75
- expect(byState.get('secondary')!.childrenText).toBe('H')
76
- })
77
-
78
- it('does not double-emit when the FULL detector already claims the site (literal-only)', () => {
79
- // A fully-literal site is the FULL-collapse shape — claimed by
80
- // detectCollapsibleShape; the dynamic-fallthrough branch in the
81
- // scan only runs when the full detector returned null.
82
- const src = `
83
- import { Button } from '@pyreon/ui-components'
84
- const x = <Button state="primary" size="medium">Save</Button>
85
- `
86
- const sites = scanCollapsibleSites(src, 'D.tsx', COLLAPSIBLE)
87
- expect(sites).toHaveLength(1)
88
- expect(sites[0]!.props).toEqual({ state: 'primary', size: 'medium' })
89
- })
90
-
91
- it('emits 4 entries for a module with 2 ternary sites (no dedupe across distinct sites)', () => {
92
- const src = `
93
- import { Button } from '@pyreon/ui-components'
94
- const a = <Button state={c1 ? "primary" : "secondary"}>A</Button>
95
- const b = <Button state={c2 ? "danger" : "success"}>B</Button>
96
- `
97
- const sites = scanCollapsibleSites(src, 'E.tsx', COLLAPSIBLE)
98
- expect(sites).toHaveLength(4)
99
- const states = sites.map((s) => `${s.props.state}/${s.childrenText}`).sort()
100
- expect(states).toEqual(['danger/B', 'primary/A', 'secondary/A', 'success/B'])
101
- })
102
-
103
- it('skips multi-ternary site entirely (separable scope, not this PR)', () => {
104
- const src = `
105
- import { Button } from '@pyreon/ui-components'
106
- const x = <Button state={a ? "x" : "y"} size={b ? "small" : "large"}>S</Button>
107
- `
108
- const sites = scanCollapsibleSites(src, 'F.tsx', COLLAPSIBLE)
109
- expect(sites).toHaveLength(0)
110
- })
111
- })
@@ -1,61 +0,0 @@
1
- /**
2
- * Compiler hardening — Round 9 (REAL bug, FIXED + bisect-verified).
3
- *
4
- * const header = <h1>T</h1>
5
- * return <div>{header}<p>x</p></div>
6
- *
7
- * Pre-fix the compiler lowered the const to `_tpl(...)` (so it KNEW `header`
8
- * was a `NativeItem` element) yet still emitted
9
- * `document.createTextNode(header)` for the `{header}` child — `createTextNode`
10
- * string-coerces the NativeItem → "[object Object]" instead of the `<h1>`.
11
- * Only `props.children` / `own.children` reached the correct `_mountSlot`.
12
- *
13
- * Fix (jsx.ts): an `elementVars` set tracks `const`/`let` bindings whose
14
- * initializer is a JSX element/fragment (optionally parenthesized); a bare
15
- * `{el}` child of such a binding routes through `_mountSlot` — the same
16
- * general child-insert `props.children` uses. Tight by construction: only a
17
- * DIRECT JSX initializer reclassifies, so string/number/prop-derived/inline-
18
- * hoisted children keep their existing (correct) paths. Routing is safe even
19
- * under later same-name shadowing — `_mountSlot` renders strings/numbers
20
- * correctly too; the only cost of imprecision is skipping the text fast path.
21
- *
22
- * NOT contradicted by `jsx.test.ts:777` `createTextNode(label)` — that pins
23
- * the FREE undeclared identifier default (genuinely ambiguous); this fix only
24
- * fires when the binding's initializer is provably JSX.
25
- *
26
- * Bisect: revert the `isElementValuedIdent` clause in `processOneChild`
27
- * (jsx.ts) → the CONTRACT specs fail (emit reverts to `createTextNode(header)`)
28
- * while every CONTROL spec stays green (proving the fix doesn't touch the
29
- * text/reactive fast paths). Restore → all pass.
30
- */
31
- import { describe, expect, it } from 'vitest'
32
- import { transformJSX_JS } from '../jsx'
33
-
34
- const emit = (c: string): string => transformJSX_JS(c, 'c.tsx').code ?? ''
35
- const ELEMENT_CONST = `function C(){ const header = <h1>T</h1>; return <div>{header}<p>x</p></div> }`
36
-
37
- describe('Round 9 — element-valued const used as a bare JSX child', () => {
38
- it('CONTROL: string/number const child still uses the correct text fast path', () => {
39
- expect(emit(`function C(){ const t = 'T'; return <div>{t}<p>x</p></div> }`)).toContain('createTextNode(t)')
40
- expect(emit(`function C(){ const n = 5; return <div>{n}</div> }`)).toContain('textContent = n')
41
- })
42
-
43
- it('CONTROL: an INLINE element child is correctly hoisted (not text-coerced)', () => {
44
- const out = emit(`function C(){ return <div>{<h1>T</h1>}<p>x</p></div> }`)
45
- expect(out).toMatch(/const _\$h\d+ =/)
46
- expect(out).not.toMatch(/createTextNode\(_\$h\d+\)/)
47
- })
48
-
49
- it('CONTRACT: element-valued const child is mounted via _mountSlot, not text-coerced', () => {
50
- const out = emit(ELEMENT_CONST)
51
- expect(out).toContain('const header = _tpl("<h1>T</h1>"')
52
- expect(out).not.toContain('createTextNode(header)')
53
- expect(out).toMatch(/_mountSlot\(\s*header\b/)
54
- })
55
-
56
- it('CONTRACT: single bare element-const child, parenthesized init, and let all mount', () => {
57
- expect(emit(`function C(){ const el = <span>hi</span>; return <div>{el}</div> }`)).toMatch(/_mountSlot\(\s*el\b/)
58
- expect(emit(`function C(){ const el = (<b>x</b>); return <div>{el}</div> }`)).toMatch(/_mountSlot\(\s*el\b/)
59
- expect(emit(`function C(){ let el = <a/>; return <div>{el}</div> }`)).toMatch(/_mountSlot\(\s*el\b/)
60
- })
61
- })
@@ -1,48 +0,0 @@
1
- /**
2
- * Compiler hardening — Round 3 (characterization, NOT a bug fix).
3
- *
4
- * Investigated: how the JSX transform emits falsy / boolean / null literal
5
- * children vs the JSX rendering contract (`true`/`false`/`null`/`undefined`
6
- * render nothing; `0` renders "0"; `''` renders empty).
7
- *
8
- * Finding: the patterns real code actually writes are CORRECT — a conditional
9
- * (`{c ? x : null}`) or short-circuit (`{c && <X/>}`) child is wrapped in a
10
- * `() =>` accessor and the null/boolean is filtered by runtime `mountChild`,
11
- * so nothing renders (Pyreon's documented `VNodeChildAtom` `&&` contract
12
- * holds). Only a CONTRIVED bare literal child (`<div>{false}</div>` — never
13
- * written in practice) takes the static path and emits `textContent = false`
14
- * → the DOM stringifies to "false". This is a spec divergence on input no one
15
- * writes; fixing it would touch the hot child-emission path for zero
16
- * real-world benefit, so the behavior is pinned here instead (any future
17
- * change to it must be deliberate, and this test will flag it).
18
- */
19
- import { describe, expect, it } from 'vitest'
20
- import { transformJSX_JS } from '../jsx'
21
-
22
- const emit = (c: string): string => transformJSX_JS(c, 'c.tsx').code ?? ''
23
-
24
- describe('Round 3 — conditional/short-circuit children are accessor-wrapped (the contract that matters)', () => {
25
- it('ternary with a null branch is wrapped in an accessor (runtime filters null)', () => {
26
- const out = emit(`function C(p){ return <div>{p.cond ? <a/> : null}</div> }`)
27
- expect(out).toContain('() => p.cond ? <a/> : null')
28
- expect(out).not.toContain('createTextNode(null)')
29
- })
30
-
31
- it('&& short-circuit is wrapped in an accessor (the documented && pattern)', () => {
32
- const out = emit(`function C(p){ return <div>{p.show && <b/>}</div> }`)
33
- expect(out).toContain('() => p.show && <b/>')
34
- expect(out).not.toContain('createTextNode(false)')
35
- })
36
- })
37
-
38
- describe('Round 3 — bare literal falsy children: pinned current behavior (contrived input)', () => {
39
- it('numeric 0 child renders "0" (JSX-correct)', () => {
40
- expect(emit(`function C(){ return <div>{0}</div> }`)).toContain('__root.textContent = 0')
41
- })
42
-
43
- // Pinned divergence: a bare `{false}` literal stringifies via textContent.
44
- // Documented, not fixed — see file header for the rationale.
45
- it('bare {false} literal takes the static textContent path (known, contrived)', () => {
46
- expect(emit(`function C(){ return <div>{false}</div> }`)).toContain('__root.textContent = false')
47
- })
48
- })