@pyreon/runtime-dom 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 (53) hide show
  1. package/package.json +5 -9
  2. package/src/delegate.ts +0 -98
  3. package/src/devtools.ts +0 -339
  4. package/src/env.d.ts +0 -6
  5. package/src/hydrate.ts +0 -450
  6. package/src/hydration-debug.ts +0 -129
  7. package/src/index.ts +0 -83
  8. package/src/keep-alive-entry.ts +0 -3
  9. package/src/keep-alive.ts +0 -83
  10. package/src/manifest.ts +0 -236
  11. package/src/mount.ts +0 -597
  12. package/src/nodes.ts +0 -896
  13. package/src/props.ts +0 -474
  14. package/src/template.ts +0 -523
  15. package/src/tests/callback-ref-unmount.browser.test.ts +0 -62
  16. package/src/tests/callback-ref-unmount.test.ts +0 -52
  17. package/src/tests/compiler-integration.test.tsx +0 -508
  18. package/src/tests/coverage-gaps.test.ts +0 -3183
  19. package/src/tests/coverage.test.ts +0 -1140
  20. package/src/tests/ctx-stack-growth-repro.test.tsx +0 -158
  21. package/src/tests/dev-gate-pattern.test.ts +0 -46
  22. package/src/tests/dev-gate-treeshake.test.ts +0 -256
  23. package/src/tests/error-boundary-stack-leak-repro.test.tsx +0 -133
  24. package/src/tests/fanout-repro.test.tsx +0 -219
  25. package/src/tests/hydration-integration.test.tsx +0 -540
  26. package/src/tests/keyed-array-in-for-batched-toggle.browser.test.ts +0 -140
  27. package/src/tests/lifecycle-integration.test.tsx +0 -342
  28. package/src/tests/lis-prepend.browser.test.ts +0 -99
  29. package/src/tests/manifest-snapshot.test.ts +0 -85
  30. package/src/tests/mount.test.ts +0 -3529
  31. package/src/tests/native-markers.test.ts +0 -19
  32. package/src/tests/props.test.ts +0 -581
  33. package/src/tests/reactive-props.test.ts +0 -270
  34. package/src/tests/real-world-integration.test.tsx +0 -714
  35. package/src/tests/rs-collapse-dyn-h.browser.test.ts +0 -303
  36. package/src/tests/rs-collapse-dyn.browser.test.ts +0 -316
  37. package/src/tests/rs-collapse-h.browser.test.ts +0 -152
  38. package/src/tests/rs-collapse-h.test.ts +0 -237
  39. package/src/tests/rs-collapse.browser.test.ts +0 -128
  40. package/src/tests/runtime-dom.browser.test.ts +0 -409
  41. package/src/tests/setup.ts +0 -3
  42. package/src/tests/show-context.test.ts +0 -270
  43. package/src/tests/show-of-for-batched-toggle.browser.test.ts +0 -122
  44. package/src/tests/ssr-xss-round-trip.browser.test.ts +0 -93
  45. package/src/tests/style-key-removal.browser.test.ts +0 -54
  46. package/src/tests/style-key-removal.test.ts +0 -88
  47. package/src/tests/template.test.ts +0 -383
  48. package/src/tests/transition-timeout-leak.test.ts +0 -126
  49. package/src/tests/transition.test.ts +0 -568
  50. package/src/tests/verified-correct-probes.test.ts +0 -56
  51. package/src/transition-entry.ts +0 -7
  52. package/src/transition-group.ts +0 -350
  53. package/src/transition.ts +0 -245
@@ -1,508 +0,0 @@
1
- /**
2
- * Compiler Integration Tests
3
- *
4
- * Full pipeline: source code -> transformJSX -> runtime mount -> signal change -> DOM verification.
5
- *
6
- * The compiler emits code referencing _tpl, _bind, _bindText, _bindDirect, _rp, h.
7
- * We strip the import lines from compiler output, inject dependencies via Function
8
- * constructor, execute, mount the result, and assert DOM state.
9
- */
10
- import { transformJSX } from '@pyreon/compiler'
11
- import { Fragment, h, _rp } from '@pyreon/core'
12
- import { _bind, signal } from '@pyreon/reactivity'
13
- import { _tpl, _bindText, _bindDirect } from '../template'
14
- import { _applyProps, mount, mountChild } from '../index'
15
-
16
- // ─── Helpers ────────────────────────────────────────────────────────────────
17
-
18
- /** Strip import lines from compiler output — we pass deps via Function args. */
19
- function stripImports(code: string): string {
20
- return code.replace(/^import\s+.*$/gm, '').trim()
21
- }
22
-
23
- /** Runtime deps that the compiler output references. */
24
- const RUNTIME_DEPS = {
25
- _tpl,
26
- _bind,
27
- _bindText,
28
- _bindDirect,
29
- _applyProps,
30
- _rp,
31
- h,
32
- Fragment,
33
- signal,
34
- document,
35
- } as const
36
-
37
- const DEP_NAMES = Object.keys(RUNTIME_DEPS)
38
- const DEP_VALUES = Object.values(RUNTIME_DEPS)
39
-
40
- /**
41
- * Compile JSX source and execute it, returning the resulting NativeItem or VNode.
42
- * For component definitions, pass the component source and call it separately.
43
- */
44
- function compileExpression(source: string) {
45
- const result = transformJSX(source, 'test.tsx')
46
- const body = stripImports(result.code)
47
- return { body, code: result.code }
48
- }
49
-
50
- /**
51
- * Compile a standalone JSX expression (not a component), execute it,
52
- * mount it into a container, and return { container, cleanup, code }.
53
- */
54
- function compileAndMount(
55
- source: string,
56
- globals: Record<string, unknown> = {},
57
- ) {
58
- const { body, code } = compileExpression(source)
59
-
60
- const globalNames = Object.keys(globals)
61
- const globalValues = Object.values(globals)
62
-
63
- // The body is an expression (e.g. _tpl(...)) — wrap in return
64
- const fn = new Function(
65
- ...DEP_NAMES,
66
- ...globalNames,
67
- `return ${body}`,
68
- )
69
-
70
- const result = fn(...DEP_VALUES, ...globalValues)
71
- const container = document.createElement('div')
72
- document.body.appendChild(container)
73
- const cleanup = mountChild(result, container)
74
-
75
- return { container, cleanup, code }
76
- }
77
-
78
- /**
79
- * Compile a component definition, extract the component function,
80
- * mount it with given props, and return { container, cleanup, code }.
81
- */
82
- function compileComponent(
83
- source: string,
84
- props: Record<string, unknown> = {},
85
- globals: Record<string, unknown> = {},
86
- ) {
87
- const { body, code } = compileExpression(source)
88
-
89
- const globalNames = Object.keys(globals)
90
- const globalValues = Object.values(globals)
91
-
92
- // Component defs are statements like `const Comp = (props) => _tpl(...)`.
93
- // We execute the body then return the named component.
94
- // Extract the component name from the source.
95
- const nameMatch = body.match(/^const\s+(\w+)\s*=/)
96
- if (!nameMatch) throw new Error('Could not find component name in compiled output')
97
- const compName = nameMatch[1]
98
-
99
- const fn = new Function(
100
- ...DEP_NAMES,
101
- ...globalNames,
102
- `${body}\nreturn ${compName}`,
103
- )
104
-
105
- const Component = fn(...DEP_VALUES, ...globalValues)
106
-
107
- // Build reactive props (same as what the runtime does when mounting h(Comp, props))
108
- const container = document.createElement('div')
109
- document.body.appendChild(container)
110
-
111
- const vnode = h(Component, props)
112
- const cleanup = mountChild(vnode, container)
113
-
114
- return { container, cleanup, code }
115
- }
116
-
117
- function createContainer(): HTMLDivElement {
118
- const el = document.createElement('div')
119
- document.body.appendChild(el)
120
- return el
121
- }
122
-
123
- // ─── Tests ──────────────────────────────────────────────────────────────────
124
-
125
- describe('compiler integration — signal text reactivity', () => {
126
- afterEach(() => {
127
- document.body.innerHTML = ''
128
- })
129
-
130
- it('signal() in text — _bindText updates DOM on signal.set', () => {
131
- const count = signal(0)
132
- const { container } = compileAndMount(
133
- '<div>{count()}</div>',
134
- { count },
135
- )
136
-
137
- expect(container.querySelector('div')!.textContent).toBe('0')
138
-
139
- count.set(42)
140
- expect(container.querySelector('div')!.textContent).toBe('42')
141
-
142
- count.set(-1)
143
- expect(container.querySelector('div')!.textContent).toBe('-1')
144
- })
145
-
146
- it('two independent signals — changing one does not affect the other', () => {
147
- const a = signal('hello')
148
- const b = signal('world')
149
- const { container } = compileAndMount(
150
- '<div><span>{a()}</span><span>{b()}</span></div>',
151
- { a, b },
152
- )
153
-
154
- const spans = container.querySelectorAll('span')
155
- expect(spans[0]!.textContent).toBe('hello')
156
- expect(spans[1]!.textContent).toBe('world')
157
-
158
- a.set('changed')
159
- expect(spans[0]!.textContent).toBe('changed')
160
- expect(spans[1]!.textContent).toBe('world')
161
-
162
- b.set('updated')
163
- expect(spans[0]!.textContent).toBe('changed')
164
- expect(spans[1]!.textContent).toBe('updated')
165
- })
166
- })
167
-
168
- describe('compiler integration — props reactivity', () => {
169
- afterEach(() => {
170
- document.body.innerHTML = ''
171
- })
172
-
173
- it('props.name in text — reactive via _bind', () => {
174
- const name = signal('Alice')
175
- const { container } = compileComponent(
176
- 'const Comp = (props) => <div>{props.name}</div>',
177
- { name: _rp(() => name()) },
178
- )
179
-
180
- expect(container.querySelector('div')!.textContent).toBe('Alice')
181
-
182
- name.set('Bob')
183
- expect(container.querySelector('div')!.textContent).toBe('Bob')
184
- })
185
-
186
- it('const x = props.y ?? "def" — compiler inlines props.y, reactive', () => {
187
- const y = signal<string | undefined>(undefined)
188
- const { container } = compileComponent(
189
- 'const Comp = (props) => { const x = props.y ?? "def"; return <div>{x}</div> }',
190
- { y: _rp(() => y()) },
191
- )
192
-
193
- // Initially undefined, so ?? "def" should produce "def"
194
- expect(container.querySelector('div')!.textContent).toBe('def')
195
-
196
- y.set('custom')
197
- expect(container.querySelector('div')!.textContent).toBe('custom')
198
-
199
- y.set(undefined)
200
- expect(container.querySelector('div')!.textContent).toBe('def')
201
- })
202
-
203
- it('multiple uses of same const derived from props — both update', () => {
204
- const y = signal('A')
205
- const { container } = compileComponent(
206
- 'const Comp = (props) => { const x = props.y; return <div><span>{x}</span><p>{x}</p></div> }',
207
- { y: _rp(() => y()) },
208
- )
209
-
210
- expect(container.querySelector('span')!.textContent).toBe('A')
211
- expect(container.querySelector('p')!.textContent).toBe('A')
212
-
213
- y.set('B')
214
- expect(container.querySelector('span')!.textContent).toBe('B')
215
- expect(container.querySelector('p')!.textContent).toBe('B')
216
- })
217
-
218
- it('let x = props.y — NOT reactive (let is mutable, unsafe to inline)', () => {
219
- const y = signal('initial')
220
- const { container } = compileComponent(
221
- 'const Comp = (props) => { let x = props.y; return <div>{x}</div> }',
222
- { y: _rp(() => y()) },
223
- )
224
-
225
- // Initial value captured at component creation time
226
- expect(container.querySelector('div')!.textContent).toBe('initial')
227
-
228
- // Signal change should NOT update — let variables are not inlined
229
- y.set('changed')
230
- expect(container.querySelector('div')!.textContent).toBe('initial')
231
- })
232
- })
233
-
234
- describe('compiler integration — class attribute reactivity', () => {
235
- afterEach(() => {
236
- document.body.innerHTML = ''
237
- })
238
-
239
- it('class={cls()} — _bindDirect updates className on signal change', () => {
240
- const cls = signal('active')
241
- const { container } = compileAndMount(
242
- '<div class={cls()}></div>',
243
- { cls },
244
- )
245
-
246
- expect(container.querySelector('div')!.className).toBe('active')
247
-
248
- cls.set('inactive')
249
- expect(container.querySelector('div')!.className).toBe('inactive')
250
-
251
- cls.set('')
252
- expect(container.querySelector('div')!.className).toBe('')
253
- })
254
- })
255
-
256
- describe('compiler integration — static content', () => {
257
- afterEach(() => {
258
- document.body.innerHTML = ''
259
- })
260
-
261
- it('purely static JSX — no bindings, renders correctly', () => {
262
- const { container, code } = compileAndMount(
263
- '<div class="box"><span>hello</span></div>',
264
- )
265
-
266
- expect(container.querySelector('div')!.className).toBe('box')
267
- expect(container.querySelector('span')!.textContent).toBe('hello')
268
- // Verify the compiler output has no reactive bindings
269
- expect(code).toContain('() => null')
270
- })
271
- })
272
-
273
- describe('compiler integration — SVG', () => {
274
- afterEach(() => {
275
- document.body.innerHTML = ''
276
- })
277
-
278
- it('SVG element renders correctly via _tpl', () => {
279
- const { container } = compileAndMount(
280
- '<svg><circle cx="50" cy="50" r="40"></circle></svg>',
281
- )
282
-
283
- const svg = container.querySelector('svg')
284
- expect(svg).not.toBeNull()
285
- const circle = container.querySelector('circle')
286
- expect(circle).not.toBeNull()
287
- expect(circle!.getAttribute('cx')).toBe('50')
288
- expect(circle!.getAttribute('cy')).toBe('50')
289
- expect(circle!.getAttribute('r')).toBe('40')
290
- })
291
- })
292
-
293
- describe('compiler integration — component element with _rp', () => {
294
- afterEach(() => {
295
- document.body.innerHTML = ''
296
- })
297
-
298
- it('component prop wrapped with _rp — reactive when signal changes', () => {
299
- const name = signal('Alice')
300
- let mountCount = 0
301
-
302
- const MyComponent = (props: { name: string }) => {
303
- mountCount++
304
- return h('span', null, () => props.name)
305
- }
306
-
307
- // The compiler emits: <MyComponent name={_rp(() => count())} />
308
- // which is equivalent to h(MyComponent, { name: _rp(() => name()) })
309
- const container = createContainer()
310
- mount(h(MyComponent, { name: _rp(() => name()) }), container)
311
-
312
- expect(mountCount).toBe(1)
313
- expect(container.querySelector('span')!.textContent).toBe('Alice')
314
-
315
- name.set('Bob')
316
- expect(mountCount).toBe(1) // no remount
317
- expect(container.querySelector('span')!.textContent).toBe('Bob')
318
- })
319
- })
320
-
321
- describe('compiler integration — compiler output structure', () => {
322
- it('signal in text emits _bindText import and call', () => {
323
- const { code } = transformJSX('<div>{count()}</div>', 'test.tsx')
324
- expect(code).toContain('import { _tpl, _bindText } from "@pyreon/runtime-dom"')
325
- expect(code).toContain('_bindText(count,')
326
- })
327
-
328
- it('props.name emits _bind import from @pyreon/reactivity', () => {
329
- const { code } = transformJSX(
330
- 'const Comp = (props) => <div>{props.name}</div>',
331
- 'test.tsx',
332
- )
333
- expect(code).toContain('import { _bind } from "@pyreon/reactivity"')
334
- expect(code).toContain('_bind(() => { __t0.data = props.name })')
335
- })
336
-
337
- it('class={cls()} emits _bindDirect', () => {
338
- const { code } = transformJSX('<div class={cls()}></div>', 'test.tsx')
339
- expect(code).toContain('_bindDirect(cls,')
340
- expect(code).toContain('__root.className')
341
- })
342
-
343
- it('component reactive prop emits _rp wrapping', () => {
344
- const { code } = transformJSX('<Button label={getText()} />', 'test.tsx')
345
- expect(code).toContain('_rp(() => getText())')
346
- })
347
-
348
- it('const from props gets inlined back to props.y in JSX', () => {
349
- const { code } = transformJSX(
350
- 'const Comp = (props) => { const x = props.y; return <div>{x}</div> }',
351
- 'test.tsx',
352
- )
353
- // Compiler inlines: const x = props.y → uses props.y directly in _bind
354
- expect(code).toContain('__t0.data = (props.y)')
355
- })
356
-
357
- it('let from props does NOT get inlined — uses captured value', () => {
358
- const { code } = transformJSX(
359
- 'const Comp = (props) => { let x = props.y; return <div>{x}</div> }',
360
- 'test.tsx',
361
- )
362
- // let is not inlined — uses static textContent assignment
363
- expect(code).toContain('__root.textContent = x')
364
- expect(code).not.toContain('_bind')
365
- })
366
-
367
- it('static JSX emits _tpl with null bind function', () => {
368
- const { code } = transformJSX(
369
- '<div class="box"><span>hello</span></div>',
370
- 'test.tsx',
371
- )
372
- expect(code).toContain('() => null')
373
- expect(code).not.toContain('_bind')
374
- expect(code).not.toContain('_bindText')
375
- })
376
- })
377
-
378
- // ─── Additional edge cases ──────────────────────────────────────────────────
379
-
380
- describe('compiler integration — prop-derived with defaults', () => {
381
- afterEach(() => { document.body.innerHTML = '' })
382
-
383
- it('props.x ?? default — starts with default, updates when set', () => {
384
- const x = signal<string | undefined>(undefined)
385
- const { container } = compileComponent(
386
- 'const Comp = (props) => { const label = props.x ?? "fallback"; return <span>{label}</span> }',
387
- { x: _rp(() => x()) },
388
- )
389
- expect(container.querySelector('span')!.textContent).toBe('fallback')
390
- x.set('real')
391
- expect(container.querySelector('span')!.textContent).toBe('real')
392
- x.set(undefined)
393
- expect(container.querySelector('span')!.textContent).toBe('fallback')
394
- })
395
-
396
- it('props.x || default — falsy fallback works', () => {
397
- const x = signal('')
398
- const { container } = compileComponent(
399
- 'const Comp = (props) => { const v = props.x || "empty"; return <span>{v}</span> }',
400
- { x: _rp(() => x()) },
401
- )
402
- expect(container.querySelector('span')!.textContent).toBe('empty')
403
- x.set('filled')
404
- expect(container.querySelector('span')!.textContent).toBe('filled')
405
- })
406
- })
407
-
408
- describe('compiler integration — ternary and expressions', () => {
409
- afterEach(() => { document.body.innerHTML = '' })
410
-
411
- it('ternary with signal — updates on change', () => {
412
- const on = signal(true)
413
- const { container } = compileAndMount(
414
- '<div>{on() ? "yes" : "no"}</div>',
415
- { on },
416
- )
417
- expect(container.querySelector('div')!.textContent).toBe('yes')
418
- on.set(false)
419
- expect(container.querySelector('div')!.textContent).toBe('no')
420
- })
421
-
422
- it('template literal with signal', () => {
423
- const name = signal('world')
424
- const { container } = compileAndMount(
425
- '<div>{`hello ${name()}`}</div>',
426
- { name },
427
- )
428
- expect(container.querySelector('div')!.textContent).toBe('hello world')
429
- name.set('Pyreon')
430
- expect(container.querySelector('div')!.textContent).toBe('hello Pyreon')
431
- })
432
-
433
- it('arithmetic with signal', () => {
434
- const n = signal(5)
435
- const { container } = compileAndMount(
436
- '<div>{n() * 2 + 1}</div>',
437
- { n },
438
- )
439
- expect(container.querySelector('div')!.textContent).toBe('11')
440
- n.set(10)
441
- expect(container.querySelector('div')!.textContent).toBe('21')
442
- })
443
- })
444
-
445
- describe('compiler integration — multiple attributes', () => {
446
- afterEach(() => { document.body.innerHTML = '' })
447
-
448
- it('reactive class + static id', () => {
449
- const cls = signal('a')
450
- const { container } = compileAndMount(
451
- '<div id="fixed" class={cls()}></div>',
452
- { cls },
453
- )
454
- const div = container.querySelector('div')!
455
- expect(div.id).toBe('fixed')
456
- expect(div.className).toBe('a')
457
- cls.set('b')
458
- expect(div.id).toBe('fixed')
459
- expect(div.className).toBe('b')
460
- })
461
-
462
- it('reactive style string', () => {
463
- const color = signal('red')
464
- const { container } = compileAndMount(
465
- '<div style={`color: ${color()}`}></div>',
466
- { color },
467
- )
468
- expect(container.querySelector('div')!.style.color).toBe('red')
469
- color.set('blue')
470
- expect(container.querySelector('div')!.style.color).toBe('blue')
471
- })
472
- })
473
-
474
- describe('compiler integration — prop-derived in attributes', () => {
475
- afterEach(() => { document.body.innerHTML = '' })
476
-
477
- it('const cls = props.class ?? "default" on class attr', () => {
478
- const c = signal<string | undefined>(undefined)
479
- const { container } = compileComponent(
480
- 'const Comp = (props) => { const cls = props.class ?? "default"; return <div class={cls}></div> }',
481
- { class: _rp(() => c()) },
482
- )
483
- expect(container.querySelector('div')!.className).toBe('default')
484
- c.set('custom')
485
- expect(container.querySelector('div')!.className).toBe('custom')
486
- })
487
- })
488
-
489
- describe('compiler integration — no false inlining', () => {
490
- it('.map callback param not treated as props', () => {
491
- const { code } = transformJSX(
492
- 'function App(props) { return <div>{items.map((item) => { const name = item.name; return <span>{name}</span> })}</div> }',
493
- 'test.tsx',
494
- )
495
- // item.name should NOT be inlined — item is a callback param, not props
496
- expect(code).not.toContain('(item.name)')
497
- })
498
-
499
- it('property access obj.x where x is also a prop-derived var', () => {
500
- const { code } = transformJSX(
501
- 'const Comp = (props) => { const x = props.x; return <div>{other.x}</div> }',
502
- 'test.tsx',
503
- )
504
- // other.x should stay as other.x — not replaced with (props.x)
505
- // sliceExpr only replaces standalone identifiers, not property access
506
- expect(code).not.toContain('other.(props.x)')
507
- })
508
- })