@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,219 +0,0 @@
1
- /** @jsxImportSource @pyreon/core */
2
- /**
3
- * Reproduction of the deferred bug from PR #490 (queryReactiveKey-1000 journey).
4
- *
5
- * The pure-reactivity unit test (packages/core/reactivity/src/tests/fanout-repro.test.ts)
6
- * passes — many effects subscribing to one signal all fire on every external
7
- * .set. So the bug must involve actual Pyreon mount frames (provide/onMount/etc),
8
- * not just the reactivity primitives.
9
- *
10
- * This test mounts a real Pyreon component with N effects subscribing to a
11
- * shared signal, then writes to that signal in a tight external loop —
12
- * mirroring the real shape from the queryReactiveKey-1000 journey.
13
- */
14
- import { For, h } from '@pyreon/core'
15
- import { effect, signal } from '@pyreon/reactivity'
16
- import { describe, expect, it } from 'vitest'
17
- import { mount } from '../index'
18
-
19
- describe('signal fan-out under tight external write loop — INSIDE mount frame', () => {
20
- it('100 effects in a Pyreon component — each fires on every external .set', () => {
21
- const sig = signal(0)
22
- const counts = new Array(100).fill(0)
23
-
24
- const root = document.createElement('div')
25
- document.body.appendChild(root)
26
-
27
- const Component = () => {
28
- for (let i = 0; i < 100; i++) {
29
- const idx = i
30
- effect(() => {
31
- sig()
32
- counts[idx]++
33
- })
34
- }
35
- return h('div', null, 'mounted')
36
- }
37
-
38
- const dispose = mount(h(Component, null), root)
39
-
40
- // All 100 effects ran their initial setup.
41
- for (const c of counts) expect(c).toBe(1)
42
-
43
- // 10 external writes outside any batch.
44
- for (let i = 1; i <= 10; i++) sig.set(i)
45
-
46
- // Each effect should have re-fired 10 more times → total = 11.
47
- let failed = 0
48
- for (let i = 0; i < counts.length; i++) {
49
- if (counts[i] !== 11) failed++
50
- }
51
- expect(failed, `effects with wrong count (out of 100)`).toBe(0)
52
-
53
- dispose()
54
- root.remove()
55
- })
56
-
57
- it('100 effects + an extra effect AFTER the loop — all fire on each .set', () => {
58
- // Real-bug shape: a diagnostic effect placed AFTER the useQuery loop
59
- // saw 0 re-runs across 10 flips, while a BEFORE-loop one saw 1 of 10.
60
- const sig = signal(0)
61
- const counts = new Array(100).fill(0)
62
- let beforeRuns = 0
63
- let afterRuns = 0
64
-
65
- const root = document.createElement('div')
66
- document.body.appendChild(root)
67
-
68
- const Component = () => {
69
- effect(() => {
70
- sig()
71
- beforeRuns++
72
- })
73
- for (let i = 0; i < 100; i++) {
74
- const idx = i
75
- effect(() => {
76
- sig()
77
- counts[idx]++
78
- })
79
- }
80
- effect(() => {
81
- sig()
82
- afterRuns++
83
- })
84
- return h('div', null, 'mounted')
85
- }
86
-
87
- const dispose = mount(h(Component, null), root)
88
-
89
- expect(beforeRuns).toBe(1)
90
- expect(afterRuns).toBe(1)
91
- for (const c of counts) expect(c).toBe(1)
92
-
93
- for (let i = 1; i <= 10; i++) sig.set(i)
94
-
95
- expect(beforeRuns, 'before-loop effect runs').toBe(11)
96
- expect(afterRuns, 'after-loop effect runs').toBe(11)
97
- let failed = 0
98
- for (let i = 0; i < counts.length; i++) {
99
- if (counts[i] !== 11) failed++
100
- }
101
- expect(failed, `effects with wrong count`).toBe(0)
102
-
103
- dispose()
104
- root.remove()
105
- })
106
-
107
- it('the body of each effect ALSO writes to a per-effect local signal (mimics useQuery slot writes)', () => {
108
- // useQuery's effect body calls observer.setOptions which triggers the
109
- // observer's subscribe callback which does batch(() => 9 signal.sets).
110
- // Approximate that with: each outer effect's body creates its own local
111
- // signal and writes to it in a batch.
112
- const sig = signal(0)
113
- const counts = new Array(100).fill(0)
114
-
115
- const root = document.createElement('div')
116
- document.body.appendChild(root)
117
-
118
- const Component = () => {
119
- for (let i = 0; i < 100; i++) {
120
- const idx = i
121
- const slot = signal('')
122
- effect(() => {
123
- sig() // subscribe
124
- // Mimic batched writes inside the effect body
125
- slot.set(`run-${counts[idx]}`)
126
- counts[idx]++
127
- })
128
- }
129
- return h('div', null, 'mounted')
130
- }
131
-
132
- const dispose = mount(h(Component, null), root)
133
-
134
- for (const c of counts) expect(c).toBe(1)
135
-
136
- for (let i = 1; i <= 10; i++) sig.set(i)
137
-
138
- let failed = 0
139
- for (let i = 0; i < counts.length; i++) {
140
- if (counts[i] !== 11) failed++
141
- }
142
- expect(failed, `effects with wrong count`).toBe(0)
143
-
144
- dispose()
145
- root.remove()
146
- })
147
-
148
- // ─── The real bug shape: <For> wrapping the queries ───────────────────────
149
- //
150
- // mountFor (`packages/core/runtime-dom/src/nodes.ts`) wraps its body in
151
- // effect() but does NOT untrack the child mountChild calls (mountReactive
152
- // does — line 92 of nodes.ts). As a result, any signal read during a
153
- // child component's setup tracks against the For effect's run. When the
154
- // tracked signal flips, For's effect re-runs → runCleanup() disposes ALL
155
- // inner effects (the per-item setOptions effects) → handleIncrementalUpdate
156
- // sees keys unchanged → does not re-mount → setOptions effects gone +
157
- // never recreated. signalWrite fires N times but effectRun stays at the
158
- // initial-mount count — the exact PR #490 observation.
159
-
160
- it('REGRESSION: 100 effects mounted under <For> re-fire when shared signal flips', () => {
161
- // Mirrors the queryReactiveKey-1000 shape: a mode/count tuple keys the
162
- // For so its body mounts QueryAtScale-equivalent ONCE. Inside, N effects
163
- // subscribe to a separate `reactKey` signal. External flips of reactKey
164
- // must propagate to all N inner effects.
165
- const reactKey = signal(0)
166
- const counts = new Array(100).fill(0)
167
- const root = document.createElement('div')
168
- document.body.appendChild(root)
169
-
170
- // Stable single-item array — For mounts the inner component exactly once.
171
- const items = [{ id: 1 }] as const
172
- type Item = (typeof items)[number]
173
-
174
- const Inner = (props: { item: Item }) => {
175
- // Read props.item to keep the component honest — same shape as
176
- // QueryAtScale (which reads props.mode + props.count). That read
177
- // tracks against the OUTER For effect via makeReactiveProps' getter.
178
- void props.item.id
179
-
180
- // Mimic useQuery's "read signal at construction time, OUTSIDE the
181
- // inner effect" pattern. This is what `new QueryObserver(client,
182
- // options())` does — options() reads reactKey while activeEffect is
183
- // the outer effect (For's run), leaking the subscription up.
184
- const _seed = reactKey() // ← this is the leak
185
-
186
- // Plus: 100 effects each subscribing to reactKey via their own bodies.
187
- for (let i = 0; i < 100; i++) {
188
- const idx = i
189
- effect(() => {
190
- reactKey()
191
- counts[idx]++
192
- })
193
- }
194
- return h('div', { 'data-testid': 'inner' }, `mounted ${_seed}`)
195
- }
196
-
197
- const dispose = mount(
198
- h(For, {
199
- each: items,
200
- by: (it: Item) => it.id,
201
- children: (it: Item) => h(Inner, { item: it }),
202
- }),
203
- root,
204
- )
205
-
206
- for (const c of counts) expect(c).toBe(1)
207
-
208
- for (let i = 1; i <= 10; i++) reactKey.set(i)
209
-
210
- let failed = 0
211
- for (let i = 0; i < counts.length; i++) {
212
- if (counts[i] !== 11) failed++
213
- }
214
- expect(failed, `effects with wrong count after 10 flips`).toBe(0)
215
-
216
- dispose()
217
- root.remove()
218
- })
219
- })