@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.
- package/package.json +5 -9
- package/src/delegate.ts +0 -98
- package/src/devtools.ts +0 -339
- package/src/env.d.ts +0 -6
- package/src/hydrate.ts +0 -450
- package/src/hydration-debug.ts +0 -129
- package/src/index.ts +0 -83
- package/src/keep-alive-entry.ts +0 -3
- package/src/keep-alive.ts +0 -83
- package/src/manifest.ts +0 -236
- package/src/mount.ts +0 -597
- package/src/nodes.ts +0 -896
- package/src/props.ts +0 -474
- package/src/template.ts +0 -523
- package/src/tests/callback-ref-unmount.browser.test.ts +0 -62
- package/src/tests/callback-ref-unmount.test.ts +0 -52
- package/src/tests/compiler-integration.test.tsx +0 -508
- package/src/tests/coverage-gaps.test.ts +0 -3183
- package/src/tests/coverage.test.ts +0 -1140
- package/src/tests/ctx-stack-growth-repro.test.tsx +0 -158
- package/src/tests/dev-gate-pattern.test.ts +0 -46
- package/src/tests/dev-gate-treeshake.test.ts +0 -256
- package/src/tests/error-boundary-stack-leak-repro.test.tsx +0 -133
- package/src/tests/fanout-repro.test.tsx +0 -219
- package/src/tests/hydration-integration.test.tsx +0 -540
- package/src/tests/keyed-array-in-for-batched-toggle.browser.test.ts +0 -140
- package/src/tests/lifecycle-integration.test.tsx +0 -342
- package/src/tests/lis-prepend.browser.test.ts +0 -99
- package/src/tests/manifest-snapshot.test.ts +0 -85
- package/src/tests/mount.test.ts +0 -3529
- package/src/tests/native-markers.test.ts +0 -19
- package/src/tests/props.test.ts +0 -581
- package/src/tests/reactive-props.test.ts +0 -270
- package/src/tests/real-world-integration.test.tsx +0 -714
- package/src/tests/rs-collapse-dyn-h.browser.test.ts +0 -303
- package/src/tests/rs-collapse-dyn.browser.test.ts +0 -316
- package/src/tests/rs-collapse-h.browser.test.ts +0 -152
- package/src/tests/rs-collapse-h.test.ts +0 -237
- package/src/tests/rs-collapse.browser.test.ts +0 -128
- package/src/tests/runtime-dom.browser.test.ts +0 -409
- package/src/tests/setup.ts +0 -3
- package/src/tests/show-context.test.ts +0 -270
- package/src/tests/show-of-for-batched-toggle.browser.test.ts +0 -122
- package/src/tests/ssr-xss-round-trip.browser.test.ts +0 -93
- package/src/tests/style-key-removal.browser.test.ts +0 -54
- package/src/tests/style-key-removal.test.ts +0 -88
- package/src/tests/template.test.ts +0 -383
- package/src/tests/transition-timeout-leak.test.ts +0 -126
- package/src/tests/transition.test.ts +0 -568
- package/src/tests/verified-correct-probes.test.ts +0 -56
- package/src/transition-entry.ts +0 -7
- package/src/transition-group.ts +0 -350
- 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
|
-
})
|