@pyreon/preact-compat 0.11.5 → 0.11.7

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.
@@ -1,6 +1,6 @@
1
- import type { VNodeChild } from "@pyreon/core"
2
- import { h as pyreonH } from "@pyreon/core"
3
- import { mount } from "@pyreon/runtime-dom"
1
+ import type { VNodeChild } from '@pyreon/core'
2
+ import { h as pyreonH } from '@pyreon/core'
3
+ import { mount } from '@pyreon/runtime-dom'
4
4
  import {
5
5
  memo,
6
6
  useCallback,
@@ -12,7 +12,7 @@ import {
12
12
  useReducer,
13
13
  useRef,
14
14
  useState,
15
- } from "../hooks"
15
+ } from '../hooks'
16
16
  import {
17
17
  Component,
18
18
  cloneElement,
@@ -27,13 +27,13 @@ import {
27
27
  render,
28
28
  toChildArray,
29
29
  useContext,
30
- } from "../index"
31
- import type { RenderContext } from "../jsx-runtime"
32
- import { beginRender, endRender, jsx } from "../jsx-runtime"
33
- import { batch, computed, signal, effect as signalEffect } from "../signals"
30
+ } from '../index'
31
+ import type { RenderContext } from '../jsx-runtime'
32
+ import { beginRender, endRender, jsx } from '../jsx-runtime'
33
+ import { batch, computed, signal, effect as signalEffect } from '../signals'
34
34
 
35
35
  function container(): HTMLElement {
36
- const el = document.createElement("div")
36
+ const el = document.createElement('div')
37
37
  document.body.appendChild(el)
38
38
  return el
39
39
  }
@@ -73,133 +73,133 @@ function createHookRunner() {
73
73
  }
74
74
  }
75
75
 
76
- describe("@pyreon/preact-compat", () => {
76
+ describe('@pyreon/preact-compat', () => {
77
77
  // ─── Core API ────────────────────────────────────────────────────────────
78
78
 
79
- test("h() creates VNodes", () => {
80
- const vnode = h("div", { class: "test" }, "hello")
81
- expect(vnode.type).toBe("div")
82
- expect(vnode.props.class).toBe("test")
83
- expect(vnode.children).toContain("hello")
79
+ test('h() creates VNodes', () => {
80
+ const vnode = h('div', { class: 'test' }, 'hello')
81
+ expect(vnode.type).toBe('div')
82
+ expect(vnode.props.class).toBe('test')
83
+ expect(vnode.children).toContain('hello')
84
84
  })
85
85
 
86
- test("createElement is alias for h", () => {
86
+ test('createElement is alias for h', () => {
87
87
  expect(createElement).toBe(h)
88
88
  })
89
89
 
90
- test("Fragment is a symbol", () => {
91
- expect(typeof Fragment).toBe("symbol")
90
+ test('Fragment is a symbol', () => {
91
+ expect(typeof Fragment).toBe('symbol')
92
92
  })
93
93
 
94
- test("render() mounts to DOM", () => {
94
+ test('render() mounts to DOM', () => {
95
95
  const el = container()
96
- render(h("span", null, "mounted"), el)
97
- expect(el.innerHTML).toContain("mounted")
96
+ render(h('span', null, 'mounted'), el)
97
+ expect(el.innerHTML).toContain('mounted')
98
98
  })
99
99
 
100
- test("hydrate() calls hydrateRoot", () => {
100
+ test('hydrate() calls hydrateRoot', () => {
101
101
  const el = container()
102
- el.innerHTML = "<span>hydrated</span>"
103
- hydrate(h("span", null, "hydrated"), el)
104
- expect(el.innerHTML).toContain("hydrated")
102
+ el.innerHTML = '<span>hydrated</span>'
103
+ hydrate(h('span', null, 'hydrated'), el)
104
+ expect(el.innerHTML).toContain('hydrated')
105
105
  })
106
106
 
107
- test("isValidElement detects VNodes", () => {
108
- const vnode = h("div", null)
107
+ test('isValidElement detects VNodes', () => {
108
+ const vnode = h('div', null)
109
109
  expect(isValidElement(vnode)).toBe(true)
110
110
  expect(isValidElement(null)).toBe(false)
111
- expect(isValidElement("string")).toBe(false)
111
+ expect(isValidElement('string')).toBe(false)
112
112
  expect(isValidElement(42)).toBe(false)
113
- expect(isValidElement({ type: "div", props: {}, children: [] })).toBe(true)
113
+ expect(isValidElement({ type: 'div', props: {}, children: [] })).toBe(true)
114
114
  })
115
115
 
116
- test("isValidElement returns false for objects missing required keys", () => {
117
- expect(isValidElement({ type: "div" })).toBe(false)
118
- expect(isValidElement({ type: "div", props: {} })).toBe(false)
116
+ test('isValidElement returns false for objects missing required keys', () => {
117
+ expect(isValidElement({ type: 'div' })).toBe(false)
118
+ expect(isValidElement({ type: 'div', props: {} })).toBe(false)
119
119
  expect(isValidElement({})).toBe(false)
120
120
  expect(isValidElement(undefined)).toBe(false)
121
121
  })
122
122
 
123
- test("toChildArray flattens children", () => {
124
- const result = toChildArray(["a", ["b", ["c"]], null, undefined, false, "d"] as VNodeChild[])
125
- expect(result).toEqual(["a", "b", "c", "d"])
123
+ test('toChildArray flattens children', () => {
124
+ const result = toChildArray(['a', ['b', ['c']], null, undefined, false, 'd'] as VNodeChild[])
125
+ expect(result).toEqual(['a', 'b', 'c', 'd'])
126
126
  })
127
127
 
128
- test("toChildArray handles single non-array child", () => {
129
- const result = toChildArray("hello")
130
- expect(result).toEqual(["hello"])
128
+ test('toChildArray handles single non-array child', () => {
129
+ const result = toChildArray('hello')
130
+ expect(result).toEqual(['hello'])
131
131
  })
132
132
 
133
- test("toChildArray handles null/undefined/boolean at top level", () => {
133
+ test('toChildArray handles null/undefined/boolean at top level', () => {
134
134
  expect(toChildArray(null as unknown as VNodeChild)).toEqual([])
135
135
  expect(toChildArray(undefined as unknown as VNodeChild)).toEqual([])
136
136
  expect(toChildArray(false as unknown as VNodeChild)).toEqual([])
137
137
  expect(toChildArray(true as unknown as VNodeChild)).toEqual([])
138
138
  })
139
139
 
140
- test("toChildArray handles number children", () => {
140
+ test('toChildArray handles number children', () => {
141
141
  const result = toChildArray([1, 2, 3] as VNodeChild[])
142
142
  expect(result).toEqual([1, 2, 3])
143
143
  })
144
144
 
145
- test("cloneElement merges props", () => {
146
- const original = h("div", { class: "a", id: "x" }, "child")
147
- const cloned = cloneElement(original, { class: "b" })
148
- expect(cloned.type).toBe("div")
149
- expect(cloned.props.class).toBe("b")
150
- expect(cloned.props.id).toBe("x")
151
- expect(cloned.children).toContain("child")
145
+ test('cloneElement merges props', () => {
146
+ const original = h('div', { class: 'a', id: 'x' }, 'child')
147
+ const cloned = cloneElement(original, { class: 'b' })
148
+ expect(cloned.type).toBe('div')
149
+ expect(cloned.props.class).toBe('b')
150
+ expect(cloned.props.id).toBe('x')
151
+ expect(cloned.children).toContain('child')
152
152
  })
153
153
 
154
- test("cloneElement replaces children when provided", () => {
155
- const original = h("div", null, "old")
156
- const cloned = cloneElement(original, undefined, "new")
157
- expect(cloned.children).toContain("new")
158
- expect(cloned.children).not.toContain("old")
154
+ test('cloneElement replaces children when provided', () => {
155
+ const original = h('div', null, 'old')
156
+ const cloned = cloneElement(original, undefined, 'new')
157
+ expect(cloned.children).toContain('new')
158
+ expect(cloned.children).not.toContain('old')
159
159
  })
160
160
 
161
- test("cloneElement preserves key from original when not overridden", () => {
162
- const original = h("div", { key: "original-key" }, "child")
163
- const cloned = cloneElement(original, { class: "b" })
164
- expect(cloned.key).toBe("original-key")
161
+ test('cloneElement preserves key from original when not overridden', () => {
162
+ const original = h('div', { key: 'original-key' }, 'child')
163
+ const cloned = cloneElement(original, { class: 'b' })
164
+ expect(cloned.key).toBe('original-key')
165
165
  })
166
166
 
167
- test("cloneElement overrides key when provided in props", () => {
168
- const original = h("div", { key: "original-key" }, "child")
169
- const cloned = cloneElement(original, { key: "new-key" })
170
- expect(cloned.key).toBe("new-key")
167
+ test('cloneElement overrides key when provided in props', () => {
168
+ const original = h('div', { key: 'original-key' }, 'child')
169
+ const cloned = cloneElement(original, { key: 'new-key' })
170
+ expect(cloned.key).toBe('new-key')
171
171
  })
172
172
 
173
- test("cloneElement with no props passes empty override", () => {
174
- const original = h("div", { id: "test" }, "child")
173
+ test('cloneElement with no props passes empty override', () => {
174
+ const original = h('div', { id: 'test' }, 'child')
175
175
  const cloned = cloneElement(original)
176
- expect(cloned.props.id).toBe("test")
177
- expect(cloned.children).toContain("child")
176
+ expect(cloned.props.id).toBe('test')
177
+ expect(cloned.children).toContain('child')
178
178
  })
179
179
 
180
- test("createRef returns { current: null }", () => {
180
+ test('createRef returns { current: null }', () => {
181
181
  const ref = createRef()
182
182
  expect(ref.current).toBe(null)
183
183
  })
184
184
 
185
- test("createContext/useContext work", () => {
186
- const Ctx = createContext("default")
187
- expect(useContext(Ctx)).toBe("default")
185
+ test('createContext/useContext work', () => {
186
+ const Ctx = createContext('default')
187
+ expect(useContext(Ctx)).toBe('default')
188
188
  })
189
189
 
190
- test("options is an empty object", () => {
191
- expect(typeof options).toBe("object")
190
+ test('options is an empty object', () => {
191
+ expect(typeof options).toBe('object')
192
192
  expect(Object.keys(options).length).toBe(0)
193
193
  })
194
194
 
195
- test("Component class setState updates state with object", () => {
195
+ test('Component class setState updates state with object', () => {
196
196
  class Counter extends Component<Record<string, never>, { count: number }> {
197
197
  constructor(props: Record<string, never>) {
198
198
  super(props)
199
199
  this.state = { count: 0 }
200
200
  }
201
201
  override render() {
202
- return h("span", null, String(this.state.count))
202
+ return h('span', null, String(this.state.count))
203
203
  }
204
204
  }
205
205
  const c = new Counter({})
@@ -208,14 +208,14 @@ describe("@pyreon/preact-compat", () => {
208
208
  expect(c.state.count).toBe(5)
209
209
  })
210
210
 
211
- test("Component class setState with updater function", () => {
211
+ test('Component class setState with updater function', () => {
212
212
  class Counter extends Component<Record<string, never>, { count: number }> {
213
213
  constructor(props: Record<string, never>) {
214
214
  super(props)
215
215
  this.state = { count: 0 }
216
216
  }
217
217
  override render() {
218
- return h("span", null, String(this.state.count))
218
+ return h('span', null, String(this.state.count))
219
219
  }
220
220
  }
221
221
  const c = new Counter({})
@@ -224,12 +224,12 @@ describe("@pyreon/preact-compat", () => {
224
224
  expect(c.state.count).toBe(6)
225
225
  })
226
226
 
227
- test("Component class render() returns null by default", () => {
227
+ test('Component class render() returns null by default', () => {
228
228
  const c = new Component({})
229
229
  expect(c.render()).toBe(null)
230
230
  })
231
231
 
232
- test("Component class forceUpdate triggers signal re-fire", () => {
232
+ test('Component class forceUpdate triggers signal re-fire', () => {
233
233
  class MyComp extends Component<Record<string, never>, { value: number }> {
234
234
  constructor(props: Record<string, never>) {
235
235
  super(props)
@@ -244,13 +244,13 @@ describe("@pyreon/preact-compat", () => {
244
244
 
245
245
  // ─── useState ─────────────────────────────────────────────────────────────────
246
246
 
247
- describe("useState", () => {
248
- test("returns [value, setter] — value is the initial value", () => {
247
+ describe('useState', () => {
248
+ test('returns [value, setter] — value is the initial value', () => {
249
249
  const [count] = withHookCtx(() => useState(0))
250
250
  expect(count).toBe(0)
251
251
  })
252
252
 
253
- test("setter updates value on re-render", () => {
253
+ test('setter updates value on re-render', () => {
254
254
  const runner = createHookRunner()
255
255
  const [, setCount] = runner.run(() => useState(0))
256
256
  setCount(5)
@@ -258,7 +258,7 @@ describe("useState", () => {
258
258
  expect(count2).toBe(5)
259
259
  })
260
260
 
261
- test("setter with function updater", () => {
261
+ test('setter with function updater', () => {
262
262
  const runner = createHookRunner()
263
263
  const [, setCount] = runner.run(() => useState(10))
264
264
  setCount((prev) => prev + 1)
@@ -266,7 +266,7 @@ describe("useState", () => {
266
266
  expect(count2).toBe(11)
267
267
  })
268
268
 
269
- test("initializer function is called once", () => {
269
+ test('initializer function is called once', () => {
270
270
  let calls = 0
271
271
  const runner = createHookRunner()
272
272
  runner.run(() =>
@@ -285,7 +285,7 @@ describe("useState", () => {
285
285
  expect(calls).toBe(1)
286
286
  })
287
287
 
288
- test("setter does nothing when value is the same (Object.is)", () => {
288
+ test('setter does nothing when value is the same (Object.is)', () => {
289
289
  const runner = createHookRunner()
290
290
  let rerenders = 0
291
291
  runner.ctx.scheduleRerender = () => {
@@ -298,7 +298,7 @@ describe("useState", () => {
298
298
  expect(rerenders).toBe(1)
299
299
  })
300
300
 
301
- test("re-render in a component via compat JSX runtime", async () => {
301
+ test('re-render in a component via compat JSX runtime', async () => {
302
302
  const el = container()
303
303
  let renderCount = 0
304
304
  let triggerSet: (v: number | ((p: number) => number)) => void = () => {}
@@ -307,44 +307,44 @@ describe("useState", () => {
307
307
  const [count, setCount] = useState(0)
308
308
  renderCount++
309
309
  triggerSet = setCount
310
- return pyreonH("span", null, String(count))
310
+ return pyreonH('span', null, String(count))
311
311
  }
312
312
 
313
313
  const vnode = jsx(Counter, {})
314
314
  mount(vnode, el)
315
- expect(el.textContent).toBe("0")
315
+ expect(el.textContent).toBe('0')
316
316
  const initialRenders = renderCount
317
317
 
318
318
  triggerSet(1)
319
319
  await new Promise<void>((r) => queueMicrotask(r))
320
320
  await new Promise<void>((r) => queueMicrotask(r))
321
- expect(el.textContent).toBe("1")
321
+ expect(el.textContent).toBe('1')
322
322
  expect(renderCount).toBe(initialRenders + 1)
323
323
  })
324
324
  })
325
325
 
326
326
  // ─── useReducer ───────────────────────────────────────────────────────────────
327
327
 
328
- describe("useReducer", () => {
329
- test("dispatch applies reducer", () => {
328
+ describe('useReducer', () => {
329
+ test('dispatch applies reducer', () => {
330
330
  const runner = createHookRunner()
331
- type Action = { type: "inc" } | { type: "dec" }
331
+ type Action = { type: 'inc' } | { type: 'dec' }
332
332
  const reducer = (state: number, action: Action) =>
333
- action.type === "inc" ? state + 1 : state - 1
333
+ action.type === 'inc' ? state + 1 : state - 1
334
334
 
335
335
  const [state0, dispatch] = runner.run(() => useReducer(reducer, 0))
336
336
  expect(state0).toBe(0)
337
337
 
338
- dispatch({ type: "inc" })
338
+ dispatch({ type: 'inc' })
339
339
  const [state1] = runner.run(() => useReducer(reducer, 0))
340
340
  expect(state1).toBe(1)
341
341
 
342
- dispatch({ type: "dec" })
342
+ dispatch({ type: 'dec' })
343
343
  const [state2] = runner.run(() => useReducer(reducer, 0))
344
344
  expect(state2).toBe(0)
345
345
  })
346
346
 
347
- test("initializer function is called once", () => {
347
+ test('initializer function is called once', () => {
348
348
  let calls = 0
349
349
  const runner = createHookRunner()
350
350
  const [state] = runner.run(() =>
@@ -370,22 +370,22 @@ describe("useReducer", () => {
370
370
  expect(calls).toBe(1)
371
371
  })
372
372
 
373
- test("dispatch does nothing when reducer returns same state", () => {
373
+ test('dispatch does nothing when reducer returns same state', () => {
374
374
  const runner = createHookRunner()
375
375
  let rerenders = 0
376
376
  runner.ctx.scheduleRerender = () => {
377
377
  rerenders++
378
378
  }
379
379
  const [, dispatch] = runner.run(() => useReducer((_s: number, _a: string) => 5, 5))
380
- dispatch("anything")
380
+ dispatch('anything')
381
381
  expect(rerenders).toBe(0)
382
382
  })
383
383
  })
384
384
 
385
385
  // ─── useEffect ────────────────────────────────────────────────────────────────
386
386
 
387
- describe("useEffect", () => {
388
- test("effect runs after render via compat JSX runtime", async () => {
387
+ describe('useEffect', () => {
388
+ test('effect runs after render via compat JSX runtime', async () => {
389
389
  const el = container()
390
390
  let effectRuns = 0
391
391
 
@@ -393,7 +393,7 @@ describe("useEffect", () => {
393
393
  useEffect(() => {
394
394
  effectRuns++
395
395
  })
396
- return pyreonH("div", null, "test")
396
+ return pyreonH('div', null, 'test')
397
397
  }
398
398
 
399
399
  mount(jsx(Comp, {}), el)
@@ -401,7 +401,7 @@ describe("useEffect", () => {
401
401
  expect(effectRuns).toBeGreaterThanOrEqual(1)
402
402
  })
403
403
 
404
- test("effect with empty deps runs once", async () => {
404
+ test('effect with empty deps runs once', async () => {
405
405
  const el = container()
406
406
  let effectRuns = 0
407
407
  let triggerSet: (v: number) => void = () => {}
@@ -412,7 +412,7 @@ describe("useEffect", () => {
412
412
  useEffect(() => {
413
413
  effectRuns++
414
414
  }, [])
415
- return pyreonH("div", null, String(count))
415
+ return pyreonH('div', null, String(count))
416
416
  }
417
417
 
418
418
  mount(jsx(Comp, {}), el)
@@ -425,7 +425,7 @@ describe("useEffect", () => {
425
425
  expect(effectRuns).toBe(1)
426
426
  })
427
427
 
428
- test("effect with deps re-runs when deps change", async () => {
428
+ test('effect with deps re-runs when deps change', async () => {
429
429
  const el = container()
430
430
  let effectRuns = 0
431
431
  let triggerSet: (v: number | ((p: number) => number)) => void = () => {}
@@ -436,7 +436,7 @@ describe("useEffect", () => {
436
436
  useEffect(() => {
437
437
  effectRuns++
438
438
  }, [count])
439
- return pyreonH("div", null, String(count))
439
+ return pyreonH('div', null, String(count))
440
440
  }
441
441
 
442
442
  mount(jsx(Comp, {}), el)
@@ -450,7 +450,7 @@ describe("useEffect", () => {
450
450
  expect(effectRuns).toBe(2)
451
451
  })
452
452
 
453
- test("effect cleanup runs before re-execution", async () => {
453
+ test('effect cleanup runs before re-execution', async () => {
454
454
  const el = container()
455
455
  let cleanups = 0
456
456
  let triggerSet: (v: number | ((p: number) => number)) => void = () => {}
@@ -463,7 +463,7 @@ describe("useEffect", () => {
463
463
  cleanups++
464
464
  }
465
465
  }, [count])
466
- return pyreonH("div", null, String(count))
466
+ return pyreonH('div', null, String(count))
467
467
  }
468
468
 
469
469
  mount(jsx(Comp, {}), el)
@@ -477,7 +477,7 @@ describe("useEffect", () => {
477
477
  expect(cleanups).toBe(1)
478
478
  })
479
479
 
480
- test("pendingEffects populated during render", () => {
480
+ test('pendingEffects populated during render', () => {
481
481
  const runner = createHookRunner()
482
482
  runner.run(() => {
483
483
  useEffect(() => {})
@@ -485,7 +485,7 @@ describe("useEffect", () => {
485
485
  expect(runner.ctx.pendingEffects).toHaveLength(1)
486
486
  })
487
487
 
488
- test("effect with same deps does not re-queue", () => {
488
+ test('effect with same deps does not re-queue', () => {
489
489
  const runner = createHookRunner()
490
490
  runner.run(() => {
491
491
  useEffect(() => {}, [1, 2])
@@ -501,8 +501,8 @@ describe("useEffect", () => {
501
501
 
502
502
  // ─── useLayoutEffect ─────────────────────────────────────────────────────────
503
503
 
504
- describe("useLayoutEffect", () => {
505
- test("layout effect runs synchronously during render in compat runtime", () => {
504
+ describe('useLayoutEffect', () => {
505
+ test('layout effect runs synchronously during render in compat runtime', () => {
506
506
  const el = container()
507
507
  let effectRuns = 0
508
508
 
@@ -510,14 +510,14 @@ describe("useLayoutEffect", () => {
510
510
  useLayoutEffect(() => {
511
511
  effectRuns++
512
512
  })
513
- return pyreonH("div", null, "layout")
513
+ return pyreonH('div', null, 'layout')
514
514
  }
515
515
 
516
516
  mount(jsx(Comp, {}), el)
517
517
  expect(effectRuns).toBeGreaterThanOrEqual(1)
518
518
  })
519
519
 
520
- test("pendingLayoutEffects populated during render", () => {
520
+ test('pendingLayoutEffects populated during render', () => {
521
521
  const runner = createHookRunner()
522
522
  runner.run(() => {
523
523
  useLayoutEffect(() => {})
@@ -525,7 +525,7 @@ describe("useLayoutEffect", () => {
525
525
  expect(runner.ctx.pendingLayoutEffects).toHaveLength(1)
526
526
  })
527
527
 
528
- test("layout effect with same deps does not re-queue", () => {
528
+ test('layout effect with same deps does not re-queue', () => {
529
529
  const runner = createHookRunner()
530
530
  runner.run(() => {
531
531
  useLayoutEffect(() => {}, [1])
@@ -541,13 +541,13 @@ describe("useLayoutEffect", () => {
541
541
 
542
542
  // ─── useMemo ──────────────────────────────────────────────────────────────────
543
543
 
544
- describe("useMemo", () => {
545
- test("returns computed value", () => {
544
+ describe('useMemo', () => {
545
+ test('returns computed value', () => {
546
546
  const value = withHookCtx(() => useMemo(() => 3 * 2, []))
547
547
  expect(value).toBe(6)
548
548
  })
549
549
 
550
- test("recomputes when deps change", () => {
550
+ test('recomputes when deps change', () => {
551
551
  const runner = createHookRunner()
552
552
  const v1 = runner.run(() => useMemo(() => 10, [1]))
553
553
  expect(v1).toBe(10)
@@ -562,8 +562,8 @@ describe("useMemo", () => {
562
562
 
563
563
  // ─── useCallback ──────────────────────────────────────────────────────────────
564
564
 
565
- describe("useCallback", () => {
566
- test("returns the same function when deps unchanged", () => {
565
+ describe('useCallback', () => {
566
+ test('returns the same function when deps unchanged', () => {
567
567
  const runner = createHookRunner()
568
568
  const fn1 = () => 42
569
569
  const fn2 = () => 99
@@ -573,7 +573,7 @@ describe("useCallback", () => {
573
573
  expect(result1()).toBe(42)
574
574
  })
575
575
 
576
- test("returns new function when deps change", () => {
576
+ test('returns new function when deps change', () => {
577
577
  const runner = createHookRunner()
578
578
  const fn1 = () => 42
579
579
  const fn2 = () => 99
@@ -587,24 +587,24 @@ describe("useCallback", () => {
587
587
 
588
588
  // ─── useRef ───────────────────────────────────────────────────────────────────
589
589
 
590
- describe("useRef", () => {
591
- test("returns { current } with null default", () => {
590
+ describe('useRef', () => {
591
+ test('returns { current } with null default', () => {
592
592
  const ref = withHookCtx(() => useRef<HTMLDivElement>())
593
593
  expect(ref.current).toBeNull()
594
594
  })
595
595
 
596
- test("returns { current } with initial value", () => {
596
+ test('returns { current } with initial value', () => {
597
597
  const ref = withHookCtx(() => useRef(42))
598
598
  expect(ref.current).toBe(42)
599
599
  })
600
600
 
601
- test("current is mutable", () => {
601
+ test('current is mutable', () => {
602
602
  const ref = withHookCtx(() => useRef(0))
603
603
  ref.current = 10
604
604
  expect(ref.current).toBe(10)
605
605
  })
606
606
 
607
- test("same ref object persists across re-renders", () => {
607
+ test('same ref object persists across re-renders', () => {
608
608
  const runner = createHookRunner()
609
609
  const ref1 = runner.run(() => useRef(0))
610
610
  ref1.current = 99
@@ -616,27 +616,27 @@ describe("useRef", () => {
616
616
 
617
617
  // ─── memo ─────────────────────────────────────────────────────────────────────
618
618
 
619
- describe("memo", () => {
620
- test("skips re-render when props are shallowly equal", () => {
619
+ describe('memo', () => {
620
+ test('skips re-render when props are shallowly equal', () => {
621
621
  let renderCount = 0
622
622
  const MyComp = (props: { name: string }) => {
623
623
  renderCount++
624
- return pyreonH("span", null, props.name)
624
+ return pyreonH('span', null, props.name)
625
625
  }
626
626
  const Memoized = memo(MyComp)
627
- Memoized({ name: "a" })
627
+ Memoized({ name: 'a' })
628
628
  expect(renderCount).toBe(1)
629
- Memoized({ name: "a" })
629
+ Memoized({ name: 'a' })
630
630
  expect(renderCount).toBe(1)
631
- Memoized({ name: "b" })
631
+ Memoized({ name: 'b' })
632
632
  expect(renderCount).toBe(2)
633
633
  })
634
634
 
635
- test("custom areEqual function", () => {
635
+ test('custom areEqual function', () => {
636
636
  let renderCount = 0
637
637
  const MyComp = (props: { x: number; y: number }) => {
638
638
  renderCount++
639
- return pyreonH("span", null, String(props.x))
639
+ return pyreonH('span', null, String(props.x))
640
640
  }
641
641
  const Memoized = memo(MyComp, (prev, next) => prev.x === next.x)
642
642
  Memoized({ x: 1, y: 1 })
@@ -647,11 +647,11 @@ describe("memo", () => {
647
647
  expect(renderCount).toBe(2)
648
648
  })
649
649
 
650
- test("different number of keys triggers re-render", () => {
650
+ test('different number of keys triggers re-render', () => {
651
651
  let renderCount = 0
652
652
  const MyComp = (_props: Record<string, unknown>) => {
653
653
  renderCount++
654
- return pyreonH("span", null, "x")
654
+ return pyreonH('span', null, 'x')
655
655
  }
656
656
  const Memoized = memo(MyComp)
657
657
  Memoized({ a: 1 })
@@ -663,25 +663,25 @@ describe("memo", () => {
663
663
 
664
664
  // ─── useId ────────────────────────────────────────────────────────────────────
665
665
 
666
- describe("useId", () => {
667
- test("returns a unique string within a component", () => {
666
+ describe('useId', () => {
667
+ test('returns a unique string within a component', () => {
668
668
  const el = container()
669
669
  const ids: string[] = []
670
670
 
671
671
  const Comp = () => {
672
672
  ids.push(useId())
673
673
  ids.push(useId())
674
- return pyreonH("div", null, "id-test")
674
+ return pyreonH('div', null, 'id-test')
675
675
  }
676
676
 
677
677
  mount(jsx(Comp, {}), el)
678
678
  expect(ids.length).toBeGreaterThanOrEqual(2)
679
679
  expect(ids[0]).not.toBe(ids[1])
680
- expect(typeof ids[0]).toBe("string")
681
- expect(ids[0]?.startsWith(":r")).toBe(true)
680
+ expect(typeof ids[0]).toBe('string')
681
+ expect(ids[0]?.startsWith(':r')).toBe(true)
682
682
  })
683
683
 
684
- test("IDs are stable across re-renders", async () => {
684
+ test('IDs are stable across re-renders', async () => {
685
685
  const el = container()
686
686
  const idHistory: string[] = []
687
687
  let triggerSet: (v: number) => void = () => {}
@@ -691,7 +691,7 @@ describe("useId", () => {
691
691
  triggerSet = setCount
692
692
  const id = useId()
693
693
  idHistory.push(id)
694
- return pyreonH("div", null, `${id}-${count}`)
694
+ return pyreonH('div', null, `${id}-${count}`)
695
695
  }
696
696
 
697
697
  mount(jsx(Comp, {}), el)
@@ -710,23 +710,23 @@ describe("useId", () => {
710
710
 
711
711
  // ─── useErrorBoundary ────────────────────────────────────────────────────────
712
712
 
713
- describe("useErrorBoundary", () => {
714
- test("is exported as a function", () => {
715
- expect(typeof useErrorBoundary).toBe("function")
713
+ describe('useErrorBoundary', () => {
714
+ test('is exported as a function', () => {
715
+ expect(typeof useErrorBoundary).toBe('function')
716
716
  })
717
717
  })
718
718
 
719
719
  // ─── Signals ─────────────────────────────────────────────────────────────────
720
720
 
721
- describe("signals", () => {
722
- test("signal() has .value accessor", () => {
721
+ describe('signals', () => {
722
+ test('signal() has .value accessor', () => {
723
723
  const count = signal(0)
724
724
  expect(count.value).toBe(0)
725
725
  count.value = 5
726
726
  expect(count.value).toBe(5)
727
727
  })
728
728
 
729
- test("computed() has .value accessor", () => {
729
+ test('computed() has .value accessor', () => {
730
730
  const count = signal(3)
731
731
  const doubled = computed(() => count.value * 2)
732
732
  expect(doubled.value).toBe(6)
@@ -734,7 +734,7 @@ describe("signals", () => {
734
734
  expect(doubled.value).toBe(20)
735
735
  })
736
736
 
737
- test("computed() peek returns value", () => {
737
+ test('computed() peek returns value', () => {
738
738
  const count = signal(3)
739
739
  const doubled = computed(() => count.value * 2)
740
740
  expect(doubled.peek()).toBe(6)
@@ -742,7 +742,7 @@ describe("signals", () => {
742
742
  expect(doubled.peek()).toBe(20)
743
743
  })
744
744
 
745
- test("effect() tracks signal reads", () => {
745
+ test('effect() tracks signal reads', () => {
746
746
  const count = signal(0)
747
747
  let observed = -1
748
748
  const dispose = signalEffect(() => {
@@ -756,7 +756,7 @@ describe("signals", () => {
756
756
  expect(observed).toBe(7)
757
757
  })
758
758
 
759
- test("effect() with cleanup function", () => {
759
+ test('effect() with cleanup function', () => {
760
760
  const count = signal(0)
761
761
  let cleanups = 0
762
762
  const dispose = signalEffect(() => {
@@ -772,7 +772,7 @@ describe("signals", () => {
772
772
  expect(cleanups).toBe(2)
773
773
  })
774
774
 
775
- test("effect() with non-function return (no cleanup)", () => {
775
+ test('effect() with non-function return (no cleanup)', () => {
776
776
  const count = signal(0)
777
777
  let runs = 0
778
778
  const dispose = signalEffect(() => {
@@ -785,7 +785,7 @@ describe("signals", () => {
785
785
  dispose()
786
786
  })
787
787
 
788
- test("batch() coalesces updates", () => {
788
+ test('batch() coalesces updates', () => {
789
789
  const a = signal(1)
790
790
  const b = signal(2)
791
791
  let runs = 0
@@ -802,7 +802,7 @@ describe("signals", () => {
802
802
  expect(runs).toBe(2)
803
803
  })
804
804
 
805
- test("signal peek() reads without tracking", () => {
805
+ test('signal peek() reads without tracking', () => {
806
806
  const count = signal(0)
807
807
  let observed = -1
808
808
  const dispose = signalEffect(() => {
@@ -817,91 +817,91 @@ describe("signals", () => {
817
817
 
818
818
  // ─── jsx-runtime ──────────────────────────────────────────────────────────────
819
819
 
820
- describe("jsx-runtime", () => {
821
- test("jsx with string type creates element VNode", () => {
822
- const vnode = jsx("div", { children: "hello" })
823
- expect(vnode.type).toBe("div")
824
- expect(vnode.children).toContain("hello")
820
+ describe('jsx-runtime', () => {
821
+ test('jsx with string type creates element VNode', () => {
822
+ const vnode = jsx('div', { children: 'hello' })
823
+ expect(vnode.type).toBe('div')
824
+ expect(vnode.children).toContain('hello')
825
825
  })
826
826
 
827
- test("jsx with key prop", () => {
828
- const vnode = jsx("div", { children: "x" }, "my-key")
829
- expect(vnode.props.key).toBe("my-key")
827
+ test('jsx with key prop', () => {
828
+ const vnode = jsx('div', { children: 'x' }, 'my-key')
829
+ expect(vnode.props.key).toBe('my-key')
830
830
  })
831
831
 
832
- test("jsx with component wraps for re-render", () => {
833
- const MyComp = () => pyreonH("span", null, "hi")
832
+ test('jsx with component wraps for re-render', () => {
833
+ const MyComp = () => pyreonH('span', null, 'hi')
834
834
  const vnode = jsx(MyComp, {})
835
835
  expect(vnode.type).not.toBe(MyComp)
836
- expect(typeof vnode.type).toBe("function")
836
+ expect(typeof vnode.type).toBe('function')
837
837
  })
838
838
 
839
- test("jsx with Fragment", () => {
839
+ test('jsx with Fragment', () => {
840
840
  const vnode = jsx(Fragment, {
841
- children: [pyreonH("span", null, "a"), pyreonH("span", null, "b")],
841
+ children: [pyreonH('span', null, 'a'), pyreonH('span', null, 'b')],
842
842
  })
843
843
  expect(vnode.type).toBe(Fragment)
844
844
  })
845
845
 
846
- test("jsx with single child (not array)", () => {
847
- const vnode = jsx("div", { children: "text" })
846
+ test('jsx with single child (not array)', () => {
847
+ const vnode = jsx('div', { children: 'text' })
848
848
  expect(vnode.children).toHaveLength(1)
849
849
  })
850
850
 
851
- test("jsx with no children", () => {
852
- const vnode = jsx("div", {})
851
+ test('jsx with no children', () => {
852
+ const vnode = jsx('div', {})
853
853
  expect(vnode.children).toHaveLength(0)
854
854
  })
855
855
 
856
- test("jsx component with children in props", () => {
857
- const MyComp = (props: { children?: string }) => pyreonH("div", null, props.children ?? "")
858
- const vnode = jsx(MyComp, { children: "child-text" })
859
- expect(typeof vnode.type).toBe("function")
856
+ test('jsx component with children in props', () => {
857
+ const MyComp = (props: { children?: string }) => pyreonH('div', null, props.children ?? '')
858
+ const vnode = jsx(MyComp, { children: 'child-text' })
859
+ expect(typeof vnode.type).toBe('function')
860
860
  })
861
861
  })
862
862
 
863
863
  // ─── Hooks outside component ─────────────────────────────────────────────────
864
864
 
865
- describe("hooks outside component", () => {
866
- test("useState throws when called outside render", () => {
867
- expect(() => useState(0)).toThrow("Hook called outside")
865
+ describe('hooks outside component', () => {
866
+ test('useState throws when called outside render', () => {
867
+ expect(() => useState(0)).toThrow('Hook called outside')
868
868
  })
869
869
 
870
- test("useEffect throws when called outside render", () => {
871
- expect(() => useEffect(() => {})).toThrow("Hook called outside")
870
+ test('useEffect throws when called outside render', () => {
871
+ expect(() => useEffect(() => {})).toThrow('Hook called outside')
872
872
  })
873
873
 
874
- test("useRef throws when called outside render", () => {
875
- expect(() => useRef(0)).toThrow("Hook called outside")
874
+ test('useRef throws when called outside render', () => {
875
+ expect(() => useRef(0)).toThrow('Hook called outside')
876
876
  })
877
877
 
878
- test("useMemo throws when called outside render", () => {
879
- expect(() => useMemo(() => 0, [])).toThrow("Hook called outside")
878
+ test('useMemo throws when called outside render', () => {
879
+ expect(() => useMemo(() => 0, [])).toThrow('Hook called outside')
880
880
  })
881
881
 
882
- test("useId throws when called outside render", () => {
883
- expect(() => useId()).toThrow("Hook called outside")
882
+ test('useId throws when called outside render', () => {
883
+ expect(() => useId()).toThrow('Hook called outside')
884
884
  })
885
885
 
886
- test("useReducer throws when called outside render", () => {
887
- expect(() => useReducer((s: number) => s, 0)).toThrow("Hook called outside")
886
+ test('useReducer throws when called outside render', () => {
887
+ expect(() => useReducer((s: number) => s, 0)).toThrow('Hook called outside')
888
888
  })
889
889
  })
890
890
 
891
891
  // ─── Edge cases ──────────────────────────────────────────────────────────────
892
892
 
893
- describe("edge cases", () => {
894
- test("useState with string initial", () => {
895
- const [val] = withHookCtx(() => useState("hello"))
896
- expect(val).toBe("hello")
893
+ describe('edge cases', () => {
894
+ test('useState with string initial', () => {
895
+ const [val] = withHookCtx(() => useState('hello'))
896
+ expect(val).toBe('hello')
897
897
  })
898
898
 
899
- test("useReducer with non-function initial", () => {
900
- const [state] = withHookCtx(() => useReducer((s: string, a: string) => s + a, "start"))
901
- expect(state).toBe("start")
899
+ test('useReducer with non-function initial', () => {
900
+ const [state] = withHookCtx(() => useReducer((s: string, a: string) => s + a, 'start'))
901
+ expect(state).toBe('start')
902
902
  })
903
903
 
904
- test("depsChanged handles different length arrays", () => {
904
+ test('depsChanged handles different length arrays', () => {
905
905
  const runner = createHookRunner()
906
906
  runner.run(() => {
907
907
  useEffect(() => {}, [1, 2])
@@ -914,7 +914,7 @@ describe("edge cases", () => {
914
914
  expect(runner.ctx.pendingEffects).toHaveLength(1)
915
915
  })
916
916
 
917
- test("depsChanged with undefined deps always re-runs", () => {
917
+ test('depsChanged with undefined deps always re-runs', () => {
918
918
  const runner = createHookRunner()
919
919
  runner.run(() => {
920
920
  useEffect(() => {})