@pyreon/reactivity 0.18.0 → 0.19.0
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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +136 -27
- package/lib/types/index.d.ts +53 -3
- package/package.json +1 -1
- package/src/computed.ts +44 -14
- package/src/effect.ts +5 -0
- package/src/index.ts +2 -0
- package/src/manifest.ts +22 -1
- package/src/reactive-trace.ts +142 -0
- package/src/reconcile.ts +12 -0
- package/src/signal.ts +34 -21
- package/src/store.ts +11 -0
- package/src/tests/computed.test.ts +31 -0
- package/src/tests/coverage-hardening.test.ts +471 -0
- package/src/tests/manifest-snapshot.test.ts +4 -3
- package/src/tests/reactive-trace.test.ts +102 -0
- package/src/tests/reconcile-security.test.ts +45 -0
- package/src/tests/signal.test.ts +35 -9
package/src/tests/signal.test.ts
CHANGED
|
@@ -164,17 +164,25 @@ describe('signal', () => {
|
|
|
164
164
|
expect(calls3).toBe(2) // still active
|
|
165
165
|
})
|
|
166
166
|
|
|
167
|
-
test('direct updater
|
|
167
|
+
test('direct updater is removed from the set after disposal', () => {
|
|
168
168
|
const s = signal(0)
|
|
169
|
-
|
|
169
|
+
let calls = 0
|
|
170
|
+
const dispose = s.direct(() => {
|
|
171
|
+
calls++
|
|
172
|
+
})
|
|
170
173
|
|
|
171
|
-
//
|
|
172
|
-
const internal = s as unknown as { _d: (
|
|
174
|
+
// Internal `_d` is a Set (not an unbounded array — see signal.ts).
|
|
175
|
+
const internal = s as unknown as { _d: Set<() => void> | null }
|
|
173
176
|
expect(internal._d).not.toBeNull()
|
|
174
|
-
expect(internal._d
|
|
177
|
+
expect(internal._d!.size).toBe(1)
|
|
178
|
+
s.set(1)
|
|
179
|
+
expect(calls).toBe(1)
|
|
175
180
|
|
|
176
181
|
dispose()
|
|
177
|
-
|
|
182
|
+
// O(1) removal — the slot is GONE, not nulled-and-retained.
|
|
183
|
+
expect(internal._d!.size).toBe(0)
|
|
184
|
+
s.set(2)
|
|
185
|
+
expect(calls).toBe(1) // disposed updater not invoked
|
|
178
186
|
})
|
|
179
187
|
})
|
|
180
188
|
|
|
@@ -208,13 +216,31 @@ describe('signal', () => {
|
|
|
208
216
|
expect(calls).toBe(1)
|
|
209
217
|
})
|
|
210
218
|
|
|
211
|
-
test('direct updater
|
|
219
|
+
test('direct updater set initializes lazily', () => {
|
|
212
220
|
const s = signal(0)
|
|
213
|
-
const internal = s as unknown as { _d: (
|
|
221
|
+
const internal = s as unknown as { _d: Set<() => void> | null }
|
|
214
222
|
expect(internal._d).toBeNull()
|
|
215
223
|
s.direct(() => {})
|
|
216
224
|
expect(internal._d).not.toBeNull()
|
|
217
|
-
expect(internal._d).
|
|
225
|
+
expect(internal._d!.size).toBe(1)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('churned direct bindings do not accumulate (no unbounded growth)', () => {
|
|
229
|
+
// Regression for the array-form leak: a long-lived signal whose
|
|
230
|
+
// direct bindings register+dispose repeatedly (e.g. <For> rows
|
|
231
|
+
// re-mounting) must keep `_d` bounded to the LIVE set, not grow
|
|
232
|
+
// one permanent dead slot per ever-registered binding.
|
|
233
|
+
const s = signal(0)
|
|
234
|
+
const internal = s as unknown as { _d: Set<() => void> | null }
|
|
235
|
+
for (let i = 0; i < 10_000; i++) {
|
|
236
|
+
const dispose = s.direct(() => {})
|
|
237
|
+
dispose()
|
|
238
|
+
}
|
|
239
|
+
expect(internal._d!.size).toBe(0)
|
|
240
|
+
// One live binding survives → notify cost is O(live), not O(10_000).
|
|
241
|
+
const dispose = s.direct(() => {})
|
|
242
|
+
expect(internal._d!.size).toBe(1)
|
|
243
|
+
dispose()
|
|
218
244
|
})
|
|
219
245
|
})
|
|
220
246
|
|