@pyreon/reactivity 0.11.5 → 0.11.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/src/signal.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
2
  declare const process: { env: { NODE_ENV?: string } } | undefined
3
3
 
4
- const __DEV__ = typeof process !== "undefined" && process?.env?.NODE_ENV !== "production"
4
+ const __DEV__ = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'
5
5
 
6
- import { enqueuePendingNotification, isBatching } from "./batch"
7
- import { _notifyTraceListeners, isTracing } from "./debug"
8
- import { notifySubscribers, trackSubscriber } from "./tracking"
6
+ import { enqueuePendingNotification, isBatching } from './batch'
7
+ import { _notifyTraceListeners, isTracing } from './debug'
8
+ import { notifySubscribers, trackSubscriber } from './tracking'
9
9
 
10
10
  export interface SignalDebugInfo<T> {
11
11
  /** Signal name (set via options or inferred) */
@@ -153,9 +153,9 @@ export function signal<T>(initialValue: T, options?: SignalOptions): Signal<T> {
153
153
  if (__DEV__ && args.length > 0) {
154
154
  // biome-ignore lint/suspicious/noConsole: dev-only signal misuse warning
155
155
  console.warn(
156
- "[Pyreon] signal() was called with an argument. " +
157
- "Use signal.set(value) or signal.update(fn) to write. " +
158
- "signal(value) only reads — the argument is ignored.",
156
+ '[Pyreon] signal() was called with an argument. ' +
157
+ 'Use signal.set(value) or signal.update(fn) to write. ' +
158
+ 'signal(value) only reads — the argument is ignored.',
159
159
  )
160
160
  }
161
161
  trackSubscriber(read as SignalFn<T>)
package/src/store.ts CHANGED
@@ -13,18 +13,18 @@
13
13
  * state.items[0].text = "world" // only text-tracking effects re-run
14
14
  */
15
15
 
16
- import { type Signal, signal } from "./signal"
16
+ import { type Signal, signal } from './signal'
17
17
 
18
18
  // WeakMap: raw object → its reactive proxy (ensures each raw object gets one proxy)
19
19
  const proxyCache = new WeakMap<object, object>()
20
20
 
21
- const IS_STORE = Symbol("pyreon.store")
21
+ const IS_STORE = Symbol('pyreon.store')
22
22
 
23
23
  /** Returns true if the value is a createStore proxy. */
24
24
  export function isStore(value: unknown): boolean {
25
25
  return (
26
26
  value !== null &&
27
- typeof value === "object" &&
27
+ typeof value === 'object' &&
28
28
  (value as Record<symbol, unknown>)[IS_STORE] === true
29
29
  )
30
30
  }
@@ -58,10 +58,10 @@ function wrap(raw: object): object {
58
58
  get(target, key) {
59
59
  // Pass through the identity marker and non-string/number keys (symbols, etc.)
60
60
  if (key === IS_STORE) return true
61
- if (typeof key === "symbol") return (target as Record<symbol, unknown>)[key]
61
+ if (typeof key === 'symbol') return (target as Record<symbol, unknown>)[key]
62
62
 
63
63
  // Array length — tracked via dedicated signal for push/pop/splice reactivity
64
- if (isArray && key === "length") return lengthSig?.()
64
+ if (isArray && key === 'length') return lengthSig?.()
65
65
 
66
66
  // Non-own properties: prototype methods (forEach, map, push, …)
67
67
  // These must be returned untracked so array methods work normally.
@@ -74,7 +74,7 @@ function wrap(raw: object): object {
74
74
  const value = getOrCreateSignal(key)()
75
75
 
76
76
  // Deep reactivity: wrap nested objects/arrays transparently
77
- if (value !== null && typeof value === "object") {
77
+ if (value !== null && typeof value === 'object') {
78
78
  return wrap(value as object)
79
79
  }
80
80
 
@@ -82,7 +82,7 @@ function wrap(raw: object): object {
82
82
  },
83
83
 
84
84
  set(target, key, value) {
85
- if (typeof key === "symbol") {
85
+ if (typeof key === 'symbol') {
86
86
  ;(target as Record<symbol, unknown>)[key] = value
87
87
  return true
88
88
  }
@@ -91,7 +91,7 @@ function wrap(raw: object): object {
91
91
  ;(target as Record<PropertyKey, unknown>)[key] = value
92
92
 
93
93
  // Array length set directly (e.g. arr.length = 0)
94
- if (isArray && key === "length") {
94
+ if (isArray && key === 'length') {
95
95
  lengthSig?.set(value as number)
96
96
  return true
97
97
  }
@@ -113,7 +113,7 @@ function wrap(raw: object): object {
113
113
 
114
114
  deleteProperty(target, key) {
115
115
  delete (target as Record<PropertyKey, unknown>)[key]
116
- if (typeof key !== "symbol" && propSignals.has(key)) {
116
+ if (typeof key !== 'symbol' && propSignals.has(key)) {
117
117
  propSignals.get(key)?.set(undefined)
118
118
  propSignals.delete(key)
119
119
  }
@@ -1,9 +1,9 @@
1
- import { batch, nextTick } from "../batch"
2
- import { effect } from "../effect"
3
- import { signal } from "../signal"
1
+ import { batch, nextTick } from '../batch'
2
+ import { effect } from '../effect'
3
+ import { signal } from '../signal'
4
4
 
5
- describe("batch", () => {
6
- test("defers notifications until end of batch", () => {
5
+ describe('batch', () => {
6
+ test('defers notifications until end of batch', () => {
7
7
  const a = signal(1)
8
8
  const b = signal(2)
9
9
  let runs = 0
@@ -22,7 +22,7 @@ describe("batch", () => {
22
22
  expect(runs).toBe(2)
23
23
  })
24
24
 
25
- test("effect sees final values after batch", () => {
25
+ test('effect sees final values after batch', () => {
26
26
  const s = signal(0)
27
27
  let seen = 0
28
28
  effect(() => {
@@ -36,7 +36,7 @@ describe("batch", () => {
36
36
  expect(seen).toBe(3)
37
37
  })
38
38
 
39
- test("nested batches flush at outermost end", () => {
39
+ test('nested batches flush at outermost end', () => {
40
40
  const s = signal(0)
41
41
  let runs = 0
42
42
  effect(() => {
@@ -55,7 +55,7 @@ describe("batch", () => {
55
55
  expect(runs).toBe(2)
56
56
  })
57
57
 
58
- test("batch propagates exceptions and still flushes", () => {
58
+ test('batch propagates exceptions and still flushes', () => {
59
59
  const s = signal(0)
60
60
  let seen = 0
61
61
  effect(() => {
@@ -66,15 +66,15 @@ describe("batch", () => {
66
66
  expect(() => {
67
67
  batch(() => {
68
68
  s.set(42)
69
- throw new Error("boom")
69
+ throw new Error('boom')
70
70
  })
71
- }).toThrow("boom")
71
+ }).toThrow('boom')
72
72
 
73
73
  // The batch should still have flushed notifications in the finally block
74
74
  expect(seen).toBe(42)
75
75
  })
76
76
 
77
- test("batch with no signal changes is a no-op", () => {
77
+ test('batch with no signal changes is a no-op', () => {
78
78
  let runs = 0
79
79
  const s = signal(0)
80
80
  effect(() => {
@@ -89,7 +89,7 @@ describe("batch", () => {
89
89
  expect(runs).toBe(1)
90
90
  })
91
91
 
92
- test("batch deduplicates same subscriber across multiple signals", () => {
92
+ test('batch deduplicates same subscriber across multiple signals', () => {
93
93
  const a = signal(1)
94
94
  const b = signal(2)
95
95
  let runs = 0
@@ -109,7 +109,7 @@ describe("batch", () => {
109
109
  expect(runs).toBe(2)
110
110
  })
111
111
 
112
- test("notifications enqueued during flush land in alternate set", () => {
112
+ test('notifications enqueued during flush land in alternate set', () => {
113
113
  const a = signal(0)
114
114
  const b = signal(0)
115
115
  const log: string[] = []
@@ -128,11 +128,11 @@ describe("batch", () => {
128
128
  a.set(1)
129
129
  })
130
130
 
131
- expect(log).toContain("a=1")
132
- expect(log).toContain("b=10")
131
+ expect(log).toContain('a=1')
132
+ expect(log).toContain('b=10')
133
133
  })
134
134
 
135
- test("nextTick resolves after microtasks flush", async () => {
135
+ test('nextTick resolves after microtasks flush', async () => {
136
136
  const s = signal(0)
137
137
  let seen = 0
138
138
  effect(() => {
@@ -1,8 +1,8 @@
1
- import { _bind } from "../effect"
2
- import { signal } from "../signal"
1
+ import { _bind } from '../effect'
2
+ import { signal } from '../signal'
3
3
 
4
- describe("_bind (static-dep binding)", () => {
5
- test("runs the function on first call and tracks deps", () => {
4
+ describe('_bind (static-dep binding)', () => {
5
+ test('runs the function on first call and tracks deps', () => {
6
6
  const s = signal(0)
7
7
  let runs = 0
8
8
 
@@ -20,7 +20,7 @@ describe("_bind (static-dep binding)", () => {
20
20
  dispose()
21
21
  })
22
22
 
23
- test("dispose stops re-runs", () => {
23
+ test('dispose stops re-runs', () => {
24
24
  const s = signal(0)
25
25
  let runs = 0
26
26
 
@@ -36,7 +36,7 @@ describe("_bind (static-dep binding)", () => {
36
36
  expect(runs).toBe(1) // no re-run
37
37
  })
38
38
 
39
- test("dispose is idempotent", () => {
39
+ test('dispose is idempotent', () => {
40
40
  const s = signal(0)
41
41
  const dispose = _bind(() => {
42
42
  s()
@@ -46,7 +46,7 @@ describe("_bind (static-dep binding)", () => {
46
46
  dispose() // should not throw
47
47
  })
48
48
 
49
- test("does not re-run after dispose even with multiple deps", () => {
49
+ test('does not re-run after dispose even with multiple deps', () => {
50
50
  const a = signal(0)
51
51
  const b = signal(0)
52
52
  let runs = 0
@@ -65,7 +65,7 @@ describe("_bind (static-dep binding)", () => {
65
65
  expect(runs).toBe(1)
66
66
  })
67
67
 
68
- test("disposed run callback is a no-op", () => {
68
+ test('disposed run callback is a no-op', () => {
69
69
  const s = signal(0)
70
70
  let runs = 0
71
71
 
@@ -1,19 +1,19 @@
1
1
  /**
2
2
  * Targeted tests for uncovered branches across reactivity package.
3
3
  */
4
- import { Cell } from "../cell"
5
- import { computed } from "../computed"
6
- import { createSelector } from "../createSelector"
7
- import { why } from "../debug"
8
- import { _bind, effect, renderEffect } from "../effect"
9
- import { reconcile } from "../reconcile"
10
- import { signal } from "../signal"
11
- import { createStore, isStore } from "../store"
4
+ import { Cell } from '../cell'
5
+ import { computed } from '../computed'
6
+ import { createSelector } from '../createSelector'
7
+ import { why } from '../debug'
8
+ import { _bind, effect, renderEffect } from '../effect'
9
+ import { reconcile } from '../reconcile'
10
+ import { signal } from '../signal'
11
+ import { createStore, isStore } from '../store'
12
12
 
13
13
  // ── cell.ts branches: promote listener to Set ─────────────────────────────────
14
14
 
15
- describe("Cell listener promotion", () => {
16
- test("promotes single listener to Set when second listener added", () => {
15
+ describe('Cell listener promotion', () => {
16
+ test('promotes single listener to Set when second listener added', () => {
17
17
  const c = new Cell(0)
18
18
  const calls: number[] = []
19
19
  c.listen(() => calls.push(1))
@@ -24,7 +24,7 @@ describe("Cell listener promotion", () => {
24
24
  expect(calls).toEqual([1, 2, 3])
25
25
  })
26
26
 
27
- test("subscribe unsubscribes single listener", () => {
27
+ test('subscribe unsubscribes single listener', () => {
28
28
  const c = new Cell(0)
29
29
  const calls: number[] = []
30
30
  const unsub = c.subscribe(() => calls.push(1))
@@ -36,7 +36,7 @@ describe("Cell listener promotion", () => {
36
36
  expect(calls).toEqual([1])
37
37
  })
38
38
 
39
- test("subscribe unsubscribes from Set", () => {
39
+ test('subscribe unsubscribes from Set', () => {
40
40
  const c = new Cell(0)
41
41
  const calls: number[] = []
42
42
  c.listen(() => calls.push(1))
@@ -48,7 +48,7 @@ describe("Cell listener promotion", () => {
48
48
  expect(calls).toEqual([1, 2, 1])
49
49
  })
50
50
 
51
- test("promote to Set when _l was unsubscribed (null _l, null _s)", () => {
51
+ test('promote to Set when _l was unsubscribed (null _l, null _s)', () => {
52
52
  const c = new Cell(0)
53
53
  const fn1 = () => {}
54
54
  // subscribe sets _l, unsub sets _l to null
@@ -62,7 +62,7 @@ describe("Cell listener promotion", () => {
62
62
  c.set(1)
63
63
  })
64
64
 
65
- test("double unsubscribe from single listener is safe", () => {
65
+ test('double unsubscribe from single listener is safe', () => {
66
66
  const c = new Cell(0)
67
67
  const fn1 = () => {}
68
68
  const unsub = c.subscribe(fn1)
@@ -74,8 +74,8 @@ describe("Cell listener promotion", () => {
74
74
 
75
75
  // ── computed.ts branches ──────────────────────────────────────────────────────
76
76
 
77
- describe("computed branches", () => {
78
- test("disposed computed does not recompute", () => {
77
+ describe('computed branches', () => {
78
+ test('disposed computed does not recompute', () => {
79
79
  const s = signal(1)
80
80
  const c = computed(() => s() * 2, { equals: Object.is })
81
81
  expect(c()).toBe(2)
@@ -85,7 +85,7 @@ describe("computed branches", () => {
85
85
  // (it may return stale value or throw — just ensure no crash)
86
86
  })
87
87
 
88
- test("computed with custom equals and subscribers", () => {
88
+ test('computed with custom equals and subscribers', () => {
89
89
  const s = signal(1)
90
90
  const c = computed(() => s() * 2, { equals: Object.is })
91
91
  const values: number[] = []
@@ -100,7 +100,7 @@ describe("computed branches", () => {
100
100
  expect(values).toEqual([2, 4])
101
101
  })
102
102
 
103
- test("computed without custom equals notifies subscribers on dep change", () => {
103
+ test('computed without custom equals notifies subscribers on dep change', () => {
104
104
  const s = signal(1)
105
105
  const c = computed(() => s() * 2)
106
106
  const values: number[] = []
@@ -115,73 +115,73 @@ describe("computed branches", () => {
115
115
 
116
116
  // ── createSelector.ts branches ────────────────────────────────────────────────
117
117
 
118
- describe("createSelector branches", () => {
119
- test("selector with no matching bucket on old value", () => {
120
- const s = signal<string>("a")
118
+ describe('createSelector branches', () => {
119
+ test('selector with no matching bucket on old value', () => {
120
+ const s = signal<string>('a')
121
121
  const isSelected = createSelector(s)
122
122
  // Read "a" — creates bucket for "a"
123
123
  effect(() => {
124
- isSelected("a")
124
+ isSelected('a')
125
125
  })
126
126
  // Change to "b" — old bucket "a" exists, new bucket "b" does not
127
- s.set("b")
127
+ s.set('b')
128
128
  })
129
129
 
130
- test("selector reuses existing host for same value", () => {
131
- const s = signal<string>("a")
130
+ test('selector reuses existing host for same value', () => {
131
+ const s = signal<string>('a')
132
132
  const isSelected = createSelector(s)
133
133
  const results: boolean[] = []
134
134
  effect(() => {
135
- results.push(isSelected("a"))
135
+ results.push(isSelected('a'))
136
136
  })
137
137
  // This second effect creates another subscription to same bucket
138
138
  effect(() => {
139
- results.push(isSelected("a"))
139
+ results.push(isSelected('a'))
140
140
  })
141
141
  expect(results).toEqual([true, true])
142
- s.set("b")
142
+ s.set('b')
143
143
  // Both should see false
144
144
  expect(results).toEqual([true, true, false, false])
145
145
  })
146
146
 
147
- test("selector handles Object.is equality (no change)", () => {
148
- const s = signal<string>("a")
147
+ test('selector handles Object.is equality (no change)', () => {
148
+ const s = signal<string>('a')
149
149
  const isSelected = createSelector(s)
150
150
  let count = 0
151
151
  effect(() => {
152
- isSelected("a")
152
+ isSelected('a')
153
153
  count++
154
154
  })
155
155
  expect(count).toBe(1)
156
156
  // Same value — Object.is check should skip
157
- s.set("a")
157
+ s.set('a')
158
158
  expect(count).toBe(1)
159
159
  })
160
160
 
161
- test("selector query for value with no existing bucket creates one", () => {
162
- const s = signal<string>("a")
161
+ test('selector query for value with no existing bucket creates one', () => {
162
+ const s = signal<string>('a')
163
163
  const isSelected = createSelector(s)
164
164
  // Query outside effect — creates a bucket for "z" that has no subscribers
165
- const result = isSelected("z")
165
+ const result = isSelected('z')
166
166
  expect(result).toBe(false)
167
167
  })
168
168
 
169
- test("selector change when old value has no subscriber bucket", () => {
170
- const s = signal<string>("a")
169
+ test('selector change when old value has no subscriber bucket', () => {
170
+ const s = signal<string>('a')
171
171
  const isSelected = createSelector(s)
172
172
  // Only subscribe to "b", not "a"
173
173
  effect(() => {
174
- isSelected("b")
174
+ isSelected('b')
175
175
  })
176
176
  // Change from "a" to "b" — old value "a" has no bucket (never queried in effect)
177
- s.set("b")
177
+ s.set('b')
178
178
  })
179
179
  })
180
180
 
181
181
  // ── effect.ts branches ────────────────────────────────────────────────────────
182
182
 
183
- describe("effect disposed branches", () => {
184
- test("disposed effect does not re-run", () => {
183
+ describe('effect disposed branches', () => {
184
+ test('disposed effect does not re-run', () => {
185
185
  const s = signal(0)
186
186
  let count = 0
187
187
  const e = effect(() => {
@@ -194,7 +194,7 @@ describe("effect disposed branches", () => {
194
194
  expect(count).toBe(1)
195
195
  })
196
196
 
197
- test("disposed _bind does not re-run", () => {
197
+ test('disposed _bind does not re-run', () => {
198
198
  const s = signal(0)
199
199
  let count = 0
200
200
  const dispose = _bind(() => {
@@ -209,7 +209,7 @@ describe("effect disposed branches", () => {
209
209
  dispose()
210
210
  })
211
211
 
212
- test("disposed renderEffect does not re-run", () => {
212
+ test('disposed renderEffect does not re-run', () => {
213
213
  const s = signal(0)
214
214
  let count = 0
215
215
  const dispose = renderEffect(() => {
@@ -225,27 +225,27 @@ describe("effect disposed branches", () => {
225
225
 
226
226
  // ── store.ts branches ─────────────────────────────────────────────────────────
227
227
 
228
- describe("store branches", () => {
229
- test("setting symbol property", () => {
228
+ describe('store branches', () => {
229
+ test('setting symbol property', () => {
230
230
  const store = createStore({ a: 1 })
231
- const sym = Symbol("test")
232
- ;(store as Record<symbol, unknown>)[sym] = "hello"
233
- expect((store as Record<symbol, unknown>)[sym]).toBe("hello")
231
+ const sym = Symbol('test')
232
+ ;(store as Record<symbol, unknown>)[sym] = 'hello'
233
+ expect((store as Record<symbol, unknown>)[sym]).toBe('hello')
234
234
  })
235
235
 
236
- test("deleteProperty on store", () => {
236
+ test('deleteProperty on store', () => {
237
237
  const store = createStore<Record<string, unknown>>({ a: 1, b: 2 })
238
238
  delete store.b
239
239
  expect(store.b).toBeUndefined()
240
240
  })
241
241
 
242
- test("deleteProperty on store array", () => {
242
+ test('deleteProperty on store array', () => {
243
243
  const store = createStore([1, 2, 3])
244
- delete (store as unknown as Record<string, unknown>)["1"]
244
+ delete (store as unknown as Record<string, unknown>)['1']
245
245
  expect(store[1]).toBeUndefined()
246
246
  })
247
247
 
248
- test("deleteProperty on store with reactive subscriber", () => {
248
+ test('deleteProperty on store with reactive subscriber', () => {
249
249
  const store = createStore<Record<string, unknown>>({ a: 1, b: 2 })
250
250
  // Read 'b' in effect to create propSignal
251
251
  let val: unknown
@@ -261,14 +261,14 @@ describe("store branches", () => {
261
261
 
262
262
  // ── reconcile.ts branches ─────────────────────────────────────────────────────
263
263
 
264
- describe("reconcile branches", () => {
265
- test("reconcile array with non-object source items", () => {
264
+ describe('reconcile branches', () => {
265
+ test('reconcile array with non-object source items', () => {
266
266
  const store = createStore([1, 2, 3])
267
267
  reconcile([4, 5], store)
268
268
  expect([...store]).toEqual([4, 5])
269
269
  })
270
270
 
271
- test("reconcile object with raw (non-store) target value", () => {
271
+ test('reconcile object with raw (non-store) target value', () => {
272
272
  // Create store where nested value isn't yet a store proxy
273
273
  const store = createStore<Record<string, unknown>>({ a: 1 })
274
274
  // Reconcile with nested object — target.a is a number (not store), so it takes the else branch
@@ -276,26 +276,26 @@ describe("reconcile branches", () => {
276
276
  expect((store.a as Record<string, unknown>).nested).toBe(true)
277
277
  })
278
278
 
279
- test("reconcile array with null source entries", () => {
279
+ test('reconcile array with null source entries', () => {
280
280
  const store = createStore([1, null, 3])
281
281
  reconcile([null, 2, null], store)
282
282
  expect([...store]).toEqual([null, 2, null])
283
283
  })
284
284
 
285
- test("reconcile object with null source values", () => {
285
+ test('reconcile object with null source values', () => {
286
286
  const store = createStore<Record<string, unknown>>({ a: { x: 1 }, b: 2 })
287
287
  reconcile({ a: null, b: 2 }, store)
288
288
  expect(store.a).toBeNull()
289
289
  })
290
290
 
291
- test("reconcile array with both source and target as objects (recursive)", () => {
291
+ test('reconcile array with both source and target as objects (recursive)', () => {
292
292
  const store = createStore([{ a: 1 }, { b: 2 }])
293
293
  reconcile([{ a: 10 }, { b: 20 }], store)
294
294
  expect(store[0]?.a).toBe(10)
295
295
  expect(store[1]?.b).toBe(20)
296
296
  })
297
297
 
298
- test("reconcile object where target has store-proxied nested object", () => {
298
+ test('reconcile object where target has store-proxied nested object', () => {
299
299
  const store = createStore<Record<string, Record<string, number>>>({ nested: { x: 1 } })
300
300
  // Access nested to ensure it's proxied as store
301
301
  const _val = store.nested?.x
@@ -304,7 +304,7 @@ describe("reconcile branches", () => {
304
304
  expect(store.nested?.x).toBe(99)
305
305
  })
306
306
 
307
- test("reconcile object where target has raw (non-store) nested object", () => {
307
+ test('reconcile object where target has raw (non-store) nested object', () => {
308
308
  // Don't access nested, so it stays as raw object (not proxied)
309
309
  const store = createStore<Record<string, Record<string, number>>>({ nested: { x: 1 } })
310
310
  // nested has not been accessed via proxy, so isStore(target.nested) is false
@@ -316,25 +316,25 @@ describe("reconcile branches", () => {
316
316
 
317
317
  // ── debug.ts branches ─────────────────────────────────────────────────────────
318
318
 
319
- describe("debug branches", () => {
320
- test("why with exactly 1 subscriber shows singular", async () => {
321
- const s = signal(0, { name: "single" })
319
+ describe('debug branches', () => {
320
+ test('why with exactly 1 subscriber shows singular', async () => {
321
+ const s = signal(0, { name: 'single' })
322
322
  // Add exactly 1 subscriber
323
323
  effect(() => {
324
324
  s()
325
325
  })
326
326
  const logs: string[] = []
327
327
  const origLog = console.log
328
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
328
+ console.log = (...args: unknown[]) => logs.push(args.join(' '))
329
329
  why()
330
330
  s.set(1)
331
331
  // why() auto-disposes via microtask
332
332
  await new Promise((r) => setTimeout(r, 10))
333
333
  console.log = origLog
334
- expect(logs.some((l) => l.includes("1 subscriber"))).toBe(true)
334
+ expect(logs.some((l) => l.includes('1 subscriber'))).toBe(true)
335
335
  })
336
336
 
337
- test("_notifyTraceListeners with no active listeners is noop", () => {
337
+ test('_notifyTraceListeners with no active listeners is noop', () => {
338
338
  // When no listeners registered, this should not throw
339
339
  // (tests the early return / null check)
340
340
  const s = signal(0)