@pyreon/runtime-dom 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.
@@ -11,17 +11,17 @@
11
11
  * - mount.ts (lines 204-206)
12
12
  * - hydration-debug.ts (line 35)
13
13
  */
14
- import type { ComponentFn, VNodeChild } from "@pyreon/core"
15
- import { createRef, defineComponent, For, Fragment, h, onMount, onUnmount } from "@pyreon/core"
16
- import { signal } from "@pyreon/reactivity"
14
+ import type { ComponentFn, VNodeChild } from '@pyreon/core'
15
+ import { createRef, defineComponent, For, Fragment, h, onMount, onUnmount } from '@pyreon/core'
16
+ import { signal } from '@pyreon/reactivity'
17
17
  import {
18
18
  installDevTools,
19
19
  onOverlayClick,
20
20
  onOverlayMouseMove,
21
21
  registerComponent,
22
22
  unregisterComponent,
23
- } from "../devtools"
24
- import { warnHydrationMismatch } from "../hydration-debug"
23
+ } from '../devtools'
24
+ import { warnHydrationMismatch } from '../hydration-debug'
25
25
  import {
26
26
  KeepAlive as _KeepAlive,
27
27
  Transition as _Transition,
@@ -33,86 +33,86 @@ import {
33
33
  mount,
34
34
  sanitizeHtml,
35
35
  setSanitizer,
36
- } from "../index"
37
- import { mountChild } from "../mount"
38
- import { applyProp } from "../props"
36
+ } from '../index'
37
+ import { mountChild } from '../mount'
38
+ import { applyProp } from '../props'
39
39
 
40
40
  const Transition = _Transition as unknown as ComponentFn<Record<string, unknown>>
41
41
  const TransitionGroup = _TransitionGroup as unknown as ComponentFn<Record<string, unknown>>
42
42
  const KeepAlive = _KeepAlive as unknown as ComponentFn<Record<string, unknown>>
43
43
 
44
44
  function container(): HTMLElement {
45
- const el = document.createElement("div")
45
+ const el = document.createElement('div')
46
46
  document.body.appendChild(el)
47
47
  return el
48
48
  }
49
49
 
50
50
  // ─── hydration-debug.ts — line 35: _enabled=false early return ──────────────
51
51
 
52
- describe("hydration-debug — disabled warnings branch", () => {
53
- test("warnHydrationMismatch does nothing when warnings disabled", () => {
54
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
52
+ describe('hydration-debug — disabled warnings branch', () => {
53
+ test('warnHydrationMismatch does nothing when warnings disabled', () => {
54
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
55
55
  disableHydrationWarnings()
56
- warnHydrationMismatch("tag", "div", "span", "root > test")
56
+ warnHydrationMismatch('tag', 'div', 'span', 'root > test')
57
57
  expect(warnSpy).not.toHaveBeenCalled()
58
58
  enableHydrationWarnings()
59
59
  warnSpy.mockRestore()
60
60
  })
61
61
 
62
- test("warnHydrationMismatch emits when warnings enabled", () => {
63
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
62
+ test('warnHydrationMismatch emits when warnings enabled', () => {
63
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
64
64
  enableHydrationWarnings()
65
- warnHydrationMismatch("tag", "div", "span", "root > test")
66
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("Hydration mismatch"))
65
+ warnHydrationMismatch('tag', 'div', 'span', 'root > test')
66
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Hydration mismatch'))
67
67
  warnSpy.mockRestore()
68
68
  })
69
69
  })
70
70
 
71
71
  // ─── devtools.ts — line 139: tooltip below element when rect.top < 35 ──────
72
72
 
73
- describe("devtools — tooltip repositioning and click paths", () => {
73
+ describe('devtools — tooltip repositioning and click paths', () => {
74
74
  beforeAll(() => {
75
75
  installDevTools()
76
76
  })
77
77
 
78
- test("highlight with valid element applies and removes outline", async () => {
78
+ test('highlight with valid element applies and removes outline', async () => {
79
79
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
80
80
  highlight: (id: string) => void
81
81
  }
82
- const target = document.createElement("div")
82
+ const target = document.createElement('div')
83
83
  document.body.appendChild(target)
84
- registerComponent("highlight-test", "HighlightComp", target, null)
84
+ registerComponent('highlight-test', 'HighlightComp', target, null)
85
85
 
86
- devtools.highlight("highlight-test")
87
- expect((target as HTMLElement).style.outline).toContain("#00b4d8")
86
+ devtools.highlight('highlight-test')
87
+ expect((target as HTMLElement).style.outline).toContain('#00b4d8')
88
88
 
89
89
  // Wait for the timeout to clear the outline (line 226)
90
90
  await new Promise<void>((r) => setTimeout(r, 1600))
91
- expect((target as HTMLElement).style.outline).toBe("")
91
+ expect((target as HTMLElement).style.outline).toBe('')
92
92
 
93
- unregisterComponent("highlight-test")
93
+ unregisterComponent('highlight-test')
94
94
  target.remove()
95
95
  })
96
96
 
97
- test("highlight with nonexistent id does nothing", () => {
97
+ test('highlight with nonexistent id does nothing', () => {
98
98
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
99
99
  highlight: (id: string) => void
100
100
  }
101
101
  // Should not throw
102
- devtools.highlight("nonexistent-id")
102
+ devtools.highlight('nonexistent-id')
103
103
  })
104
104
 
105
- test("highlight with no element (el: null) does nothing", () => {
105
+ test('highlight with no element (el: null) does nothing', () => {
106
106
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
107
107
  highlight: (id: string) => void
108
108
  }
109
- registerComponent("no-el", "NoElComp", null, null)
109
+ registerComponent('no-el', 'NoElComp', null, null)
110
110
  // entry exists but el is null — should early return (line 221)
111
- devtools.highlight("no-el")
112
- unregisterComponent("no-el")
111
+ devtools.highlight('no-el')
112
+ unregisterComponent('no-el')
113
113
  })
114
114
 
115
- test("onComponentMount listener fires on register", () => {
115
+ test('onComponentMount listener fires on register', () => {
116
116
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
117
117
  onComponentMount: (cb: (entry: unknown) => void) => () => void
118
118
  onComponentUnmount: (cb: (id: string) => void) => () => void
@@ -121,33 +121,33 @@ describe("devtools — tooltip repositioning and click paths", () => {
121
121
  const unsub = devtools.onComponentMount((entry) => {
122
122
  mountedEntry = entry
123
123
  })
124
- registerComponent("listener-test", "ListenerComp", null, null)
124
+ registerComponent('listener-test', 'ListenerComp', null, null)
125
125
  expect(mountedEntry).not.toBeNull()
126
126
 
127
127
  let unmountedId: string | null = null
128
128
  const unsub2 = devtools.onComponentUnmount((id) => {
129
129
  unmountedId = id
130
130
  })
131
- unregisterComponent("listener-test")
132
- expect(unmountedId).toBe("listener-test")
131
+ unregisterComponent('listener-test')
132
+ expect(unmountedId).toBe('listener-test')
133
133
 
134
134
  // Unsubscribe
135
135
  unsub()
136
136
  unsub2()
137
137
  })
138
138
 
139
- test("unregisterComponent with non-existent id does nothing", () => {
139
+ test('unregisterComponent with non-existent id does nothing', () => {
140
140
  // Should not throw — early return on line 61
141
- unregisterComponent("does-not-exist")
141
+ unregisterComponent('does-not-exist')
142
142
  })
143
143
 
144
- test("unregisterComponent with no parent does not try to update parent", () => {
145
- registerComponent("orphan", "Orphan", null, null)
144
+ test('unregisterComponent with no parent does not try to update parent', () => {
145
+ registerComponent('orphan', 'Orphan', null, null)
146
146
  // parentId is null — should skip parent.childIds update
147
- unregisterComponent("orphan")
147
+ unregisterComponent('orphan')
148
148
  })
149
149
 
150
- test("onComponentMount unsubscribe with already-removed listener is safe", () => {
150
+ test('onComponentMount unsubscribe with already-removed listener is safe', () => {
151
151
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
152
152
  onComponentMount: (cb: (entry: unknown) => void) => () => void
153
153
  }
@@ -158,7 +158,7 @@ describe("devtools — tooltip repositioning and click paths", () => {
158
158
  unsub()
159
159
  })
160
160
 
161
- test("onComponentUnmount unsubscribe with already-removed listener is safe", () => {
161
+ test('onComponentUnmount unsubscribe with already-removed listener is safe', () => {
162
162
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
163
163
  onComponentUnmount: (cb: (id: string) => void) => () => void
164
164
  }
@@ -168,13 +168,13 @@ describe("devtools — tooltip repositioning and click paths", () => {
168
168
  unsub()
169
169
  })
170
170
 
171
- test("installDevTools called again is noop (already installed)", () => {
171
+ test('installDevTools called again is noop (already installed)', () => {
172
172
  // Second call should return early (line 205: _installed = true)
173
173
  installDevTools()
174
174
  expect((window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__).toBeDefined()
175
175
  })
176
176
 
177
- test("overlay click path — entry found with parentId triggers parent log", () => {
177
+ test('overlay click path — entry found with parentId triggers parent log', () => {
178
178
  // We need to actually exercise the onOverlayClick code paths.
179
179
  // In happy-dom, elementFromPoint may return null, so let's test the
180
180
  // code path by directly triggering click events and checking no errors.
@@ -183,51 +183,51 @@ describe("devtools — tooltip repositioning and click paths", () => {
183
183
  disableOverlay: () => void
184
184
  }
185
185
 
186
- const parentEl = document.createElement("div")
187
- parentEl.style.cssText = "width:200px;height:200px;position:fixed;top:0;left:0;"
186
+ const parentEl = document.createElement('div')
187
+ parentEl.style.cssText = 'width:200px;height:200px;position:fixed;top:0;left:0;'
188
188
  document.body.appendChild(parentEl)
189
189
 
190
- const childEl = document.createElement("span")
191
- childEl.style.cssText = "width:50px;height:50px;"
190
+ const childEl = document.createElement('span')
191
+ childEl.style.cssText = 'width:50px;height:50px;'
192
192
  parentEl.appendChild(childEl)
193
193
 
194
- registerComponent("click-p", "ClickParent", parentEl, null)
195
- registerComponent("click-c", "ClickChild", childEl, "click-p")
194
+ registerComponent('click-p', 'ClickParent', parentEl, null)
195
+ registerComponent('click-c', 'ClickChild', childEl, 'click-p')
196
196
 
197
197
  devtools.enableOverlay()
198
198
 
199
199
  // Simulate click
200
- const event = new MouseEvent("click", { clientX: 25, clientY: 25, bubbles: true })
200
+ const event = new MouseEvent('click', { clientX: 25, clientY: 25, bubbles: true })
201
201
  document.dispatchEvent(event)
202
202
 
203
203
  devtools.disableOverlay()
204
- unregisterComponent("click-c")
205
- unregisterComponent("click-p")
204
+ unregisterComponent('click-c')
205
+ unregisterComponent('click-p')
206
206
  parentEl.remove()
207
207
  })
208
208
 
209
- test("overlay click on null target returns early", () => {
209
+ test('overlay click on null target returns early', () => {
210
210
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
211
211
  enableOverlay: () => void
212
212
  disableOverlay: () => void
213
213
  }
214
214
  devtools.enableOverlay()
215
215
  // dispatch click; in happy-dom elementFromPoint may return null → line 148 return
216
- const event = new MouseEvent("click", { clientX: -1, clientY: -1, bubbles: true })
216
+ const event = new MouseEvent('click', { clientX: -1, clientY: -1, bubbles: true })
217
217
  document.dispatchEvent(event)
218
218
  devtools.disableOverlay()
219
219
  })
220
220
 
221
- test("overlay mousemove on overlay/tooltip element itself is ignored", () => {
221
+ test('overlay mousemove on overlay/tooltip element itself is ignored', () => {
222
222
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
223
223
  enableOverlay: () => void
224
224
  disableOverlay: () => void
225
225
  }
226
226
  devtools.enableOverlay()
227
227
  // The overlay div itself should be ignored (line 107)
228
- const overlayEl = document.getElementById("__pyreon-overlay")
228
+ const overlayEl = document.getElementById('__pyreon-overlay')
229
229
  if (overlayEl) {
230
- const event = new MouseEvent("mousemove", { clientX: 0, clientY: 0, bubbles: true })
230
+ const event = new MouseEvent('mousemove', { clientX: 0, clientY: 0, bubbles: true })
231
231
  overlayEl.dispatchEvent(event)
232
232
  }
233
233
  devtools.disableOverlay()
@@ -236,46 +236,46 @@ describe("devtools — tooltip repositioning and click paths", () => {
236
236
 
237
237
  // ─── keep-alive.ts — line 48 (no container) and line 55 (no children) ───────
238
238
 
239
- describe("KeepAlive — edge cases", () => {
240
- test("KeepAlive with no active prop defaults to visible", () => {
239
+ describe('KeepAlive — edge cases', () => {
240
+ test('KeepAlive with no active prop defaults to visible', () => {
241
241
  const el = container()
242
- const unmount = mount(h(KeepAlive, {}, h("span", null, "always-visible")), el)
242
+ const unmount = mount(h(KeepAlive, {}, h('span', null, 'always-visible')), el)
243
243
  // Should render and be visible (active defaults to true)
244
- expect(el.querySelector("span")?.textContent).toBe("always-visible")
244
+ expect(el.querySelector('span')?.textContent).toBe('always-visible')
245
245
  unmount()
246
246
  })
247
247
 
248
- test("KeepAlive toggles visibility without remounting", () => {
248
+ test('KeepAlive toggles visibility without remounting', () => {
249
249
  const el = container()
250
250
  const active = signal(true)
251
- const unmount = mount(h(KeepAlive, { active: () => active() }, h("span", null, "toggle")), el)
252
- expect(el.querySelector("span")?.textContent).toBe("toggle")
251
+ const unmount = mount(h(KeepAlive, { active: () => active() }, h('span', null, 'toggle')), el)
252
+ expect(el.querySelector('span')?.textContent).toBe('toggle')
253
253
 
254
254
  // Hide
255
255
  active.set(false)
256
- const wrapper = el.querySelector("div") as HTMLElement
257
- expect(wrapper?.style.display).toBe("none")
256
+ const wrapper = el.querySelector('div') as HTMLElement
257
+ expect(wrapper?.style.display).toBe('none')
258
258
 
259
259
  // Show again — same element, not remounted
260
260
  active.set(true)
261
- expect(wrapper?.style.display).toBe("")
261
+ expect(wrapper?.style.display).toBe('')
262
262
 
263
263
  unmount()
264
264
  })
265
265
 
266
- test("KeepAlive with no children mounts empty container", () => {
266
+ test('KeepAlive with no children mounts empty container', () => {
267
267
  const el = container()
268
268
  const unmount = mount(h(KeepAlive, { active: () => true }), el)
269
269
  // Container div exists but no children
270
- expect(el.querySelector("div")).not.toBeNull()
270
+ expect(el.querySelector('div')).not.toBeNull()
271
271
  unmount()
272
272
  })
273
273
  })
274
274
 
275
275
  // ─── nodes.ts — lines 175-178 (LIS typed array growth), line 338 (dev dup key warn) ─────
276
276
 
277
- describe("nodes.ts — LIS array growth and dev warnings", () => {
278
- test("mountFor with > 16 items triggers typed array growth (lines 175-178)", () => {
277
+ describe('nodes.ts — LIS array growth and dev warnings', () => {
278
+ test('mountFor with > 16 items triggers typed array growth (lines 175-178)', () => {
279
279
  const el = container()
280
280
  // Create > 16 items to trigger array growth in LIS path
281
281
  const initial = Array.from({ length: 20 }, (_, i) => ({ id: i, label: `item-${i}` }))
@@ -283,64 +283,64 @@ describe("nodes.ts — LIS array growth and dev warnings", () => {
283
283
 
284
284
  mount(
285
285
  h(
286
- "div",
286
+ 'div',
287
287
  null,
288
288
  For({
289
289
  each: items,
290
290
  by: (r: { id: number }) => r.id,
291
- children: (r: { id: number; label: string }) => h("span", null, r.label),
291
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
292
292
  }),
293
293
  ),
294
294
  el,
295
295
  )
296
- expect(el.querySelectorAll("span").length).toBe(20)
296
+ expect(el.querySelectorAll('span').length).toBe(20)
297
297
 
298
298
  // Reverse to trigger full LIS reorder with > 16 entries
299
299
  const reversed = [...initial].reverse()
300
300
  items.set(reversed)
301
- expect(el.querySelectorAll("span").length).toBe(20)
301
+ expect(el.querySelectorAll('span').length).toBe(20)
302
302
 
303
303
  el.remove()
304
304
  })
305
305
 
306
- test("mountFor duplicate key warning in dev mode (line 338)", () => {
306
+ test('mountFor duplicate key warning in dev mode (line 338)', () => {
307
307
  const el = container()
308
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
308
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
309
309
 
310
310
  const items = signal([{ id: 1 }, { id: 1 }]) // duplicate keys
311
311
 
312
312
  mount(
313
313
  h(
314
- "div",
314
+ 'div',
315
315
  null,
316
316
  For({
317
317
  each: items,
318
318
  by: (r: { id: number }) => r.id,
319
- children: (r: { id: number }) => h("span", null, String(r.id)),
319
+ children: (r: { id: number }) => h('span', null, String(r.id)),
320
320
  }),
321
321
  ),
322
322
  el,
323
323
  )
324
324
 
325
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("Duplicate key"))
325
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Duplicate key'))
326
326
  warnSpy.mockRestore()
327
327
  el.remove()
328
328
  })
329
329
 
330
- test("mountFor duplicate key warning on update (line 385)", () => {
330
+ test('mountFor duplicate key warning on update (line 385)', () => {
331
331
  const el = container()
332
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
332
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
333
333
 
334
334
  const items = signal([{ id: 1 }, { id: 2 }])
335
335
 
336
336
  mount(
337
337
  h(
338
- "div",
338
+ 'div',
339
339
  null,
340
340
  For({
341
341
  each: items,
342
342
  by: (r: { id: number }) => r.id,
343
- children: (r: { id: number }) => h("span", null, String(r.id)),
343
+ children: (r: { id: number }) => h('span', null, String(r.id)),
344
344
  }),
345
345
  ),
346
346
  el,
@@ -349,37 +349,37 @@ describe("nodes.ts — LIS array growth and dev warnings", () => {
349
349
  // Update with duplicate keys
350
350
  items.set([{ id: 3 }, { id: 3 }])
351
351
 
352
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("Duplicate key"))
352
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Duplicate key'))
353
353
  warnSpy.mockRestore()
354
354
  el.remove()
355
355
  })
356
356
 
357
- test("mountFor clear path — items reduced to 0", () => {
357
+ test('mountFor clear path — items reduced to 0', () => {
358
358
  const el = container()
359
359
  const items = signal([{ id: 1 }, { id: 2 }, { id: 3 }])
360
360
 
361
361
  mount(
362
362
  h(
363
- "div",
363
+ 'div',
364
364
  null,
365
365
  For({
366
366
  each: items,
367
367
  by: (r: { id: number }) => r.id,
368
- children: (r: { id: number }) => h("span", null, String(r.id)),
368
+ children: (r: { id: number }) => h('span', null, String(r.id)),
369
369
  }),
370
370
  ),
371
371
  el,
372
372
  )
373
- expect(el.querySelectorAll("span").length).toBe(3)
373
+ expect(el.querySelectorAll('span').length).toBe(3)
374
374
 
375
375
  // Clear — fast clear path
376
376
  items.set([])
377
- expect(el.querySelectorAll("span").length).toBe(0)
377
+ expect(el.querySelectorAll('span').length).toBe(0)
378
378
 
379
379
  el.remove()
380
380
  })
381
381
 
382
- test("mountFor cleanup return — cleanupCount > 0 path in final cleanup", () => {
382
+ test('mountFor cleanup return — cleanupCount > 0 path in final cleanup', () => {
383
383
  const el = container()
384
384
  let cleanupCalled = 0
385
385
  const items = signal([{ id: 1 }])
@@ -388,12 +388,12 @@ describe("nodes.ts — LIS array growth and dev warnings", () => {
388
388
  onUnmount(() => {
389
389
  cleanupCalled++
390
390
  })
391
- return h("span", null, "has-cleanup")
391
+ return h('span', null, 'has-cleanup')
392
392
  })
393
393
 
394
394
  const unmount = mount(
395
395
  h(
396
- "div",
396
+ 'div',
397
397
  null,
398
398
  For({
399
399
  each: items,
@@ -410,22 +410,22 @@ describe("nodes.ts — LIS array growth and dev warnings", () => {
410
410
  el.remove()
411
411
  })
412
412
 
413
- test("mountFor with NativeItem entries", () => {
413
+ test('mountFor with NativeItem entries', () => {
414
414
  const el = container()
415
415
  const items = signal([
416
- { id: 1, label: "a" },
417
- { id: 2, label: "b" },
416
+ { id: 1, label: 'a' },
417
+ { id: 2, label: 'b' },
418
418
  ])
419
419
 
420
420
  mount(
421
421
  h(
422
- "div",
422
+ 'div',
423
423
  null,
424
424
  For({
425
425
  each: items,
426
426
  by: (r: { id: number }) => r.id,
427
427
  children: (r: { id: number; label: string }) => {
428
- const native = _tpl("<b></b>", (root) => {
428
+ const native = _tpl('<b></b>', (root) => {
429
429
  root.textContent = r.label
430
430
  return null
431
431
  })
@@ -435,33 +435,33 @@ describe("nodes.ts — LIS array growth and dev warnings", () => {
435
435
  ),
436
436
  el,
437
437
  )
438
- expect(el.querySelectorAll("b").length).toBe(2)
438
+ expect(el.querySelectorAll('b').length).toBe(2)
439
439
 
440
440
  // Add a new NativeItem entry — step 3 mount new entries with NativeItem
441
441
  items.set([
442
- { id: 1, label: "a" },
443
- { id: 2, label: "b" },
444
- { id: 3, label: "c" },
442
+ { id: 1, label: 'a' },
443
+ { id: 2, label: 'b' },
444
+ { id: 3, label: 'c' },
445
445
  ])
446
- expect(el.querySelectorAll("b").length).toBe(3)
446
+ expect(el.querySelectorAll('b').length).toBe(3)
447
447
 
448
448
  el.remove()
449
449
  })
450
450
 
451
- test("mountFor step 3 NativeItem with cleanup", () => {
451
+ test('mountFor step 3 NativeItem with cleanup', () => {
452
452
  const el = container()
453
453
  let _cleanupCount = 0
454
- const items = signal([{ id: 1, label: "a" }])
454
+ const items = signal([{ id: 1, label: 'a' }])
455
455
 
456
456
  mount(
457
457
  h(
458
- "div",
458
+ 'div',
459
459
  null,
460
460
  For({
461
461
  each: items,
462
462
  by: (r: { id: number }) => r.id,
463
463
  children: (r: { id: number; label: string }) => {
464
- const native = _tpl("<b></b>", (root) => {
464
+ const native = _tpl('<b></b>', (root) => {
465
465
  root.textContent = r.label
466
466
  return () => {
467
467
  _cleanupCount++
@@ -476,24 +476,24 @@ describe("nodes.ts — LIS array growth and dev warnings", () => {
476
476
 
477
477
  // Add new NativeItem with cleanup — exercises step 3 NativeItem cleanup path
478
478
  items.set([
479
- { id: 1, label: "a" },
480
- { id: 2, label: "new" },
479
+ { id: 1, label: 'a' },
480
+ { id: 2, label: 'new' },
481
481
  ])
482
- expect(el.querySelectorAll("b").length).toBe(2)
482
+ expect(el.querySelectorAll('b').length).toBe(2)
483
483
 
484
484
  el.remove()
485
485
  })
486
486
 
487
- test("mountReactive — __DEV__ warning when accessor returns function", () => {
487
+ test('mountReactive — __DEV__ warning when accessor returns function', () => {
488
488
  const el = container()
489
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
489
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
490
490
 
491
491
  // Reactive accessor that returns a function (not a value) — dev warning
492
- const badAccessor = () => (() => "oops") as unknown as VNodeChild
493
- mount(h("div", null, badAccessor as VNodeChild), el)
492
+ const badAccessor = () => (() => 'oops') as unknown as VNodeChild
493
+ mount(h('div', null, badAccessor as VNodeChild), el)
494
494
 
495
495
  expect(warnSpy).toHaveBeenCalledWith(
496
- expect.stringContaining("returned a function instead of a value"),
496
+ expect.stringContaining('returned a function instead of a value'),
497
497
  )
498
498
  warnSpy.mockRestore()
499
499
  el.remove()
@@ -502,214 +502,214 @@ describe("nodes.ts — LIS array growth and dev warnings", () => {
502
502
 
503
503
  // ─── props.ts — lines 182 (Sanitizer API), 190 (no DOMParser) ─────
504
504
 
505
- describe("props.ts — Sanitizer API branch", () => {
506
- test("sanitizeHtml fallback — strips unsafe tags via DOMParser", () => {
505
+ describe('props.ts — Sanitizer API branch', () => {
506
+ test('sanitizeHtml fallback — strips unsafe tags via DOMParser', () => {
507
507
  setSanitizer(null)
508
508
  // _nativeSanitizer is undefined (happy-dom has no Sanitizer API)
509
509
  // Falls through to DOMParser-based fallback sanitizer
510
- const result = sanitizeHtml("<b>bold</b><script>bad</script>")
510
+ const result = sanitizeHtml('<b>bold</b><script>bad</script>')
511
511
  // <script> should be stripped, <b> should remain
512
- expect(result).toContain("<b>")
513
- expect(result).not.toContain("<script>")
512
+ expect(result).toContain('<b>')
513
+ expect(result).not.toContain('<script>')
514
514
  })
515
515
 
516
- test("sanitizeHtml fallback strips event handler attributes", () => {
516
+ test('sanitizeHtml fallback strips event handler attributes', () => {
517
517
  setSanitizer(null)
518
518
  const result = sanitizeHtml('<div onclick="alert(1)">test</div>')
519
- expect(result).not.toContain("onclick")
519
+ expect(result).not.toContain('onclick')
520
520
  })
521
521
 
522
- test("sanitizeHtml fallback blocks javascript: URLs", () => {
522
+ test('sanitizeHtml fallback blocks javascript: URLs', () => {
523
523
  setSanitizer(null)
524
524
  const result = sanitizeHtml('<a href="javascript:alert(1)">click</a>')
525
- expect(result).not.toContain("javascript:")
525
+ expect(result).not.toContain('javascript:')
526
526
  })
527
527
 
528
- test("blocked unsafe URL in href attribute", () => {
529
- const el = document.createElement("a")
530
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
528
+ test('blocked unsafe URL in href attribute', () => {
529
+ const el = document.createElement('a')
530
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
531
531
 
532
- applyProp(el, "href", "javascript:alert(1)")
533
- expect(el.getAttribute("href")).toBeNull()
534
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("Blocked unsafe"))
532
+ applyProp(el, 'href', 'javascript:alert(1)')
533
+ expect(el.getAttribute('href')).toBeNull()
534
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Blocked unsafe'))
535
535
 
536
536
  warnSpy.mockRestore()
537
537
  })
538
538
 
539
- test("blocked unsafe data: URL in src attribute", () => {
540
- const el = document.createElement("img")
541
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
539
+ test('blocked unsafe data: URL in src attribute', () => {
540
+ const el = document.createElement('img')
541
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
542
542
 
543
- applyProp(el, "src", "data:text/html,<script>bad</script>")
544
- expect(el.getAttribute("src")).toBeNull()
543
+ applyProp(el, 'src', 'data:text/html,<script>bad</script>')
544
+ expect(el.getAttribute('src')).toBeNull()
545
545
 
546
546
  warnSpy.mockRestore()
547
547
  })
548
548
 
549
- test("style as object applies via Object.assign", () => {
549
+ test('style as object applies via Object.assign', () => {
550
550
  const el = container()
551
- mount(h("div", { style: { color: "red", fontSize: "14px" } }), el)
552
- const div = el.querySelector("div") as HTMLElement
553
- expect(div.style.color).toBe("red")
554
- expect(div.style.fontSize).toBe("14px")
551
+ mount(h('div', { style: { color: 'red', fontSize: '14px' } }), el)
552
+ const div = el.querySelector('div') as HTMLElement
553
+ expect(div.style.color).toBe('red')
554
+ expect(div.style.fontSize).toBe('14px')
555
555
  el.remove()
556
556
  })
557
557
 
558
- test("boolean attribute false removes attribute", () => {
559
- const el = document.createElement("input")
560
- applyProp(el, "disabled", true)
561
- expect(el.hasAttribute("disabled")).toBe(true)
562
- applyProp(el, "disabled", false)
563
- expect(el.hasAttribute("disabled")).toBe(false)
558
+ test('boolean attribute false removes attribute', () => {
559
+ const el = document.createElement('input')
560
+ applyProp(el, 'disabled', true)
561
+ expect(el.hasAttribute('disabled')).toBe(true)
562
+ applyProp(el, 'disabled', false)
563
+ expect(el.hasAttribute('disabled')).toBe(false)
564
564
  })
565
565
 
566
- test("null value removes attribute", () => {
567
- const el = document.createElement("div")
568
- el.setAttribute("data-x", "value")
569
- applyProp(el, "data-x", null)
570
- expect(el.hasAttribute("data-x")).toBe(false)
566
+ test('null value removes attribute', () => {
567
+ const el = document.createElement('div')
568
+ el.setAttribute('data-x', 'value')
569
+ applyProp(el, 'data-x', null)
570
+ expect(el.hasAttribute('data-x')).toBe(false)
571
571
  })
572
572
 
573
- test("DOM property is set directly when key exists on element", () => {
574
- const el = document.createElement("input")
575
- applyProp(el, "value", "hello")
576
- expect(el.value).toBe("hello")
573
+ test('DOM property is set directly when key exists on element', () => {
574
+ const el = document.createElement('input')
575
+ applyProp(el, 'value', 'hello')
576
+ expect(el.value).toBe('hello')
577
577
  })
578
578
 
579
- test("className alias sets class attribute", () => {
580
- const el = document.createElement("div")
581
- applyProp(el, "className", "my-class")
582
- expect(el.getAttribute("class")).toBe("my-class")
579
+ test('className alias sets class attribute', () => {
580
+ const el = document.createElement('div')
581
+ applyProp(el, 'className', 'my-class')
582
+ expect(el.getAttribute('class')).toBe('my-class')
583
583
  })
584
584
 
585
- test("class with null value sets empty string", () => {
586
- const el = document.createElement("div")
587
- applyProp(el, "class", null)
588
- expect(el.getAttribute("class")).toBe("")
585
+ test('class with null value sets empty string', () => {
586
+ const el = document.createElement('div')
587
+ applyProp(el, 'class', null)
588
+ expect(el.getAttribute('class')).toBe('')
589
589
  })
590
590
  })
591
591
 
592
592
  // ─── hydrate.ts — lines 162-183 (For with/without markers, afterEnd paths) ──
593
593
 
594
- describe("hydrate.ts — For and reactive accessor branches", () => {
595
- test("For hydration without markers and no domNode (null)", () => {
594
+ describe('hydrate.ts — For and reactive accessor branches', () => {
595
+ test('For hydration without markers and no domNode (null)', () => {
596
596
  const el = container()
597
597
  // Empty container — domNode will be null
598
- el.innerHTML = ""
599
- const items = signal([{ id: 1, label: "a" }])
598
+ el.innerHTML = ''
599
+ const items = signal([{ id: 1, label: 'a' }])
600
600
  const cleanup = hydrateRoot(
601
601
  el,
602
602
  For({
603
603
  each: items,
604
604
  by: (r: { id: number }) => r.id,
605
- children: (r: { id: number; label: string }) => h("li", null, r.label),
605
+ children: (r: { id: number; label: string }) => h('li', null, r.label),
606
606
  }),
607
607
  )
608
608
  cleanup()
609
609
  })
610
610
 
611
- test("hydrate reactive accessor returning null/false inserts marker before domNode", () => {
611
+ test('hydrate reactive accessor returning null/false inserts marker before domNode', () => {
612
612
  const el = container()
613
- el.innerHTML = "<p>existing</p>"
613
+ el.innerHTML = '<p>existing</p>'
614
614
  const content = signal<VNodeChild>(null)
615
615
  const cleanup = hydrateRoot(el, (() => content()) as unknown as VNodeChild)
616
616
  // Accessor returns null — comment marker inserted before existing <p>
617
617
  cleanup()
618
618
  })
619
619
 
620
- test("hydrate reactive accessor returning null with no domNode appends marker", () => {
620
+ test('hydrate reactive accessor returning null with no domNode appends marker', () => {
621
621
  const el = container()
622
- el.innerHTML = ""
622
+ el.innerHTML = ''
623
623
  const content = signal<VNodeChild>(null)
624
624
  const cleanup = hydrateRoot(el, (() => content()) as unknown as VNodeChild)
625
625
  cleanup()
626
626
  })
627
627
 
628
- test("hydrate reactive text with DOM mismatch (not a text node)", () => {
628
+ test('hydrate reactive text with DOM mismatch (not a text node)', () => {
629
629
  const el = container()
630
- el.innerHTML = "<div>not text</div>" // Element instead of text node
631
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
632
- const text = signal("hello")
630
+ el.innerHTML = '<div>not text</div>' // Element instead of text node
631
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
632
+ const text = signal('hello')
633
633
  const cleanup = hydrateRoot(el, (() => text()) as unknown as VNodeChild)
634
634
  // Should hit the mismatch path (line 119)
635
635
  cleanup()
636
636
  warnSpy.mockRestore()
637
637
  })
638
638
 
639
- test("hydrate reactive VNode with no domNode appends marker", () => {
639
+ test('hydrate reactive VNode with no domNode appends marker', () => {
640
640
  const el = container()
641
- el.innerHTML = ""
642
- const content = signal<VNodeChild>(h("span", null, "dynamic"))
641
+ el.innerHTML = ''
642
+ const content = signal<VNodeChild>(h('span', null, 'dynamic'))
643
643
  const cleanup = hydrateRoot(el, (() => content()) as unknown as VNodeChild)
644
644
  cleanup()
645
645
  })
646
646
 
647
- test("hydrate static text with non-text domNode (mismatch)", () => {
647
+ test('hydrate static text with non-text domNode (mismatch)', () => {
648
648
  const el = container()
649
- el.innerHTML = "<div>wrong</div>" // Element where text expected
650
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
651
- const cleanup = hydrateRoot(el, "plain text")
649
+ el.innerHTML = '<div>wrong</div>' // Element where text expected
650
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
651
+ const cleanup = hydrateRoot(el, 'plain text')
652
652
  cleanup()
653
653
  warnSpy.mockRestore()
654
654
  })
655
655
 
656
- test("hydrate element with ref sets ref.current", () => {
656
+ test('hydrate element with ref sets ref.current', () => {
657
657
  const el = container()
658
- el.innerHTML = "<div>with ref</div>"
658
+ el.innerHTML = '<div>with ref</div>'
659
659
  const ref = createRef<Element>()
660
- const cleanup = hydrateRoot(el, h("div", { ref }, "with ref"))
660
+ const cleanup = hydrateRoot(el, h('div', { ref }, 'with ref'))
661
661
  expect(ref.current).not.toBeNull()
662
- expect(ref.current?.textContent).toBe("with ref")
662
+ expect(ref.current?.textContent).toBe('with ref')
663
663
  cleanup()
664
664
  expect(ref.current).toBeNull()
665
665
  })
666
666
 
667
- test("hydrate component that throws during setup", () => {
667
+ test('hydrate component that throws during setup', () => {
668
668
  const el = container()
669
- el.innerHTML = "<span>error</span>"
670
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
669
+ el.innerHTML = '<span>error</span>'
670
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
671
671
  const BadComp = defineComponent(() => {
672
- throw new Error("hydrate setup error")
672
+ throw new Error('hydrate setup error')
673
673
  })
674
674
  const cleanup = hydrateRoot(el, h(BadComp, null))
675
675
  expect(errorSpy).toHaveBeenCalledWith(
676
- expect.stringContaining("Error hydrating component"),
676
+ expect.stringContaining('Error hydrating component'),
677
677
  expect.any(Error),
678
678
  )
679
679
  cleanup()
680
680
  errorSpy.mockRestore()
681
681
  })
682
682
 
683
- test("hydrate array of children", () => {
683
+ test('hydrate array of children', () => {
684
684
  const el = container()
685
- el.innerHTML = "<span>a</span><span>b</span>"
686
- const cleanup = hydrateRoot(el, h(Fragment, null, h("span", null, "a"), h("span", null, "b")))
685
+ el.innerHTML = '<span>a</span><span>b</span>'
686
+ const cleanup = hydrateRoot(el, h(Fragment, null, h('span', null, 'a'), h('span', null, 'b')))
687
687
  cleanup()
688
688
  })
689
689
 
690
- test("hydrate skips comments and whitespace-only text nodes", () => {
690
+ test('hydrate skips comments and whitespace-only text nodes', () => {
691
691
  const el = container()
692
692
  // HTML with comments and whitespace between elements
693
- el.innerHTML = "<!-- comment --> <div>real</div>"
694
- const cleanup = hydrateRoot(el, h("div", null, "real"))
693
+ el.innerHTML = '<!-- comment --> <div>real</div>'
694
+ const cleanup = hydrateRoot(el, h('div', null, 'real'))
695
695
  cleanup()
696
696
  })
697
697
 
698
- test("hydrate children with reactive text (reuses text node)", () => {
698
+ test('hydrate children with reactive text (reuses text node)', () => {
699
699
  const el = container()
700
- el.innerHTML = "<div>hello</div>"
701
- const text = signal("hello")
702
- const cleanup = hydrateRoot(el, h("div", null, (() => text()) as unknown as VNodeChild))
700
+ el.innerHTML = '<div>hello</div>'
701
+ const text = signal('hello')
702
+ const cleanup = hydrateRoot(el, h('div', null, (() => text()) as unknown as VNodeChild))
703
703
  // Text should be reactive
704
- text.set("world")
704
+ text.set('world')
705
705
  cleanup()
706
706
  })
707
707
  })
708
708
 
709
709
  // ─── transition.ts — lines 111-113 (pendingLeaveCancel in applyLeave rAF) ──
710
710
 
711
- describe("Transition — leave cancel and re-enter", () => {
712
- test("re-enter during leave cancels pending leave animation (lines 110-113)", async () => {
711
+ describe('Transition — leave cancel and re-enter', () => {
712
+ test('re-enter during leave cancels pending leave animation (lines 110-113)', async () => {
713
713
  const el = container()
714
714
  const visible = signal(true)
715
715
  let beforeEnterCount = 0
@@ -718,14 +718,14 @@ describe("Transition — leave cancel and re-enter", () => {
718
718
  mount(
719
719
  h(Transition, {
720
720
  show: visible,
721
- name: "fade",
721
+ name: 'fade',
722
722
  onBeforeEnter: () => {
723
723
  beforeEnterCount++
724
724
  },
725
725
  onBeforeLeave: () => {
726
726
  beforeLeaveCount++
727
727
  },
728
- children: h("div", { id: "reenter-test" }, "content"),
728
+ children: h('div', { id: 'reenter-test' }, 'content'),
729
729
  }),
730
730
  el,
731
731
  )
@@ -747,19 +747,19 @@ describe("Transition — leave cancel and re-enter", () => {
747
747
  el.remove()
748
748
  })
749
749
 
750
- test("Transition appear prop triggers enter animation on initial mount", async () => {
750
+ test('Transition appear prop triggers enter animation on initial mount', async () => {
751
751
  const el = container()
752
752
  let afterEnterCalled = false
753
753
 
754
754
  mount(
755
755
  h(Transition, {
756
756
  show: () => true,
757
- name: "fade",
757
+ name: 'fade',
758
758
  appear: true,
759
759
  onAfterEnter: () => {
760
760
  afterEnterCalled = true
761
761
  },
762
- children: h("div", { id: "appear-test" }, "appear"),
762
+ children: h('div', { id: 'appear-test' }, 'appear'),
763
763
  }),
764
764
  el,
765
765
  )
@@ -767,36 +767,36 @@ describe("Transition — leave cancel and re-enter", () => {
767
767
  await new Promise<void>((r) => queueMicrotask(r))
768
768
  await new Promise<void>((r) => requestAnimationFrame(() => r()))
769
769
 
770
- const target = el.querySelector("#appear-test")
770
+ const target = el.querySelector('#appear-test')
771
771
  if (target) {
772
- target.dispatchEvent(new Event("transitionend"))
772
+ target.dispatchEvent(new Event('transitionend'))
773
773
  }
774
774
  expect(afterEnterCalled).toBe(true)
775
775
 
776
776
  el.remove()
777
777
  })
778
778
 
779
- test("Transition show starts false then becomes true", async () => {
779
+ test('Transition show starts false then becomes true', async () => {
780
780
  const el = container()
781
781
  const visible = signal(false)
782
782
 
783
783
  mount(
784
784
  h(Transition, {
785
785
  show: visible,
786
- name: "fade",
787
- children: h("div", { id: "false-start" }, "content"),
786
+ name: 'fade',
787
+ children: h('div', { id: 'false-start' }, 'content'),
788
788
  }),
789
789
  el,
790
790
  )
791
791
 
792
792
  // Initially hidden
793
- expect(el.querySelector("#false-start")).toBeNull()
793
+ expect(el.querySelector('#false-start')).toBeNull()
794
794
 
795
795
  // Show
796
796
  visible.set(true)
797
797
  await new Promise<void>((r) => queueMicrotask(r))
798
798
 
799
- expect(el.querySelector("#false-start")).not.toBeNull()
799
+ expect(el.querySelector('#false-start')).not.toBeNull()
800
800
 
801
801
  el.remove()
802
802
  })
@@ -804,21 +804,21 @@ describe("Transition — leave cancel and re-enter", () => {
804
804
 
805
805
  // ─── transition-group.ts — lines 209-218 (FLIP inner rAF) ───────────────────
806
806
 
807
- describe("TransitionGroup — leave and enter edge cases", () => {
808
- test("TransitionGroup with appear triggers enter animation", async () => {
807
+ describe('TransitionGroup — leave and enter edge cases', () => {
808
+ test('TransitionGroup with appear triggers enter animation', async () => {
809
809
  const el = container()
810
810
  let enterCount = 0
811
- const items = signal([{ id: 1, label: "a" }])
811
+ const items = signal([{ id: 1, label: 'a' }])
812
812
 
813
813
  mount(
814
814
  h(TransitionGroup, {
815
- tag: "div",
816
- name: "list",
815
+ tag: 'div',
816
+ name: 'list',
817
817
  appear: true,
818
818
  items: () => items(),
819
819
  keyFn: (item: { id: number }) => item.id,
820
820
  render: (item: { id: number; label: string }) =>
821
- h("span", { class: "tg-item" }, item.label),
821
+ h('span', { class: 'tg-item' }, item.label),
822
822
  onBeforeEnter: () => {
823
823
  enterCount++
824
824
  },
@@ -832,20 +832,20 @@ describe("TransitionGroup — leave and enter edge cases", () => {
832
832
  el.remove()
833
833
  })
834
834
 
835
- test("TransitionGroup item removal with no ref.current cleans up without leave animation", async () => {
835
+ test('TransitionGroup item removal with no ref.current cleans up without leave animation', async () => {
836
836
  const el = container()
837
837
  const items = signal([
838
- { id: 1, label: "a" },
839
- { id: 2, label: "b" },
838
+ { id: 1, label: 'a' },
839
+ { id: 2, label: 'b' },
840
840
  ])
841
841
 
842
842
  // Use component type child so ref won't be injected (line 171)
843
- const ItemComp = defineComponent((props: { label: string }) => h("span", null, props.label))
843
+ const ItemComp = defineComponent((props: { label: string }) => h('span', null, props.label))
844
844
 
845
845
  mount(
846
846
  h(TransitionGroup, {
847
- tag: "div",
848
- name: "list",
847
+ tag: 'div',
848
+ name: 'list',
849
849
  items: () => items(),
850
850
  keyFn: (item: { id: number }) => item.id,
851
851
  render: (item: { id: number; label: string }) =>
@@ -856,29 +856,29 @@ describe("TransitionGroup — leave and enter edge cases", () => {
856
856
  await new Promise<void>((r) => queueMicrotask(r))
857
857
 
858
858
  // Remove an item — ref.current will be null for component children
859
- items.set([{ id: 1, label: "a" }])
859
+ items.set([{ id: 1, label: 'a' }])
860
860
  await new Promise<void>((r) => queueMicrotask(r))
861
861
 
862
862
  el.remove()
863
863
  })
864
864
 
865
- test("TransitionGroup leave animation with onBeforeLeave and onAfterLeave", async () => {
865
+ test('TransitionGroup leave animation with onBeforeLeave and onAfterLeave', async () => {
866
866
  const el = container()
867
867
  let beforeLeaveCalled = false
868
868
  let afterLeaveCalled = false
869
869
  const items = signal([
870
- { id: 1, label: "a" },
871
- { id: 2, label: "b" },
870
+ { id: 1, label: 'a' },
871
+ { id: 2, label: 'b' },
872
872
  ])
873
873
 
874
874
  mount(
875
875
  h(TransitionGroup, {
876
- tag: "div",
877
- name: "list",
876
+ tag: 'div',
877
+ name: 'list',
878
878
  items: () => items(),
879
879
  keyFn: (item: { id: number }) => item.id,
880
880
  render: (item: { id: number; label: string }) =>
881
- h("span", { class: "leave-item" }, item.label),
881
+ h('span', { class: 'leave-item' }, item.label),
882
882
  onBeforeLeave: () => {
883
883
  beforeLeaveCalled = true
884
884
  },
@@ -891,7 +891,7 @@ describe("TransitionGroup — leave and enter edge cases", () => {
891
891
  await new Promise<void>((r) => queueMicrotask(r))
892
892
 
893
893
  // Remove an item
894
- items.set([{ id: 1, label: "a" }])
894
+ items.set([{ id: 1, label: 'a' }])
895
895
  await new Promise<void>((r) => queueMicrotask(r))
896
896
 
897
897
  expect(beforeLeaveCalled).toBe(true)
@@ -900,9 +900,9 @@ describe("TransitionGroup — leave and enter edge cases", () => {
900
900
  await new Promise<void>((r) => requestAnimationFrame(() => r()))
901
901
 
902
902
  // Fire transitionend on the leaving element
903
- const spans = el.querySelectorAll("span.leave-item")
903
+ const spans = el.querySelectorAll('span.leave-item')
904
904
  for (const span of spans) {
905
- span.dispatchEvent(new Event("transitionend"))
905
+ span.dispatchEvent(new Event('transitionend'))
906
906
  }
907
907
 
908
908
  // afterLeaveCalled should be true after transitionend
@@ -914,88 +914,88 @@ describe("TransitionGroup — leave and enter edge cases", () => {
914
914
 
915
915
  // ─── mount.ts — lines 204-206 (mountElement nested with ref, no propCleanup) ─
916
916
 
917
- describe("mount.ts — nested element branches", () => {
918
- test("nested element with ref but no propCleanup (line 202-207)", () => {
917
+ describe('mount.ts — nested element branches', () => {
918
+ test('nested element with ref but no propCleanup (line 202-207)', () => {
919
919
  const el = container()
920
920
  const ref = createRef<HTMLElement>()
921
921
 
922
922
  // Inner element has ref but no reactive props
923
- const unmount = mount(h("div", null, h("span", { ref }, "with-ref-only")), el)
923
+ const unmount = mount(h('div', null, h('span', { ref }, 'with-ref-only')), el)
924
924
 
925
925
  expect(ref.current).not.toBeNull()
926
- expect(ref.current?.textContent).toBe("with-ref-only")
926
+ expect(ref.current?.textContent).toBe('with-ref-only')
927
927
 
928
928
  unmount()
929
929
  // Ref should be cleaned up
930
930
  expect(ref.current).toBeNull()
931
931
  })
932
932
 
933
- test("nested element with childCleanup only (line 196)", () => {
933
+ test('nested element with childCleanup only (line 196)', () => {
934
934
  const el = container()
935
- const text = signal("dynamic")
935
+ const text = signal('dynamic')
936
936
 
937
937
  // Inner element with reactive children but no ref, no propCleanup
938
938
  const unmount = mount(
939
939
  h(
940
- "div",
940
+ 'div',
941
941
  null,
942
- h("span", null, () => text()),
942
+ h('span', null, () => text()),
943
943
  ),
944
944
  el,
945
945
  )
946
946
 
947
- expect(el.querySelector("span")?.textContent).toBe("dynamic")
948
- text.set("updated")
949
- expect(el.querySelector("span")?.textContent).toBe("updated")
947
+ expect(el.querySelector('span')?.textContent).toBe('dynamic')
948
+ text.set('updated')
949
+ expect(el.querySelector('span')?.textContent).toBe('updated')
950
950
 
951
951
  unmount()
952
952
  })
953
953
 
954
- test("mountChild with boolean sample (true) uses text fast path", () => {
954
+ test('mountChild with boolean sample (true) uses text fast path', () => {
955
955
  const el = container()
956
956
  const flag = signal(true)
957
957
  mount(
958
- h("div", null, () => flag()),
958
+ h('div', null, () => flag()),
959
959
  el,
960
960
  )
961
- expect(el.querySelector("div")?.textContent).toBe("true")
961
+ expect(el.querySelector('div')?.textContent).toBe('true')
962
962
  flag.set(false)
963
- expect(el.querySelector("div")?.textContent).toBe("")
963
+ expect(el.querySelector('div')?.textContent).toBe('')
964
964
  })
965
965
 
966
- test("mountElement at depth 0 with ref + propCleanup + childCleanup", () => {
966
+ test('mountElement at depth 0 with ref + propCleanup + childCleanup', () => {
967
967
  const el = container()
968
968
  const ref = createRef<HTMLElement>()
969
- const cls = signal("cls")
970
- const text = signal("txt")
969
+ const cls = signal('cls')
970
+ const text = signal('txt')
971
971
 
972
972
  const unmount = mount(
973
- h("span", { ref, class: () => cls() }, () => text()),
973
+ h('span', { ref, class: () => cls() }, () => text()),
974
974
  el,
975
975
  )
976
976
 
977
977
  expect(ref.current).not.toBeNull()
978
- expect(ref.current?.className).toBe("cls")
979
- expect(ref.current?.textContent).toBe("txt")
978
+ expect(ref.current?.className).toBe('cls')
979
+ expect(ref.current?.textContent).toBe('txt')
980
980
 
981
981
  unmount()
982
982
  expect(ref.current).toBeNull()
983
983
  })
984
984
 
985
- test("mountChildren with undefined children", () => {
985
+ test('mountChildren with undefined children', () => {
986
986
  const el = container()
987
987
  // Single child that is undefined — should be handled
988
- mount(h("div", null, undefined), el)
989
- expect(el.querySelector("div")?.textContent).toBe("")
988
+ mount(h('div', null, undefined), el)
989
+ expect(el.querySelector('div')?.textContent).toBe('')
990
990
  })
991
991
 
992
- test("mountChildren 2-child path with undefined child in slot 0", () => {
992
+ test('mountChildren 2-child path with undefined child in slot 0', () => {
993
993
  const el = container()
994
994
  // 2 children where first is undefined — falls through to map path
995
- mount(h("div", null, undefined, "text"), el)
995
+ mount(h('div', null, undefined, 'text'), el)
996
996
  })
997
997
 
998
- test("component with onMount returning cleanup", () => {
998
+ test('component with onMount returning cleanup', () => {
999
999
  const el = container()
1000
1000
  let mountCleanupCalled = false
1001
1001
 
@@ -1005,7 +1005,7 @@ describe("mount.ts — nested element branches", () => {
1005
1005
  mountCleanupCalled = true
1006
1006
  }
1007
1007
  })
1008
- return h("span", null, "with-mount-cleanup")
1008
+ return h('span', null, 'with-mount-cleanup')
1009
1009
  })
1010
1010
 
1011
1011
  const unmount = mount(h(Comp, null), el)
@@ -1013,126 +1013,126 @@ describe("mount.ts — nested element branches", () => {
1013
1013
  expect(mountCleanupCalled).toBe(true)
1014
1014
  })
1015
1015
 
1016
- test("component onMount hook that throws", () => {
1016
+ test('component onMount hook that throws', () => {
1017
1017
  const el = container()
1018
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
1018
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
1019
1019
 
1020
1020
  const Comp = defineComponent(() => {
1021
1021
  onMount(() => {
1022
- throw new Error("mount hook error")
1022
+ throw new Error('mount hook error')
1023
1023
  })
1024
- return h("span", null, "error-mount")
1024
+ return h('span', null, 'error-mount')
1025
1025
  })
1026
1026
 
1027
1027
  mount(h(Comp, null), el)
1028
1028
  expect(errorSpy).toHaveBeenCalledWith(
1029
- expect.stringContaining("Error in onMount hook"),
1029
+ expect.stringContaining('Error in onMount hook'),
1030
1030
  expect.any(Error),
1031
1031
  )
1032
1032
  errorSpy.mockRestore()
1033
1033
  })
1034
1034
 
1035
- test("component onUnmount hook that throws", () => {
1035
+ test('component onUnmount hook that throws', () => {
1036
1036
  const el = container()
1037
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
1037
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
1038
1038
 
1039
1039
  const Comp = defineComponent(() => {
1040
1040
  onUnmount(() => {
1041
- throw new Error("unmount hook error")
1041
+ throw new Error('unmount hook error')
1042
1042
  })
1043
- return h("span", null, "error-unmount")
1043
+ return h('span', null, 'error-unmount')
1044
1044
  })
1045
1045
 
1046
1046
  const unmount = mount(h(Comp, null), el)
1047
1047
  unmount()
1048
1048
  expect(errorSpy).toHaveBeenCalledWith(
1049
- expect.stringContaining("Error in onUnmount hook"),
1049
+ expect.stringContaining('Error in onUnmount hook'),
1050
1050
  expect.any(Error),
1051
1051
  )
1052
1052
  errorSpy.mockRestore()
1053
1053
  })
1054
1054
 
1055
- test("mount with null container in dev mode throws", () => {
1055
+ test('mount with null container in dev mode throws', () => {
1056
1056
  expect(() => {
1057
- mount(h("div", null), null as unknown as Element)
1058
- }).toThrow("mount() called with a null/undefined container")
1057
+ mount(h('div', null), null as unknown as Element)
1058
+ }).toThrow('mount() called with a null/undefined container')
1059
1059
  })
1060
1060
  })
1061
1061
 
1062
1062
  // ─── nodes.ts — mountKeyedList branches ─────────────────────────────────────
1063
1063
 
1064
- describe("nodes.ts — mountKeyedList branches", () => {
1065
- test("mountKeyedList fast clear path", () => {
1064
+ describe('nodes.ts — mountKeyedList branches', () => {
1065
+ test('mountKeyedList fast clear path', () => {
1066
1066
  const el = container()
1067
- const items = signal([h("span", { key: 1 }, "a"), h("span", { key: 2 }, "b")] as VNodeChild)
1067
+ const items = signal([h('span', { key: 1 }, 'a'), h('span', { key: 2 }, 'b')] as VNodeChild)
1068
1068
 
1069
- mount(h("div", null, (() => items()) as VNodeChild), el)
1070
- expect(el.querySelectorAll("span").length).toBe(2)
1069
+ mount(h('div', null, (() => items()) as VNodeChild), el)
1070
+ expect(el.querySelectorAll('span').length).toBe(2)
1071
1071
 
1072
1072
  // Clear all
1073
1073
  items.set([] as unknown as VNodeChild)
1074
- expect(el.querySelectorAll("span").length).toBe(0)
1074
+ expect(el.querySelectorAll('span').length).toBe(0)
1075
1075
  })
1076
1076
 
1077
- test("mountKeyedList stale entry removal", () => {
1077
+ test('mountKeyedList stale entry removal', () => {
1078
1078
  const el = container()
1079
1079
  const items = signal([
1080
- h("span", { key: 1 }, "a"),
1081
- h("span", { key: 2 }, "b"),
1082
- h("span", { key: 3 }, "c"),
1080
+ h('span', { key: 1 }, 'a'),
1081
+ h('span', { key: 2 }, 'b'),
1082
+ h('span', { key: 3 }, 'c'),
1083
1083
  ] as VNodeChild)
1084
1084
 
1085
- mount(h("div", null, (() => items()) as VNodeChild), el)
1086
- expect(el.querySelectorAll("span").length).toBe(3)
1085
+ mount(h('div', null, (() => items()) as VNodeChild), el)
1086
+ expect(el.querySelectorAll('span').length).toBe(3)
1087
1087
 
1088
1088
  // Remove middle item
1089
- items.set([h("span", { key: 1 }, "a"), h("span", { key: 3 }, "c")] as unknown as VNodeChild)
1090
- expect(el.querySelectorAll("span").length).toBe(2)
1089
+ items.set([h('span', { key: 1 }, 'a'), h('span', { key: 3 }, 'c')] as unknown as VNodeChild)
1090
+ expect(el.querySelectorAll('span').length).toBe(2)
1091
1091
  })
1092
1092
 
1093
- test("mountKeyedList reorder with LIS", () => {
1093
+ test('mountKeyedList reorder with LIS', () => {
1094
1094
  const el = container()
1095
1095
  const items = signal([
1096
- h("span", { key: 1 }, "a"),
1097
- h("span", { key: 2 }, "b"),
1098
- h("span", { key: 3 }, "c"),
1096
+ h('span', { key: 1 }, 'a'),
1097
+ h('span', { key: 2 }, 'b'),
1098
+ h('span', { key: 3 }, 'c'),
1099
1099
  ] as VNodeChild)
1100
1100
 
1101
- mount(h("div", null, (() => items()) as VNodeChild), el)
1101
+ mount(h('div', null, (() => items()) as VNodeChild), el)
1102
1102
 
1103
1103
  // Reverse order — triggers LIS reorder
1104
1104
  items.set([
1105
- h("span", { key: 3 }, "c"),
1106
- h("span", { key: 2 }, "b"),
1107
- h("span", { key: 1 }, "a"),
1105
+ h('span', { key: 3 }, 'c'),
1106
+ h('span', { key: 2 }, 'b'),
1107
+ h('span', { key: 1 }, 'a'),
1108
1108
  ] as unknown as VNodeChild)
1109
1109
 
1110
- const spans = el.querySelectorAll("span")
1111
- expect(spans[0]?.textContent).toBe("c")
1112
- expect(spans[1]?.textContent).toBe("b")
1113
- expect(spans[2]?.textContent).toBe("a")
1110
+ const spans = el.querySelectorAll('span')
1111
+ expect(spans[0]?.textContent).toBe('c')
1112
+ expect(spans[1]?.textContent).toBe('b')
1113
+ expect(spans[2]?.textContent).toBe('a')
1114
1114
  })
1115
1115
  })
1116
1116
 
1117
1117
  // ─── nodes.ts — mountFor small-k reorder and replace-all paths ──────────────
1118
1118
 
1119
- describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1120
- test("mountFor small-k fast path — few items swapped", () => {
1119
+ describe('nodes.ts — mountFor small-k and clearBetween paths', () => {
1120
+ test('mountFor small-k fast path — few items swapped', () => {
1121
1121
  const el = container()
1122
1122
  const items = signal([
1123
- { id: 1, label: "a" },
1124
- { id: 2, label: "b" },
1125
- { id: 3, label: "c" },
1123
+ { id: 1, label: 'a' },
1124
+ { id: 2, label: 'b' },
1125
+ { id: 3, label: 'c' },
1126
1126
  ])
1127
1127
 
1128
1128
  mount(
1129
1129
  h(
1130
- "div",
1130
+ 'div',
1131
1131
  null,
1132
1132
  For({
1133
1133
  each: items,
1134
1134
  by: (r: { id: number }) => r.id,
1135
- children: (r: { id: number; label: string }) => h("span", null, r.label),
1135
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
1136
1136
  }),
1137
1137
  ),
1138
1138
  el,
@@ -1140,35 +1140,35 @@ describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1140
1140
 
1141
1141
  // Swap first two — small-k path (diffs.length <= SMALL_K)
1142
1142
  items.set([
1143
- { id: 2, label: "b" },
1144
- { id: 1, label: "a" },
1145
- { id: 3, label: "c" },
1143
+ { id: 2, label: 'b' },
1144
+ { id: 1, label: 'a' },
1145
+ { id: 3, label: 'c' },
1146
1146
  ])
1147
1147
 
1148
- const spans = el.querySelectorAll("span")
1149
- expect(spans[0]?.textContent).toBe("b")
1150
- expect(spans[1]?.textContent).toBe("a")
1151
- expect(spans[2]?.textContent).toBe("c")
1148
+ const spans = el.querySelectorAll('span')
1149
+ expect(spans[0]?.textContent).toBe('b')
1150
+ expect(spans[1]?.textContent).toBe('a')
1151
+ expect(spans[2]?.textContent).toBe('c')
1152
1152
 
1153
1153
  el.remove()
1154
1154
  })
1155
1155
 
1156
- test("mountFor remove stale entries (step 2)", () => {
1156
+ test('mountFor remove stale entries (step 2)', () => {
1157
1157
  const el = container()
1158
1158
  const items = signal([
1159
- { id: 1, label: "a" },
1160
- { id: 2, label: "b" },
1161
- { id: 3, label: "c" },
1159
+ { id: 1, label: 'a' },
1160
+ { id: 2, label: 'b' },
1161
+ { id: 3, label: 'c' },
1162
1162
  ])
1163
1163
 
1164
1164
  mount(
1165
1165
  h(
1166
- "div",
1166
+ 'div',
1167
1167
  null,
1168
1168
  For({
1169
1169
  each: items,
1170
1170
  by: (r: { id: number }) => r.id,
1171
- children: (r: { id: number; label: string }) => h("span", null, r.label),
1171
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
1172
1172
  }),
1173
1173
  ),
1174
1174
  el,
@@ -1176,45 +1176,45 @@ describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1176
1176
 
1177
1177
  // Remove item 2 and add item 4 — keeps some, removes some, adds some
1178
1178
  items.set([
1179
- { id: 1, label: "a" },
1180
- { id: 4, label: "d" },
1181
- { id: 3, label: "c" },
1179
+ { id: 1, label: 'a' },
1180
+ { id: 4, label: 'd' },
1181
+ { id: 3, label: 'c' },
1182
1182
  ])
1183
1183
 
1184
- expect(el.querySelectorAll("span").length).toBe(3)
1184
+ expect(el.querySelectorAll('span').length).toBe(3)
1185
1185
 
1186
1186
  el.remove()
1187
1187
  })
1188
1188
 
1189
- test("mountFor replace-all with clearBetween fallback (not sole children)", () => {
1189
+ test('mountFor replace-all with clearBetween fallback (not sole children)', () => {
1190
1190
  const el = container()
1191
1191
  // Add sibling content so markers are not the sole children
1192
- const wrapper = document.createElement("div")
1192
+ const wrapper = document.createElement('div')
1193
1193
  el.appendChild(wrapper)
1194
- const before = document.createElement("p")
1195
- before.textContent = "before"
1194
+ const before = document.createElement('p')
1195
+ before.textContent = 'before'
1196
1196
  wrapper.appendChild(before)
1197
1197
 
1198
- const items = signal([{ id: 1, label: "old" }])
1198
+ const items = signal([{ id: 1, label: 'old' }])
1199
1199
 
1200
1200
  mountChild(
1201
1201
  For({
1202
1202
  each: items,
1203
1203
  by: (r: { id: number }) => r.id,
1204
- children: (r: { id: number; label: string }) => h("span", null, r.label),
1204
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
1205
1205
  }),
1206
1206
  wrapper,
1207
1207
  null,
1208
1208
  )
1209
1209
 
1210
1210
  // Replace all — wrapper has <p> + markers, so canSwap is false → clearBetween
1211
- items.set([{ id: 10, label: "new" }])
1212
- expect(wrapper.querySelector("span")?.textContent).toBe("new")
1211
+ items.set([{ id: 10, label: 'new' }])
1212
+ expect(wrapper.querySelector('span')?.textContent).toBe('new')
1213
1213
 
1214
1214
  el.remove()
1215
1215
  })
1216
1216
 
1217
- test("mountFor LIS fallback for large reorder (> SMALL_K diffs)", () => {
1217
+ test('mountFor LIS fallback for large reorder (> SMALL_K diffs)', () => {
1218
1218
  const el = container()
1219
1219
  // Create items with enough to exceed SMALL_K (8) diffs
1220
1220
  const initial = Array.from({ length: 12 }, (_, i) => ({ id: i, label: `item-${i}` }))
@@ -1222,12 +1222,12 @@ describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1222
1222
 
1223
1223
  mount(
1224
1224
  h(
1225
- "div",
1225
+ 'div',
1226
1226
  null,
1227
1227
  For({
1228
1228
  each: items,
1229
1229
  by: (r: { id: number }) => r.id,
1230
- children: (r: { id: number; label: string }) => h("span", null, r.label),
1230
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
1231
1231
  }),
1232
1232
  ),
1233
1233
  el,
@@ -1236,29 +1236,29 @@ describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1236
1236
  // Reverse all — > SMALL_K diffs triggers LIS fallback
1237
1237
  items.set([...initial].reverse())
1238
1238
 
1239
- const spans = el.querySelectorAll("span")
1240
- expect(spans[0]?.textContent).toBe("item-11")
1241
- expect(spans[11]?.textContent).toBe("item-0")
1239
+ const spans = el.querySelectorAll('span')
1240
+ expect(spans[0]?.textContent).toBe('item-11')
1241
+ expect(spans[11]?.textContent).toBe('item-0')
1242
1242
 
1243
1243
  el.remove()
1244
1244
  })
1245
1245
 
1246
- test("mountFor clear with non-sole children uses clearBetween", () => {
1246
+ test('mountFor clear with non-sole children uses clearBetween', () => {
1247
1247
  const el = container()
1248
- const wrapper = document.createElement("div")
1248
+ const wrapper = document.createElement('div')
1249
1249
  el.appendChild(wrapper)
1250
1250
  // Add a sibling so markers are not sole children
1251
- const sibling = document.createElement("p")
1252
- sibling.textContent = "sibling"
1251
+ const sibling = document.createElement('p')
1252
+ sibling.textContent = 'sibling'
1253
1253
  wrapper.appendChild(sibling)
1254
1254
 
1255
- const items = signal([{ id: 1, label: "a" }])
1255
+ const items = signal([{ id: 1, label: 'a' }])
1256
1256
 
1257
1257
  mountChild(
1258
1258
  For({
1259
1259
  each: items,
1260
1260
  by: (r: { id: number }) => r.id,
1261
- children: (r: { id: number; label: string }) => h("span", null, r.label),
1261
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
1262
1262
  }),
1263
1263
  wrapper,
1264
1264
  null,
@@ -1266,26 +1266,26 @@ describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1266
1266
 
1267
1267
  // Clear — wrapper has <p> + markers so canSwap is false
1268
1268
  items.set([])
1269
- expect(wrapper.querySelectorAll("span").length).toBe(0)
1269
+ expect(wrapper.querySelectorAll('span').length).toBe(0)
1270
1270
 
1271
1271
  el.remove()
1272
1272
  })
1273
1273
 
1274
- test("mountFor size change triggers LIS not small-k", () => {
1274
+ test('mountFor size change triggers LIS not small-k', () => {
1275
1275
  const el = container()
1276
1276
  const items = signal([
1277
- { id: 1, label: "a" },
1278
- { id: 2, label: "b" },
1277
+ { id: 1, label: 'a' },
1278
+ { id: 2, label: 'b' },
1279
1279
  ])
1280
1280
 
1281
1281
  mount(
1282
1282
  h(
1283
- "div",
1283
+ 'div',
1284
1284
  null,
1285
1285
  For({
1286
1286
  each: items,
1287
1287
  by: (r: { id: number }) => r.id,
1288
- children: (r: { id: number; label: string }) => h("span", null, r.label),
1288
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
1289
1289
  }),
1290
1290
  ),
1291
1291
  el,
@@ -1293,12 +1293,12 @@ describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1293
1293
 
1294
1294
  // Different size — skips small-k, goes to LIS
1295
1295
  items.set([
1296
- { id: 2, label: "b" },
1297
- { id: 1, label: "a" },
1298
- { id: 3, label: "c" },
1296
+ { id: 2, label: 'b' },
1297
+ { id: 1, label: 'a' },
1298
+ { id: 3, label: 'c' },
1299
1299
  ])
1300
1300
 
1301
- expect(el.querySelectorAll("span").length).toBe(3)
1301
+ expect(el.querySelectorAll('span').length).toBe(3)
1302
1302
  el.remove()
1303
1303
  })
1304
1304
  })
@@ -1307,21 +1307,21 @@ describe("nodes.ts — mountFor small-k and clearBetween paths", () => {
1307
1307
  // happy-dom's elementFromPoint returns null, so we mock it to exercise the
1308
1308
  // overlay click and mousemove code paths that depend on finding a target element.
1309
1309
 
1310
- describe("devtools — overlay paths with mocked elementFromPoint", () => {
1310
+ describe('devtools — overlay paths with mocked elementFromPoint', () => {
1311
1311
  beforeAll(() => {
1312
1312
  installDevTools()
1313
1313
  })
1314
1314
 
1315
- test("overlay mousemove finds component and positions overlay + tooltip (line 120-141)", () => {
1315
+ test('overlay mousemove finds component and positions overlay + tooltip (line 120-141)', () => {
1316
1316
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1317
1317
  enableOverlay: () => void
1318
1318
  disableOverlay: () => void
1319
1319
  }
1320
1320
 
1321
- const target = document.createElement("div")
1322
- target.style.cssText = "width:100px;height:100px;position:fixed;top:50px;left:50px;"
1321
+ const target = document.createElement('div')
1322
+ target.style.cssText = 'width:100px;height:100px;position:fixed;top:50px;left:50px;'
1323
1323
  document.body.appendChild(target)
1324
- registerComponent("mm-comp", "MouseMoveComp", target, null)
1324
+ registerComponent('mm-comp', 'MouseMoveComp', target, null)
1325
1325
 
1326
1326
  // Mock elementFromPoint to return our target
1327
1327
  const origElementFromPoint = document.elementFromPoint
@@ -1330,33 +1330,33 @@ describe("devtools — overlay paths with mocked elementFromPoint", () => {
1330
1330
  devtools.enableOverlay()
1331
1331
 
1332
1332
  // First mousemove — sets _currentHighlight
1333
- const event1 = new MouseEvent("mousemove", { clientX: 75, clientY: 75, bubbles: true })
1333
+ const event1 = new MouseEvent('mousemove', { clientX: 75, clientY: 75, bubbles: true })
1334
1334
  document.dispatchEvent(event1)
1335
1335
 
1336
1336
  // Overlay should be visible
1337
- const overlayEl = document.getElementById("__pyreon-overlay")
1338
- expect(overlayEl?.style.display).toBe("block")
1337
+ const overlayEl = document.getElementById('__pyreon-overlay')
1338
+ expect(overlayEl?.style.display).toBe('block')
1339
1339
 
1340
1340
  // Second mousemove on same element — should early return (line 117: same _currentHighlight)
1341
- const event2 = new MouseEvent("mousemove", { clientX: 76, clientY: 76, bubbles: true })
1341
+ const event2 = new MouseEvent('mousemove', { clientX: 76, clientY: 76, bubbles: true })
1342
1342
  document.dispatchEvent(event2)
1343
1343
 
1344
1344
  devtools.disableOverlay()
1345
1345
  document.elementFromPoint = origElementFromPoint
1346
- unregisterComponent("mm-comp")
1346
+ unregisterComponent('mm-comp')
1347
1347
  target.remove()
1348
1348
  })
1349
1349
 
1350
- test("overlay mousemove with tooltip near top of viewport repositions below (line 139)", () => {
1350
+ test('overlay mousemove with tooltip near top of viewport repositions below (line 139)', () => {
1351
1351
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1352
1352
  enableOverlay: () => void
1353
1353
  disableOverlay: () => void
1354
1354
  }
1355
1355
 
1356
- const target = document.createElement("div")
1357
- target.style.cssText = "width:100px;height:20px;position:fixed;top:5px;left:10px;"
1356
+ const target = document.createElement('div')
1357
+ target.style.cssText = 'width:100px;height:20px;position:fixed;top:5px;left:10px;'
1358
1358
  document.body.appendChild(target)
1359
- registerComponent("top-mm", "TopMouseMove", target, null)
1359
+ registerComponent('top-mm', 'TopMouseMove', target, null)
1360
1360
 
1361
1361
  // Mock getBoundingClientRect to return rect.top < 35
1362
1362
  const origGetBCR = target.getBoundingClientRect.bind(target)
@@ -1377,83 +1377,83 @@ describe("devtools — overlay paths with mocked elementFromPoint", () => {
1377
1377
 
1378
1378
  devtools.enableOverlay()
1379
1379
 
1380
- const event = new MouseEvent("mousemove", { clientX: 50, clientY: 15, bubbles: true })
1380
+ const event = new MouseEvent('mousemove', { clientX: 50, clientY: 15, bubbles: true })
1381
1381
  document.dispatchEvent(event)
1382
1382
 
1383
1383
  devtools.disableOverlay()
1384
1384
  document.elementFromPoint = origElementFromPoint
1385
1385
  target.getBoundingClientRect = origGetBCR
1386
- unregisterComponent("top-mm")
1386
+ unregisterComponent('top-mm')
1387
1387
  target.remove()
1388
1388
  })
1389
1389
 
1390
- test("overlay mousemove with children count shows plural text (line 132)", () => {
1390
+ test('overlay mousemove with children count shows plural text (line 132)', () => {
1391
1391
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1392
1392
  enableOverlay: () => void
1393
1393
  disableOverlay: () => void
1394
1394
  }
1395
1395
 
1396
- const target = document.createElement("div")
1397
- target.style.cssText = "width:100px;height:100px;position:fixed;top:50px;left:50px;"
1396
+ const target = document.createElement('div')
1397
+ target.style.cssText = 'width:100px;height:100px;position:fixed;top:50px;left:50px;'
1398
1398
  document.body.appendChild(target)
1399
1399
 
1400
1400
  // Parent with 2 children — triggers plural "components" text
1401
- registerComponent("multi-parent", "MultiParent", target, null)
1402
- registerComponent("multi-c1", "Child1", null, "multi-parent")
1403
- registerComponent("multi-c2", "Child2", null, "multi-parent")
1401
+ registerComponent('multi-parent', 'MultiParent', target, null)
1402
+ registerComponent('multi-c1', 'Child1', null, 'multi-parent')
1403
+ registerComponent('multi-c2', 'Child2', null, 'multi-parent')
1404
1404
 
1405
1405
  const origElementFromPoint = document.elementFromPoint
1406
1406
  document.elementFromPoint = () => target
1407
1407
 
1408
1408
  devtools.enableOverlay()
1409
- const event = new MouseEvent("mousemove", { clientX: 75, clientY: 75, bubbles: true })
1409
+ const event = new MouseEvent('mousemove', { clientX: 75, clientY: 75, bubbles: true })
1410
1410
  document.dispatchEvent(event)
1411
1411
 
1412
1412
  devtools.disableOverlay()
1413
1413
  document.elementFromPoint = origElementFromPoint
1414
- unregisterComponent("multi-c2")
1415
- unregisterComponent("multi-c1")
1416
- unregisterComponent("multi-parent")
1414
+ unregisterComponent('multi-c2')
1415
+ unregisterComponent('multi-c1')
1416
+ unregisterComponent('multi-parent')
1417
1417
  target.remove()
1418
1418
  })
1419
1419
 
1420
- test("overlay mousemove with 1 child shows singular text (line 132)", () => {
1420
+ test('overlay mousemove with 1 child shows singular text (line 132)', () => {
1421
1421
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1422
1422
  enableOverlay: () => void
1423
1423
  disableOverlay: () => void
1424
1424
  }
1425
1425
 
1426
- const target = document.createElement("div")
1427
- target.style.cssText = "width:100px;height:100px;position:fixed;top:50px;left:50px;"
1426
+ const target = document.createElement('div')
1427
+ target.style.cssText = 'width:100px;height:100px;position:fixed;top:50px;left:50px;'
1428
1428
  document.body.appendChild(target)
1429
1429
 
1430
- registerComponent("single-parent", "SingleParent", target, null)
1431
- registerComponent("single-c1", "SingleChild", null, "single-parent")
1430
+ registerComponent('single-parent', 'SingleParent', target, null)
1431
+ registerComponent('single-c1', 'SingleChild', null, 'single-parent')
1432
1432
 
1433
1433
  const origElementFromPoint = document.elementFromPoint
1434
1434
  document.elementFromPoint = () => target
1435
1435
 
1436
1436
  devtools.enableOverlay()
1437
- const event = new MouseEvent("mousemove", { clientX: 75, clientY: 75, bubbles: true })
1437
+ const event = new MouseEvent('mousemove', { clientX: 75, clientY: 75, bubbles: true })
1438
1438
  document.dispatchEvent(event)
1439
1439
 
1440
1440
  devtools.disableOverlay()
1441
1441
  document.elementFromPoint = origElementFromPoint
1442
- unregisterComponent("single-c1")
1443
- unregisterComponent("single-parent")
1442
+ unregisterComponent('single-c1')
1443
+ unregisterComponent('single-parent')
1444
1444
  target.remove()
1445
1445
  })
1446
1446
 
1447
- test("overlay mousemove with entry.el null hides overlay (line 110)", () => {
1447
+ test('overlay mousemove with entry.el null hides overlay (line 110)', () => {
1448
1448
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1449
1449
  enableOverlay: () => void
1450
1450
  disableOverlay: () => void
1451
1451
  }
1452
1452
 
1453
- const target = document.createElement("div")
1453
+ const target = document.createElement('div')
1454
1454
  document.body.appendChild(target)
1455
1455
  // Register component with null element
1456
- registerComponent("null-el-comp", "NullElComp", null, null)
1456
+ registerComponent('null-el-comp', 'NullElComp', null, null)
1457
1457
 
1458
1458
  // elementFromPoint returns target, but findComponentForElement won't find
1459
1459
  // a match since none of our registered components have target as their el.
@@ -1463,107 +1463,107 @@ describe("devtools — overlay paths with mocked elementFromPoint", () => {
1463
1463
 
1464
1464
  devtools.enableOverlay()
1465
1465
  // First set a highlight by registering a component with the target
1466
- registerComponent("real-comp", "RealComp", target, null)
1467
- const event1 = new MouseEvent("mousemove", { clientX: 50, clientY: 50, bubbles: true })
1466
+ registerComponent('real-comp', 'RealComp', target, null)
1467
+ const event1 = new MouseEvent('mousemove', { clientX: 50, clientY: 50, bubbles: true })
1468
1468
  document.dispatchEvent(event1)
1469
- unregisterComponent("real-comp")
1469
+ unregisterComponent('real-comp')
1470
1470
 
1471
1471
  // Now target is not associated with any component, so entry.el won't match
1472
1472
  // This triggers the "no entry?.el" path
1473
- const event2 = new MouseEvent("mousemove", { clientX: 51, clientY: 51, bubbles: true })
1473
+ const event2 = new MouseEvent('mousemove', { clientX: 51, clientY: 51, bubbles: true })
1474
1474
  document.dispatchEvent(event2)
1475
1475
 
1476
1476
  devtools.disableOverlay()
1477
1477
  document.elementFromPoint = origElementFromPoint
1478
- unregisterComponent("null-el-comp")
1478
+ unregisterComponent('null-el-comp')
1479
1479
  target.remove()
1480
1480
  })
1481
1481
 
1482
- test("overlay click finds component entry and logs it (lines 149-165)", () => {
1482
+ test('overlay click finds component entry and logs it (lines 149-165)', () => {
1483
1483
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1484
1484
  enableOverlay: () => void
1485
1485
  disableOverlay: () => void
1486
1486
  }
1487
1487
 
1488
- const target = document.createElement("div")
1489
- target.style.cssText = "width:100px;height:100px;position:fixed;top:50px;left:50px;"
1488
+ const target = document.createElement('div')
1489
+ target.style.cssText = 'width:100px;height:100px;position:fixed;top:50px;left:50px;'
1490
1490
  document.body.appendChild(target)
1491
1491
 
1492
- registerComponent("click-found", "ClickFound", target, null)
1492
+ registerComponent('click-found', 'ClickFound', target, null)
1493
1493
 
1494
1494
  const origElementFromPoint = document.elementFromPoint
1495
1495
  document.elementFromPoint = () => target
1496
1496
 
1497
- const groupSpy = vi.spyOn(console, "group").mockImplementation(() => {})
1498
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {})
1499
- const groupEndSpy = vi.spyOn(console, "groupEnd").mockImplementation(() => {})
1497
+ const groupSpy = vi.spyOn(console, 'group').mockImplementation(() => {})
1498
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
1499
+ const groupEndSpy = vi.spyOn(console, 'groupEnd').mockImplementation(() => {})
1500
1500
 
1501
1501
  devtools.enableOverlay()
1502
1502
 
1503
- const event = new MouseEvent("click", { clientX: 75, clientY: 75, bubbles: true })
1503
+ const event = new MouseEvent('click', { clientX: 75, clientY: 75, bubbles: true })
1504
1504
  document.dispatchEvent(event)
1505
1505
 
1506
1506
  // click handler calls console.group, console.log, console.groupEnd
1507
1507
  expect(groupSpy).toHaveBeenCalled()
1508
- expect(logSpy).toHaveBeenCalledWith("element:", target)
1509
- expect(logSpy).toHaveBeenCalledWith("children:", 0)
1508
+ expect(logSpy).toHaveBeenCalledWith('element:', target)
1509
+ expect(logSpy).toHaveBeenCalledWith('children:', 0)
1510
1510
  expect(groupEndSpy).toHaveBeenCalled()
1511
1511
 
1512
1512
  // disableOverlay is called by click handler
1513
- expect(document.body.style.cursor).toBe("")
1513
+ expect(document.body.style.cursor).toBe('')
1514
1514
 
1515
1515
  groupSpy.mockRestore()
1516
1516
  logSpy.mockRestore()
1517
1517
  groupEndSpy.mockRestore()
1518
1518
  document.elementFromPoint = origElementFromPoint
1519
- unregisterComponent("click-found")
1519
+ unregisterComponent('click-found')
1520
1520
  target.remove()
1521
1521
  })
1522
1522
 
1523
- test("overlay click on component with parentId logs parent name (lines 159-162)", () => {
1523
+ test('overlay click on component with parentId logs parent name (lines 159-162)', () => {
1524
1524
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1525
1525
  enableOverlay: () => void
1526
1526
  disableOverlay: () => void
1527
1527
  }
1528
1528
 
1529
- const parentEl = document.createElement("div")
1530
- const childEl = document.createElement("span")
1529
+ const parentEl = document.createElement('div')
1530
+ const childEl = document.createElement('span')
1531
1531
  parentEl.appendChild(childEl)
1532
1532
  document.body.appendChild(parentEl)
1533
1533
 
1534
- registerComponent("click-parent-log", "ParentLog", parentEl, null)
1535
- registerComponent("click-child-log", "ChildLog", childEl, "click-parent-log")
1534
+ registerComponent('click-parent-log', 'ParentLog', parentEl, null)
1535
+ registerComponent('click-child-log', 'ChildLog', childEl, 'click-parent-log')
1536
1536
 
1537
1537
  const origElementFromPoint = document.elementFromPoint
1538
1538
  document.elementFromPoint = () => childEl
1539
1539
 
1540
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {})
1541
- const groupSpy = vi.spyOn(console, "group").mockImplementation(() => {})
1542
- const groupEndSpy = vi.spyOn(console, "groupEnd").mockImplementation(() => {})
1540
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
1541
+ const groupSpy = vi.spyOn(console, 'group').mockImplementation(() => {})
1542
+ const groupEndSpy = vi.spyOn(console, 'groupEnd').mockImplementation(() => {})
1543
1543
 
1544
1544
  devtools.enableOverlay()
1545
- const event = new MouseEvent("click", { clientX: 25, clientY: 25, bubbles: true })
1545
+ const event = new MouseEvent('click', { clientX: 25, clientY: 25, bubbles: true })
1546
1546
  document.dispatchEvent(event)
1547
1547
 
1548
1548
  // Should log parent name (line 161)
1549
- expect(logSpy).toHaveBeenCalledWith("parent:", "<ParentLog>")
1549
+ expect(logSpy).toHaveBeenCalledWith('parent:', '<ParentLog>')
1550
1550
 
1551
1551
  groupSpy.mockRestore()
1552
1552
  logSpy.mockRestore()
1553
1553
  groupEndSpy.mockRestore()
1554
1554
  document.elementFromPoint = origElementFromPoint
1555
- unregisterComponent("click-child-log")
1556
- unregisterComponent("click-parent-log")
1555
+ unregisterComponent('click-child-log')
1556
+ unregisterComponent('click-parent-log')
1557
1557
  parentEl.remove()
1558
1558
  })
1559
1559
 
1560
- test("overlay click on component without entry (no match) just disables (line 149-165 else)", () => {
1560
+ test('overlay click on component without entry (no match) just disables (line 149-165 else)', () => {
1561
1561
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1562
1562
  enableOverlay: () => void
1563
1563
  disableOverlay: () => void
1564
1564
  }
1565
1565
 
1566
- const target = document.createElement("div")
1566
+ const target = document.createElement('div')
1567
1567
  document.body.appendChild(target)
1568
1568
 
1569
1569
  // No component registered for this element
@@ -1571,11 +1571,11 @@ describe("devtools — overlay paths with mocked elementFromPoint", () => {
1571
1571
  document.elementFromPoint = () => target
1572
1572
 
1573
1573
  devtools.enableOverlay()
1574
- const event = new MouseEvent("click", { clientX: 50, clientY: 50, bubbles: true })
1574
+ const event = new MouseEvent('click', { clientX: 50, clientY: 50, bubbles: true })
1575
1575
  document.dispatchEvent(event)
1576
1576
 
1577
1577
  // disableOverlay still called — cursor restored
1578
- expect(document.body.style.cursor).toBe("")
1578
+ expect(document.body.style.cursor).toBe('')
1579
1579
 
1580
1580
  document.elementFromPoint = origElementFromPoint
1581
1581
  target.remove()
@@ -1584,20 +1584,20 @@ describe("devtools — overlay paths with mocked elementFromPoint", () => {
1584
1584
 
1585
1585
  // ─── devtools.ts — direct handler calls to cover remaining branches ──────────
1586
1586
 
1587
- describe("devtools — direct handler calls", () => {
1587
+ describe('devtools — direct handler calls', () => {
1588
1588
  beforeAll(() => {
1589
1589
  installDevTools()
1590
1590
  })
1591
1591
 
1592
- test("onOverlayMouseMove with rect.top >= 35 does NOT reposition tooltip below (line 138 false)", () => {
1592
+ test('onOverlayMouseMove with rect.top >= 35 does NOT reposition tooltip below (line 138 false)', () => {
1593
1593
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1594
1594
  enableOverlay: () => void
1595
1595
  disableOverlay: () => void
1596
1596
  }
1597
1597
 
1598
- const target = document.createElement("div")
1598
+ const target = document.createElement('div')
1599
1599
  document.body.appendChild(target)
1600
- registerComponent("direct-mm", "DirectMM", target, null)
1600
+ registerComponent('direct-mm', 'DirectMM', target, null)
1601
1601
 
1602
1602
  target.getBoundingClientRect = () => ({
1603
1603
  top: 100,
@@ -1616,27 +1616,27 @@ describe("devtools — direct handler calls", () => {
1616
1616
 
1617
1617
  devtools.enableOverlay()
1618
1618
  // Call handler directly
1619
- onOverlayMouseMove(new MouseEvent("mousemove", { clientX: 75, clientY: 125 }))
1619
+ onOverlayMouseMove(new MouseEvent('mousemove', { clientX: 75, clientY: 125 }))
1620
1620
 
1621
1621
  const tooltipEl = document.querySelector("[style*='ui-monospace']") as HTMLElement
1622
1622
  // tooltip top should be rect.top - 30 = 70, NOT repositioned below
1623
- if (tooltipEl) expect(tooltipEl.style.top).toBe("70px")
1623
+ if (tooltipEl) expect(tooltipEl.style.top).toBe('70px')
1624
1624
 
1625
1625
  devtools.disableOverlay()
1626
1626
  document.elementFromPoint = origEFP
1627
- unregisterComponent("direct-mm")
1627
+ unregisterComponent('direct-mm')
1628
1628
  target.remove()
1629
1629
  })
1630
1630
 
1631
- test("onOverlayMouseMove with rect.top < 35 repositions tooltip below (line 138 true)", () => {
1631
+ test('onOverlayMouseMove with rect.top < 35 repositions tooltip below (line 138 true)', () => {
1632
1632
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1633
1633
  enableOverlay: () => void
1634
1634
  disableOverlay: () => void
1635
1635
  }
1636
1636
 
1637
- const target = document.createElement("div")
1637
+ const target = document.createElement('div')
1638
1638
  document.body.appendChild(target)
1639
- registerComponent("direct-mm2", "DirectMM2", target, null)
1639
+ registerComponent('direct-mm2', 'DirectMM2', target, null)
1640
1640
 
1641
1641
  target.getBoundingClientRect = () => ({
1642
1642
  top: 10,
@@ -1654,56 +1654,56 @@ describe("devtools — direct handler calls", () => {
1654
1654
  document.elementFromPoint = () => target
1655
1655
 
1656
1656
  devtools.enableOverlay()
1657
- onOverlayMouseMove(new MouseEvent("mousemove", { clientX: 75, clientY: 15 }))
1657
+ onOverlayMouseMove(new MouseEvent('mousemove', { clientX: 75, clientY: 15 }))
1658
1658
 
1659
1659
  devtools.disableOverlay()
1660
1660
  document.elementFromPoint = origEFP
1661
- unregisterComponent("direct-mm2")
1661
+ unregisterComponent('direct-mm2')
1662
1662
  target.remove()
1663
1663
  })
1664
1664
 
1665
- test("onOverlayClick with parentId but parent unregistered (line 161 false)", () => {
1665
+ test('onOverlayClick with parentId but parent unregistered (line 161 false)', () => {
1666
1666
  const devtools = (window as unknown as Record<string, unknown>).__PYREON_DEVTOOLS__ as {
1667
1667
  enableOverlay: () => void
1668
1668
  disableOverlay: () => void
1669
1669
  }
1670
1670
 
1671
- const target = document.createElement("div")
1671
+ const target = document.createElement('div')
1672
1672
  document.body.appendChild(target)
1673
1673
  // Register child with parentId pointing to nonexistent parent
1674
- registerComponent("orphan-child", "OrphanChild", target, "nonexistent-parent")
1674
+ registerComponent('orphan-child', 'OrphanChild', target, 'nonexistent-parent')
1675
1675
 
1676
1676
  const origEFP = document.elementFromPoint
1677
1677
  document.elementFromPoint = () => target
1678
1678
 
1679
- const groupSpy = vi.spyOn(console, "group").mockImplementation(() => {})
1680
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {})
1681
- const groupEndSpy = vi.spyOn(console, "groupEnd").mockImplementation(() => {})
1679
+ const groupSpy = vi.spyOn(console, 'group').mockImplementation(() => {})
1680
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
1681
+ const groupEndSpy = vi.spyOn(console, 'groupEnd').mockImplementation(() => {})
1682
1682
 
1683
1683
  devtools.enableOverlay()
1684
- onOverlayClick(new MouseEvent("click", { clientX: 50, clientY: 50 }))
1684
+ onOverlayClick(new MouseEvent('click', { clientX: 50, clientY: 50 }))
1685
1685
 
1686
1686
  // entry.parentId is truthy, but _components.get("nonexistent-parent") is undefined
1687
1687
  // so the `if (parent)` false branch is hit
1688
1688
  expect(groupSpy).toHaveBeenCalled()
1689
- expect(logSpy).not.toHaveBeenCalledWith("parent:", expect.anything())
1689
+ expect(logSpy).not.toHaveBeenCalledWith('parent:', expect.anything())
1690
1690
 
1691
1691
  groupSpy.mockRestore()
1692
1692
  logSpy.mockRestore()
1693
1693
  groupEndSpy.mockRestore()
1694
1694
  document.elementFromPoint = origEFP
1695
- unregisterComponent("orphan-child")
1695
+ unregisterComponent('orphan-child')
1696
1696
  target.remove()
1697
1697
  })
1698
1698
 
1699
- test("$p.stats reports component counts (line 284)", () => {
1699
+ test('$p.stats reports component counts (line 284)', () => {
1700
1700
  const $p = (window as unknown as Record<string, unknown>).$p as {
1701
1701
  stats: () => { total: number; roots: number }
1702
1702
  }
1703
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {})
1703
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
1704
1704
  const result = $p.stats()
1705
- expect(result).toHaveProperty("total")
1706
- expect(result).toHaveProperty("roots")
1705
+ expect(result).toHaveProperty('total')
1706
+ expect(result).toHaveProperty('roots')
1707
1707
  expect(logSpy).toHaveBeenCalled()
1708
1708
  logSpy.mockRestore()
1709
1709
  })
@@ -1711,10 +1711,10 @@ describe("devtools — direct handler calls", () => {
1711
1711
 
1712
1712
  // ─── hydrate.ts — reactive accessor with complex VNode and no domNode ────────
1713
1713
 
1714
- describe("hydrate.ts — additional reactive accessor branches", () => {
1715
- test("hydrate reactive accessor returning null with domNode appends marker before it", () => {
1714
+ describe('hydrate.ts — additional reactive accessor branches', () => {
1715
+ test('hydrate reactive accessor returning null with domNode appends marker before it', () => {
1716
1716
  const el = container()
1717
- el.innerHTML = "<span>existing-content</span>"
1717
+ el.innerHTML = '<span>existing-content</span>'
1718
1718
  const content = signal<VNodeChild>(null)
1719
1719
  const cleanup = hydrateRoot(
1720
1720
  el,
@@ -1722,43 +1722,43 @@ describe("hydrate.ts — additional reactive accessor branches", () => {
1722
1722
  Fragment,
1723
1723
  null,
1724
1724
  (() => content()) as unknown as VNodeChild,
1725
- h("span", null, "existing-content"),
1725
+ h('span', null, 'existing-content'),
1726
1726
  ),
1727
1727
  )
1728
1728
  cleanup()
1729
1729
  })
1730
1730
 
1731
- test("hydrate reactive text matching a text node reuses it (line 110-116)", () => {
1731
+ test('hydrate reactive text matching a text node reuses it (line 110-116)', () => {
1732
1732
  const el = container()
1733
- el.innerHTML = "initial text"
1734
- const text = signal("initial text")
1733
+ el.innerHTML = 'initial text'
1734
+ const text = signal('initial text')
1735
1735
  const cleanup = hydrateRoot(el, (() => text()) as unknown as VNodeChild)
1736
1736
  // Should reuse the existing text node and attach effect
1737
- text.set("updated text")
1738
- expect(el.textContent).toContain("updated text")
1737
+ text.set('updated text')
1738
+ expect(el.textContent).toContain('updated text')
1739
1739
  cleanup()
1740
1740
  })
1741
1741
 
1742
- test("hydrate For without markers and domNode is null (line 187-194)", () => {
1742
+ test('hydrate For without markers and domNode is null (line 187-194)', () => {
1743
1743
  const el = container()
1744
- el.innerHTML = "" // empty, so domNode is null
1745
- const items = signal([{ id: 1, label: "x" }])
1744
+ el.innerHTML = '' // empty, so domNode is null
1745
+ const items = signal([{ id: 1, label: 'x' }])
1746
1746
  const cleanup = hydrateRoot(
1747
1747
  el,
1748
1748
  For({
1749
1749
  each: items,
1750
1750
  by: (r: { id: number }) => r.id,
1751
- children: (r: { id: number; label: string }) => h("li", null, r.label),
1751
+ children: (r: { id: number; label: string }) => h('li', null, r.label),
1752
1752
  }),
1753
1753
  )
1754
1754
  cleanup()
1755
1755
  })
1756
1756
 
1757
- test("hydrate element with null domNode (no matching DOM) falls back to mount", () => {
1757
+ test('hydrate element with null domNode (no matching DOM) falls back to mount', () => {
1758
1758
  const el = container()
1759
- el.innerHTML = "" // nothing to match
1760
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
1761
- const cleanup = hydrateRoot(el, h("div", null, "fresh"))
1759
+ el.innerHTML = '' // nothing to match
1760
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
1761
+ const cleanup = hydrateRoot(el, h('div', null, 'fresh'))
1762
1762
  cleanup()
1763
1763
  warnSpy.mockRestore()
1764
1764
  })
@@ -1766,8 +1766,8 @@ describe("hydrate.ts — additional reactive accessor branches", () => {
1766
1766
 
1767
1767
  // ─── transition.ts — additional pendingLeaveCancel branch ────────────────────
1768
1768
 
1769
- describe("Transition — pendingLeaveCancel exercised in rAF callback (lines 110-113)", () => {
1770
- test("leave rAF sets pendingLeaveCancel then re-enter cancels it", async () => {
1769
+ describe('Transition — pendingLeaveCancel exercised in rAF callback (lines 110-113)', () => {
1770
+ test('leave rAF sets pendingLeaveCancel then re-enter cancels it', async () => {
1771
1771
  const el = container()
1772
1772
  const visible = signal(true)
1773
1773
  const _removeListenerCalled = false
@@ -1775,8 +1775,8 @@ describe("Transition — pendingLeaveCancel exercised in rAF callback (lines 110
1775
1775
  mount(
1776
1776
  h(Transition, {
1777
1777
  show: visible,
1778
- name: "cancel-test",
1779
- children: h("div", { id: "cancel-target" }, "content"),
1778
+ name: 'cancel-test',
1779
+ children: h('div', { id: 'cancel-target' }, 'content'),
1780
1780
  }),
1781
1781
  el,
1782
1782
  )
@@ -1788,8 +1788,8 @@ describe("Transition — pendingLeaveCancel exercised in rAF callback (lines 110
1788
1788
  await new Promise<void>((r) => requestAnimationFrame(() => r()))
1789
1789
 
1790
1790
  // The target should now have leave classes
1791
- const target = el.querySelector("#cancel-target")
1792
- expect(target?.classList.contains("cancel-test-leave-active")).toBe(true)
1791
+ const target = el.querySelector('#cancel-target')
1792
+ expect(target?.classList.contains('cancel-test-leave-active')).toBe(true)
1793
1793
 
1794
1794
  // Re-enter — applyEnter calls pendingLeaveCancel (line 80-81)
1795
1795
  visible.set(true)
@@ -1803,87 +1803,87 @@ describe("Transition — pendingLeaveCancel exercised in rAF callback (lines 110
1803
1803
 
1804
1804
  // ─── mount.ts — mountElement nested paths ────────────────────────────────────
1805
1805
 
1806
- describe("mount.ts — additional nested element branch paths", () => {
1807
- test("nested element with ref + propCleanup + childCleanup at depth > 0 (lines 202-207)", () => {
1806
+ describe('mount.ts — additional nested element branch paths', () => {
1807
+ test('nested element with ref + propCleanup + childCleanup at depth > 0 (lines 202-207)', () => {
1808
1808
  const el = container()
1809
1809
  const ref = createRef<HTMLElement>()
1810
- const cls = signal("dynamic")
1811
- const text = signal("inner")
1810
+ const cls = signal('dynamic')
1811
+ const text = signal('inner')
1812
1812
 
1813
1813
  const unmount = mount(
1814
1814
  h(
1815
- "div",
1815
+ 'div',
1816
1816
  null,
1817
- h("span", { ref, class: () => cls() }, () => text()),
1817
+ h('span', { ref, class: () => cls() }, () => text()),
1818
1818
  ),
1819
1819
  el,
1820
1820
  )
1821
1821
 
1822
1822
  expect(ref.current).not.toBeNull()
1823
- expect(ref.current?.className).toBe("dynamic")
1824
- expect(ref.current?.textContent).toBe("inner")
1823
+ expect(ref.current?.className).toBe('dynamic')
1824
+ expect(ref.current?.textContent).toBe('inner')
1825
1825
 
1826
- cls.set("changed")
1827
- text.set("updated")
1828
- expect(ref.current?.className).toBe("changed")
1826
+ cls.set('changed')
1827
+ text.set('updated')
1828
+ expect(ref.current?.className).toBe('changed')
1829
1829
 
1830
1830
  unmount()
1831
1831
  expect(ref.current).toBeNull()
1832
1832
  })
1833
1833
 
1834
- test("nested element with childCleanup but no ref and no propCleanup (line 196)", () => {
1834
+ test('nested element with childCleanup but no ref and no propCleanup (line 196)', () => {
1835
1835
  const el = container()
1836
- const text = signal("reactive-child")
1836
+ const text = signal('reactive-child')
1837
1837
 
1838
1838
  // span is nested (depth > 0), has reactive child (childCleanup !== noop)
1839
1839
  // but no ref and no propCleanup
1840
1840
  const unmount = mount(
1841
1841
  h(
1842
- "div",
1842
+ 'div',
1843
1843
  null,
1844
- h("span", null, () => text()),
1844
+ h('span', null, () => text()),
1845
1845
  ),
1846
1846
  el,
1847
1847
  )
1848
1848
 
1849
- expect(el.querySelector("span")?.textContent).toBe("reactive-child")
1850
- text.set("changed-child")
1851
- expect(el.querySelector("span")?.textContent).toBe("changed-child")
1849
+ expect(el.querySelector('span')?.textContent).toBe('reactive-child')
1850
+ text.set('changed-child')
1851
+ expect(el.querySelector('span')?.textContent).toBe('changed-child')
1852
1852
  unmount()
1853
1853
  })
1854
1854
 
1855
- test("nested element with propCleanup but no ref at depth > 0 (line 197)", () => {
1855
+ test('nested element with propCleanup but no ref at depth > 0 (line 197)', () => {
1856
1856
  const el = container()
1857
- const cls = signal("cls-val")
1857
+ const cls = signal('cls-val')
1858
1858
 
1859
1859
  // span nested, has reactive prop (propCleanup) but no ref
1860
1860
  // childCleanup is noop (static text child)
1861
- const unmount = mount(h("div", null, h("span", { class: () => cls() }, "static-text")), el)
1861
+ const unmount = mount(h('div', null, h('span', { class: () => cls() }, 'static-text')), el)
1862
1862
 
1863
- expect(el.querySelector("span")?.className).toBe("cls-val")
1864
- cls.set("new-cls")
1865
- expect(el.querySelector("span")?.className).toBe("new-cls")
1863
+ expect(el.querySelector('span')?.className).toBe('cls-val')
1864
+ cls.set('new-cls')
1865
+ expect(el.querySelector('span')?.className).toBe('new-cls')
1866
1866
  unmount()
1867
1867
  })
1868
1868
  })
1869
1869
 
1870
1870
  // ─── nodes.ts — mountFor NativeItem without cleanup in step 3 ────────────────
1871
1871
 
1872
- describe("nodes.ts — mountFor NativeItem edge cases in step 3", () => {
1873
- test("mountFor step 3 NativeItem without cleanup (cleanupCount unchanged)", () => {
1872
+ describe('nodes.ts — mountFor NativeItem edge cases in step 3', () => {
1873
+ test('mountFor step 3 NativeItem without cleanup (cleanupCount unchanged)', () => {
1874
1874
  const el = container()
1875
- const items = signal([{ id: 1, label: "a" }])
1875
+ const items = signal([{ id: 1, label: 'a' }])
1876
1876
 
1877
1877
  mount(
1878
1878
  h(
1879
- "div",
1879
+ 'div',
1880
1880
  null,
1881
1881
  For({
1882
1882
  each: items,
1883
1883
  by: (r: { id: number }) => r.id,
1884
1884
  children: (r: { id: number; label: string }) => {
1885
1885
  // NativeItem with null cleanup
1886
- const native = _tpl("<b></b>", (root) => {
1886
+ const native = _tpl('<b></b>', (root) => {
1887
1887
  root.textContent = r.label
1888
1888
  return null
1889
1889
  })
@@ -1896,59 +1896,59 @@ describe("nodes.ts — mountFor NativeItem edge cases in step 3", () => {
1896
1896
 
1897
1897
  // Add new item — step 3 mounts NativeItem with null cleanup
1898
1898
  items.set([
1899
- { id: 1, label: "a" },
1900
- { id: 2, label: "b" },
1899
+ { id: 1, label: 'a' },
1900
+ { id: 2, label: 'b' },
1901
1901
  ])
1902
- expect(el.querySelectorAll("b").length).toBe(2)
1902
+ expect(el.querySelectorAll('b').length).toBe(2)
1903
1903
 
1904
1904
  el.remove()
1905
1905
  })
1906
1906
 
1907
- test("mountFor step 2 removes stale entry with cleanup (cleanupCount--)", () => {
1907
+ test('mountFor step 2 removes stale entry with cleanup (cleanupCount--)', () => {
1908
1908
  const el = container()
1909
1909
  const _cleanupCalled = false
1910
1910
  const items = signal([
1911
- { id: 1, label: "a" },
1912
- { id: 2, label: "b" },
1911
+ { id: 1, label: 'a' },
1912
+ { id: 2, label: 'b' },
1913
1913
  ])
1914
1914
 
1915
1915
  mount(
1916
1916
  h(
1917
- "div",
1917
+ 'div',
1918
1918
  null,
1919
1919
  For({
1920
1920
  each: items,
1921
1921
  by: (r: { id: number }) => r.id,
1922
- children: (r: { id: number; label: string }) => h("span", null, r.label),
1922
+ children: (r: { id: number; label: string }) => h('span', null, r.label),
1923
1923
  }),
1924
1924
  ),
1925
1925
  el,
1926
1926
  )
1927
1927
 
1928
1928
  // Remove item 2 but keep item 1 — step 2 removes stale entry
1929
- items.set([{ id: 1, label: "a" }])
1930
- expect(el.querySelectorAll("span").length).toBe(1)
1929
+ items.set([{ id: 1, label: 'a' }])
1930
+ expect(el.querySelectorAll('span').length).toBe(1)
1931
1931
 
1932
1932
  el.remove()
1933
1933
  })
1934
1934
 
1935
- test("mountFor step 2 removes stale NativeItem entry with cleanup", () => {
1935
+ test('mountFor step 2 removes stale NativeItem entry with cleanup', () => {
1936
1936
  const el = container()
1937
1937
  let cleanupCount = 0
1938
1938
  const items = signal([
1939
- { id: 1, label: "a" },
1940
- { id: 2, label: "b" },
1939
+ { id: 1, label: 'a' },
1940
+ { id: 2, label: 'b' },
1941
1941
  ])
1942
1942
 
1943
1943
  mount(
1944
1944
  h(
1945
- "div",
1945
+ 'div',
1946
1946
  null,
1947
1947
  For({
1948
1948
  each: items,
1949
1949
  by: (r: { id: number }) => r.id,
1950
1950
  children: (r: { id: number; label: string }) => {
1951
- const native = _tpl("<b></b>", (root) => {
1951
+ const native = _tpl('<b></b>', (root) => {
1952
1952
  root.textContent = r.label
1953
1953
  return () => {
1954
1954
  cleanupCount++
@@ -1962,26 +1962,26 @@ describe("nodes.ts — mountFor NativeItem edge cases in step 3", () => {
1962
1962
  )
1963
1963
 
1964
1964
  // Remove item 2 (keeps item 1) — step 2 with entry.cleanup non-null
1965
- items.set([{ id: 1, label: "a" }])
1965
+ items.set([{ id: 1, label: 'a' }])
1966
1966
  expect(cleanupCount).toBe(1)
1967
1967
 
1968
1968
  el.remove()
1969
1969
  })
1970
1970
 
1971
- test("mountFor clear path with cleanupCount = 0 skips cleanup iteration", () => {
1971
+ test('mountFor clear path with cleanupCount = 0 skips cleanup iteration', () => {
1972
1972
  const el = container()
1973
1973
  const items = signal([{ id: 1 }])
1974
1974
 
1975
1975
  // Use NativeItem with null cleanup so cleanupCount stays 0
1976
1976
  mount(
1977
1977
  h(
1978
- "div",
1978
+ 'div',
1979
1979
  null,
1980
1980
  For({
1981
1981
  each: items,
1982
1982
  by: (r: { id: number }) => r.id,
1983
1983
  children: (r: { id: number }) => {
1984
- const native = _tpl("<b></b>", (root) => {
1984
+ const native = _tpl('<b></b>', (root) => {
1985
1985
  root.textContent = String(r.id)
1986
1986
  return null
1987
1987
  })
@@ -1994,24 +1994,24 @@ describe("nodes.ts — mountFor NativeItem edge cases in step 3", () => {
1994
1994
 
1995
1995
  // Clear — cleanupCount = 0, so skip cleanup iteration
1996
1996
  items.set([])
1997
- expect(el.querySelectorAll("b").length).toBe(0)
1997
+ expect(el.querySelectorAll('b').length).toBe(0)
1998
1998
 
1999
1999
  el.remove()
2000
2000
  })
2001
2001
 
2002
- test("mountFor replace-all with NativeItem entries having no cleanup", () => {
2002
+ test('mountFor replace-all with NativeItem entries having no cleanup', () => {
2003
2003
  const el = container()
2004
- const items = signal([{ id: 1, label: "old" }])
2004
+ const items = signal([{ id: 1, label: 'old' }])
2005
2005
 
2006
2006
  mount(
2007
2007
  h(
2008
- "div",
2008
+ 'div',
2009
2009
  null,
2010
2010
  For({
2011
2011
  each: items,
2012
2012
  by: (r: { id: number }) => r.id,
2013
2013
  children: (r: { id: number; label: string }) => {
2014
- const native = _tpl("<b></b>", (root) => {
2014
+ const native = _tpl('<b></b>', (root) => {
2015
2015
  root.textContent = r.label
2016
2016
  return null // no cleanup
2017
2017
  })
@@ -2023,8 +2023,8 @@ describe("nodes.ts — mountFor NativeItem edge cases in step 3", () => {
2023
2023
  )
2024
2024
 
2025
2025
  // Replace all with completely new keys
2026
- items.set([{ id: 10, label: "new" }])
2027
- expect(el.querySelector("b")?.textContent).toBe("new")
2026
+ items.set([{ id: 10, label: 'new' }])
2027
+ expect(el.querySelector('b')?.textContent).toBe('new')
2028
2028
 
2029
2029
  el.remove()
2030
2030
  })
@@ -2032,70 +2032,70 @@ describe("nodes.ts — mountFor NativeItem edge cases in step 3", () => {
2032
2032
 
2033
2033
  // ─── props.ts — sanitizeHtml SSR fallback (no DOMParser) ─────────────────────
2034
2034
 
2035
- describe("props.ts — sanitizeHtml edge cases", () => {
2036
- test("sanitizeHtml with custom sanitizer takes priority over native and fallback", () => {
2035
+ describe('props.ts — sanitizeHtml edge cases', () => {
2036
+ test('sanitizeHtml with custom sanitizer takes priority over native and fallback', () => {
2037
2037
  let called = false
2038
2038
  setSanitizer((html) => {
2039
2039
  called = true
2040
- return html.replace(/<[^>]*>/g, "STRIPPED")
2040
+ return html.replace(/<[^>]*>/g, 'STRIPPED')
2041
2041
  })
2042
- const result = sanitizeHtml("<b>test</b>")
2042
+ const result = sanitizeHtml('<b>test</b>')
2043
2043
  expect(called).toBe(true)
2044
- expect(result).toContain("STRIPPED")
2044
+ expect(result).toContain('STRIPPED')
2045
2045
  setSanitizer(null)
2046
2046
  })
2047
2047
 
2048
- test("applyProp with reactive function prop creates renderEffect", () => {
2049
- const el = document.createElement("div")
2050
- const title = signal("initial")
2051
- const cleanup = applyProp(el, "title", () => title())
2052
- expect(el.title).toBe("initial")
2053
- title.set("updated")
2054
- expect(el.title).toBe("updated")
2048
+ test('applyProp with reactive function prop creates renderEffect', () => {
2049
+ const el = document.createElement('div')
2050
+ const title = signal('initial')
2051
+ const cleanup = applyProp(el, 'title', () => title())
2052
+ expect(el.title).toBe('initial')
2053
+ title.set('updated')
2054
+ expect(el.title).toBe('updated')
2055
2055
  cleanup?.()
2056
2056
  })
2057
2057
  })
2058
2058
 
2059
2059
  // ─── keep-alive.ts — KeepAlive where container ref not yet set ───────────────
2060
2060
 
2061
- describe("KeepAlive — container ref edge cases", () => {
2062
- test("KeepAlive mounts children once and preserves state across hide/show cycles", () => {
2061
+ describe('KeepAlive — container ref edge cases', () => {
2062
+ test('KeepAlive mounts children once and preserves state across hide/show cycles', () => {
2063
2063
  const el = container()
2064
2064
  const active = signal(true)
2065
2065
  const count = signal(0)
2066
2066
 
2067
2067
  const Inner = defineComponent(() => {
2068
- return h("span", null, () => String(count()))
2068
+ return h('span', null, () => String(count()))
2069
2069
  })
2070
2070
 
2071
2071
  const unmount = mount(h(KeepAlive, { active: () => active() }, h(Inner, null)), el)
2072
2072
 
2073
- expect(el.querySelector("span")?.textContent).toBe("0")
2073
+ expect(el.querySelector('span')?.textContent).toBe('0')
2074
2074
 
2075
2075
  // Update state while visible
2076
2076
  count.set(5)
2077
- expect(el.querySelector("span")?.textContent).toBe("5")
2077
+ expect(el.querySelector('span')?.textContent).toBe('5')
2078
2078
 
2079
2079
  // Hide — state preserved
2080
2080
  active.set(false)
2081
- const wrapper = el.querySelector("div[style*='display']") ?? el.querySelector("div")
2081
+ const wrapper = el.querySelector("div[style*='display']") ?? el.querySelector('div')
2082
2082
  expect(wrapper).not.toBeNull()
2083
2083
 
2084
2084
  // Show again — state still preserved
2085
2085
  active.set(true)
2086
- expect(el.querySelector("span")?.textContent).toBe("5")
2086
+ expect(el.querySelector('span')?.textContent).toBe('5')
2087
2087
 
2088
2088
  unmount()
2089
2089
  })
2090
2090
 
2091
- test("KeepAlive with null children mounts nothing (line 55)", () => {
2091
+ test('KeepAlive with null children mounts nothing (line 55)', () => {
2092
2092
  const el = container()
2093
2093
  const unmount = mount(
2094
2094
  h(KeepAlive, { active: () => true, children: null as unknown as undefined }),
2095
2095
  el,
2096
2096
  )
2097
2097
  // Container exists but empty
2098
- const wrapper = el.querySelector("div")
2098
+ const wrapper = el.querySelector('div')
2099
2099
  expect(wrapper).not.toBeNull()
2100
2100
  unmount()
2101
2101
  })
@@ -2103,23 +2103,23 @@ describe("KeepAlive — container ref edge cases", () => {
2103
2103
 
2104
2104
  // ─── Additional coverage: mountKeyedList LIS array growth (nodes.ts lines 174-178) ──
2105
2105
 
2106
- describe("nodes.ts — mountKeyedList LIS typed array reallocation", () => {
2107
- test("mountKeyedList with >16 keyed items triggers LIS array growth", () => {
2106
+ describe('nodes.ts — mountKeyedList LIS typed array reallocation', () => {
2107
+ test('mountKeyedList with >16 keyed items triggers LIS array growth', () => {
2108
2108
  const el = container()
2109
2109
  // Start with > 16 keyed VNodes to exceed initial Int32Array(16) size
2110
- const makeItems = (ids: number[]) => ids.map((id) => h("span", { key: id }, String(id)))
2110
+ const makeItems = (ids: number[]) => ids.map((id) => h('span', { key: id }, String(id)))
2111
2111
 
2112
2112
  const ids = Array.from({ length: 20 }, (_, i) => i)
2113
2113
  const items = signal(makeItems(ids) as VNodeChild)
2114
2114
 
2115
- mount(h("div", null, (() => items()) as VNodeChild), el)
2116
- expect(el.querySelectorAll("span").length).toBe(20)
2115
+ mount(h('div', null, (() => items()) as VNodeChild), el)
2116
+ expect(el.querySelectorAll('span').length).toBe(20)
2117
2117
 
2118
2118
  // Reverse to trigger LIS reorder with typed array growth
2119
2119
  const reversed = [...ids].reverse()
2120
2120
  items.set(makeItems(reversed) as unknown as VNodeChild)
2121
- expect(el.querySelectorAll("span").length).toBe(20)
2122
- expect(el.querySelectorAll("span")[0]?.textContent).toBe("19")
2121
+ expect(el.querySelectorAll('span').length).toBe(20)
2122
+ expect(el.querySelectorAll('span')[0]?.textContent).toBe('19')
2123
2123
 
2124
2124
  el.remove()
2125
2125
  })
@@ -2127,13 +2127,13 @@ describe("nodes.ts — mountKeyedList LIS typed array reallocation", () => {
2127
2127
 
2128
2128
  // ─── Additional coverage: Transition with no children (rawChild is undefined) ──
2129
2129
 
2130
- describe("Transition — rawChild undefined branch (line 164-165)", () => {
2131
- test("Transition with no children returns null when mounted", () => {
2130
+ describe('Transition — rawChild undefined branch (line 164-165)', () => {
2131
+ test('Transition with no children returns null when mounted', () => {
2132
2132
  const el = container()
2133
2133
  const visible = signal(true)
2134
2134
 
2135
2135
  // No children prop at all — rawChild is undefined
2136
- mount(h(Transition, { show: visible, name: "fade" }), el)
2136
+ mount(h(Transition, { show: visible, name: 'fade' }), el)
2137
2137
 
2138
2138
  // Should not throw; renders nothing meaningful
2139
2139
  el.remove()
@@ -2142,44 +2142,44 @@ describe("Transition — rawChild undefined branch (line 164-165)", () => {
2142
2142
 
2143
2143
  // ─── Additional coverage: anonymous component (mount.ts line 234 || "Anonymous") ──
2144
2144
 
2145
- describe("mount.ts — anonymous component name fallback", () => {
2145
+ describe('mount.ts — anonymous component name fallback', () => {
2146
2146
  test("anonymous component uses 'Anonymous' fallback name", () => {
2147
2147
  const el = container()
2148
2148
 
2149
2149
  // Arrow function has name: "" (empty string) — triggers || "Anonymous"
2150
- const unmount = mount(h((() => h("span", null, "anon")) as unknown as ComponentFn, null), el)
2150
+ const unmount = mount(h((() => h('span', null, 'anon')) as unknown as ComponentFn, null), el)
2151
2151
 
2152
- expect(el.querySelector("span")?.textContent).toBe("anon")
2152
+ expect(el.querySelector('span')?.textContent).toBe('anon')
2153
2153
  unmount()
2154
2154
  })
2155
2155
  })
2156
2156
 
2157
2157
  // ─── Additional coverage: TransitionGroup FLIP inner rAF (lines 209-218) ──
2158
2158
 
2159
- describe("TransitionGroup — FLIP inner rAF with mocked getBoundingClientRect", () => {
2160
- test("FLIP move animation fires inner rAF when positions differ", async () => {
2159
+ describe('TransitionGroup — FLIP inner rAF with mocked getBoundingClientRect', () => {
2160
+ test('FLIP move animation fires inner rAF when positions differ', async () => {
2161
2161
  const el = container()
2162
2162
  const items = signal([
2163
- { id: 1, label: "a" },
2164
- { id: 2, label: "b" },
2165
- { id: 3, label: "c" },
2163
+ { id: 1, label: 'a' },
2164
+ { id: 2, label: 'b' },
2165
+ { id: 3, label: 'c' },
2166
2166
  ])
2167
2167
 
2168
2168
  mount(
2169
2169
  h(TransitionGroup, {
2170
- tag: "div",
2171
- name: "flip",
2170
+ tag: 'div',
2171
+ name: 'flip',
2172
2172
  items: () => items(),
2173
2173
  keyFn: (item: { id: number }) => item.id,
2174
2174
  render: (item: { id: number; label: string }) =>
2175
- h("span", { class: "flip-mock" }, item.label),
2175
+ h('span', { class: 'flip-mock' }, item.label),
2176
2176
  }),
2177
2177
  el,
2178
2178
  )
2179
2179
  await new Promise<void>((r) => queueMicrotask(r))
2180
2180
 
2181
2181
  // Mock getBoundingClientRect on each span to return different positions
2182
- const spans = el.querySelectorAll("span.flip-mock")
2182
+ const spans = el.querySelectorAll('span.flip-mock')
2183
2183
  let callCount = 0
2184
2184
  for (const span of spans) {
2185
2185
  const idx = callCount++
@@ -2198,13 +2198,13 @@ describe("TransitionGroup — FLIP inner rAF with mocked getBoundingClientRect",
2198
2198
 
2199
2199
  // Reorder items to trigger FLIP
2200
2200
  items.set([
2201
- { id: 3, label: "c" },
2202
- { id: 1, label: "a" },
2203
- { id: 2, label: "b" },
2201
+ { id: 3, label: 'c' },
2202
+ { id: 1, label: 'a' },
2203
+ { id: 2, label: 'b' },
2204
2204
  ])
2205
2205
 
2206
2206
  // Update getBoundingClientRect for new positions
2207
- const newSpans = el.querySelectorAll("span.flip-mock")
2207
+ const newSpans = el.querySelectorAll('span.flip-mock')
2208
2208
  let newIdx = 0
2209
2209
  for (const span of newSpans) {
2210
2210
  const i = newIdx++
@@ -2228,8 +2228,8 @@ describe("TransitionGroup — FLIP inner rAF with mocked getBoundingClientRect",
2228
2228
  await new Promise<void>((r) => requestAnimationFrame(() => r()))
2229
2229
 
2230
2230
  // Fire transitionend to clean up move class
2231
- for (const span of el.querySelectorAll("span.flip-mock")) {
2232
- span.dispatchEvent(new Event("transitionend"))
2231
+ for (const span of el.querySelectorAll('span.flip-mock')) {
2232
+ span.dispatchEvent(new Event('transitionend'))
2233
2233
  }
2234
2234
 
2235
2235
  el.remove()
@@ -2238,22 +2238,22 @@ describe("TransitionGroup — FLIP inner rAF with mocked getBoundingClientRect",
2238
2238
 
2239
2239
  // ─── Additional coverage: Transition with custom class overrides ──
2240
2240
 
2241
- describe("Transition — custom class overrides (transition.ts ?? branches)", () => {
2242
- test("Transition with explicit class overrides uses provided classes", async () => {
2241
+ describe('Transition — custom class overrides (transition.ts ?? branches)', () => {
2242
+ test('Transition with explicit class overrides uses provided classes', async () => {
2243
2243
  const el = container()
2244
2244
  const visible = signal(false)
2245
2245
 
2246
2246
  mount(
2247
2247
  h(Transition, {
2248
2248
  show: visible,
2249
- name: "custom",
2250
- enterFrom: "my-enter-from",
2251
- enterActive: "my-enter-active",
2252
- enterTo: "my-enter-to",
2253
- leaveFrom: "my-leave-from",
2254
- leaveActive: "my-leave-active",
2255
- leaveTo: "my-leave-to",
2256
- children: h("div", { id: "custom-cls" }, "custom"),
2249
+ name: 'custom',
2250
+ enterFrom: 'my-enter-from',
2251
+ enterActive: 'my-enter-active',
2252
+ enterTo: 'my-enter-to',
2253
+ leaveFrom: 'my-leave-from',
2254
+ leaveActive: 'my-leave-active',
2255
+ leaveTo: 'my-leave-to',
2256
+ children: h('div', { id: 'custom-cls' }, 'custom'),
2257
2257
  }),
2258
2258
  el,
2259
2259
  )
@@ -2261,14 +2261,14 @@ describe("Transition — custom class overrides (transition.ts ?? branches)", ()
2261
2261
  visible.set(true)
2262
2262
  await new Promise<void>((r) => queueMicrotask(r))
2263
2263
 
2264
- const target = el.querySelector("#custom-cls")
2264
+ const target = el.querySelector('#custom-cls')
2265
2265
  await new Promise<void>((r) => requestAnimationFrame(() => r()))
2266
2266
  // After rAF, enter-active and enter-to should be applied
2267
- expect(target?.classList.contains("my-enter-active")).toBe(true)
2268
- expect(target?.classList.contains("my-enter-to")).toBe(true)
2267
+ expect(target?.classList.contains('my-enter-active')).toBe(true)
2268
+ expect(target?.classList.contains('my-enter-to')).toBe(true)
2269
2269
 
2270
2270
  // Fire transitionend
2271
- if (target) target.dispatchEvent(new Event("transitionend"))
2271
+ if (target) target.dispatchEvent(new Event('transitionend'))
2272
2272
 
2273
2273
  el.remove()
2274
2274
  })
@@ -2276,25 +2276,25 @@ describe("Transition — custom class overrides (transition.ts ?? branches)", ()
2276
2276
 
2277
2277
  // ─── Additional coverage: TransitionGroup with custom class overrides ──
2278
2278
 
2279
- describe("TransitionGroup — custom class overrides (transition-group.ts ?? branches)", () => {
2280
- test("TransitionGroup with explicit class overrides", async () => {
2279
+ describe('TransitionGroup — custom class overrides (transition-group.ts ?? branches)', () => {
2280
+ test('TransitionGroup with explicit class overrides', async () => {
2281
2281
  const el = container()
2282
- const items = signal([{ id: 1, label: "a" }])
2282
+ const items = signal([{ id: 1, label: 'a' }])
2283
2283
 
2284
2284
  mount(
2285
2285
  h(TransitionGroup, {
2286
- tag: "ul",
2287
- name: "tg",
2286
+ tag: 'ul',
2287
+ name: 'tg',
2288
2288
  items: () => items(),
2289
2289
  keyFn: (item: { id: number }) => item.id,
2290
- render: (item: { id: number; label: string }) => h("li", { class: "tg-item" }, item.label),
2291
- enterFrom: "custom-ef",
2292
- enterActive: "custom-ea",
2293
- enterTo: "custom-et",
2294
- leaveFrom: "custom-lf",
2295
- leaveActive: "custom-la",
2296
- leaveTo: "custom-lt",
2297
- moveClass: "custom-move",
2290
+ render: (item: { id: number; label: string }) => h('li', { class: 'tg-item' }, item.label),
2291
+ enterFrom: 'custom-ef',
2292
+ enterActive: 'custom-ea',
2293
+ enterTo: 'custom-et',
2294
+ leaveFrom: 'custom-lf',
2295
+ leaveActive: 'custom-la',
2296
+ leaveTo: 'custom-lt',
2297
+ moveClass: 'custom-move',
2298
2298
  }),
2299
2299
  el,
2300
2300
  )
@@ -2302,8 +2302,8 @@ describe("TransitionGroup — custom class overrides (transition-group.ts ?? bra
2302
2302
 
2303
2303
  // Add item to trigger enter
2304
2304
  items.set([
2305
- { id: 1, label: "a" },
2306
- { id: 2, label: "b" },
2305
+ { id: 1, label: 'a' },
2306
+ { id: 2, label: 'b' },
2307
2307
  ])
2308
2308
  await new Promise<void>((r) => queueMicrotask(r))
2309
2309
 
@@ -2313,21 +2313,21 @@ describe("TransitionGroup — custom class overrides (transition-group.ts ?? bra
2313
2313
 
2314
2314
  // ─── transition.ts — component child warning (line 170) ─────────────────────
2315
2315
 
2316
- describe("transition.ts — component child warning", () => {
2317
- test("Transition warns when child is a component (line 170)", async () => {
2318
- const el = document.createElement("div")
2316
+ describe('transition.ts — component child warning', () => {
2317
+ test('Transition warns when child is a component (line 170)', async () => {
2318
+ const el = document.createElement('div')
2319
2319
  document.body.appendChild(el)
2320
2320
  const show = signal(true)
2321
2321
 
2322
2322
  function Inner() {
2323
- return h("span", null, "inner")
2323
+ return h('span', null, 'inner')
2324
2324
  }
2325
2325
 
2326
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
2326
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
2327
2327
 
2328
2328
  mount(
2329
2329
  h(Transition, {
2330
- name: "fade",
2330
+ name: 'fade',
2331
2331
  show: () => show(),
2332
2332
  children: h(Inner, null),
2333
2333
  }),
@@ -2335,50 +2335,50 @@ describe("transition.ts — component child warning", () => {
2335
2335
  )
2336
2336
  await new Promise<void>((r) => queueMicrotask(r))
2337
2337
 
2338
- expect(warn).toHaveBeenCalledWith(expect.stringContaining("Transition child is a component"))
2338
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('Transition child is a component'))
2339
2339
  warn.mockRestore()
2340
2340
  el.remove()
2341
2341
  })
2342
2342
 
2343
- test("Transition with non-VNode children returns rawChild ?? null (line 165)", async () => {
2344
- const el = document.createElement("div")
2343
+ test('Transition with non-VNode children returns rawChild ?? null (line 165)', async () => {
2344
+ const el = document.createElement('div')
2345
2345
  document.body.appendChild(el)
2346
2346
  const show = signal(true)
2347
2347
 
2348
2348
  mount(
2349
2349
  h(Transition, {
2350
- name: "fade",
2350
+ name: 'fade',
2351
2351
  show: () => show(),
2352
- children: "just text",
2352
+ children: 'just text',
2353
2353
  }),
2354
2354
  el,
2355
2355
  )
2356
2356
  await new Promise<void>((r) => queueMicrotask(r))
2357
- expect(el.textContent).toContain("just text")
2357
+ expect(el.textContent).toContain('just text')
2358
2358
  el.remove()
2359
2359
  })
2360
2360
  })
2361
2361
 
2362
2362
  // ─── devtools.ts — overlay handlers (lines 128-169, 284) ────────────────────
2363
2363
 
2364
- describe("devtools.ts — $p console helper branches", () => {
2365
- test("$p.highlight with unknown id does nothing", () => {
2364
+ describe('devtools.ts — $p console helper branches', () => {
2365
+ test('$p.highlight with unknown id does nothing', () => {
2366
2366
  installDevTools()
2367
2367
  const p = (window as unknown as Record<string, unknown>).$p as Record<
2368
2368
  string,
2369
2369
  (...args: unknown[]) => unknown
2370
2370
  >
2371
2371
  // Should not throw
2372
- p.highlight?.("nonexistent-id-12345")
2372
+ p.highlight?.('nonexistent-id-12345')
2373
2373
  })
2374
2374
 
2375
- test("$p.help prints usage (line 291+)", () => {
2375
+ test('$p.help prints usage (line 291+)', () => {
2376
2376
  installDevTools()
2377
2377
  const p = (window as unknown as Record<string, unknown>).$p as Record<
2378
2378
  string,
2379
2379
  (...args: unknown[]) => unknown
2380
2380
  >
2381
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {})
2381
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
2382
2382
  p.help?.()
2383
2383
  expect(logSpy).toHaveBeenCalled()
2384
2384
  logSpy.mockRestore()
@@ -2387,84 +2387,84 @@ describe("devtools.ts — $p console helper branches", () => {
2387
2387
 
2388
2388
  // ─── nodes.ts — keyed list LIS reorder branches ─────────────────────────────
2389
2389
 
2390
- describe("nodes.ts — keyed list LIS reorder", () => {
2391
- test("mountFor LIS fallback — reverse with size change (lines 536-578)", () => {
2392
- const el = document.createElement("div")
2390
+ describe('nodes.ts — keyed list LIS reorder', () => {
2391
+ test('mountFor LIS fallback — reverse with size change (lines 536-578)', () => {
2392
+ const el = document.createElement('div')
2393
2393
  document.body.appendChild(el)
2394
2394
 
2395
2395
  type Item = { id: number; label: string }
2396
2396
  const items = signal<Item[]>([
2397
- { id: 1, label: "a" },
2398
- { id: 2, label: "b" },
2399
- { id: 3, label: "c" },
2400
- { id: 4, label: "d" },
2401
- { id: 5, label: "e" },
2402
- { id: 6, label: "f" },
2403
- { id: 7, label: "g" },
2404
- { id: 8, label: "h" },
2405
- { id: 9, label: "i" },
2406
- { id: 10, label: "j" },
2397
+ { id: 1, label: 'a' },
2398
+ { id: 2, label: 'b' },
2399
+ { id: 3, label: 'c' },
2400
+ { id: 4, label: 'd' },
2401
+ { id: 5, label: 'e' },
2402
+ { id: 6, label: 'f' },
2403
+ { id: 7, label: 'g' },
2404
+ { id: 8, label: 'h' },
2405
+ { id: 9, label: 'i' },
2406
+ { id: 10, label: 'j' },
2407
2407
  ])
2408
2408
 
2409
2409
  mount(
2410
2410
  h(For, {
2411
2411
  each: items,
2412
2412
  by: (item: Item) => item.id,
2413
- children: (item: Item) => h("span", null, item.label),
2413
+ children: (item: Item) => h('span', null, item.label),
2414
2414
  }),
2415
2415
  el,
2416
2416
  )
2417
- expect(el.textContent).toBe("abcdefghij")
2417
+ expect(el.textContent).toBe('abcdefghij')
2418
2418
 
2419
2419
  // Reverse + add new item = size change → hits LIS fallback (not small-k)
2420
2420
  items.set([
2421
- { id: 10, label: "j" },
2422
- { id: 9, label: "i" },
2423
- { id: 8, label: "h" },
2424
- { id: 7, label: "g" },
2425
- { id: 6, label: "f" },
2426
- { id: 5, label: "e" },
2427
- { id: 4, label: "d" },
2428
- { id: 3, label: "c" },
2429
- { id: 2, label: "b" },
2430
- { id: 1, label: "a" },
2431
- { id: 11, label: "k" },
2421
+ { id: 10, label: 'j' },
2422
+ { id: 9, label: 'i' },
2423
+ { id: 8, label: 'h' },
2424
+ { id: 7, label: 'g' },
2425
+ { id: 6, label: 'f' },
2426
+ { id: 5, label: 'e' },
2427
+ { id: 4, label: 'd' },
2428
+ { id: 3, label: 'c' },
2429
+ { id: 2, label: 'b' },
2430
+ { id: 1, label: 'a' },
2431
+ { id: 11, label: 'k' },
2432
2432
  ])
2433
- expect(el.textContent).toBe("jihgfedcbak")
2433
+ expect(el.textContent).toBe('jihgfedcbak')
2434
2434
 
2435
2435
  el.remove()
2436
2436
  })
2437
2437
 
2438
- test("mountFor smallKPlace — few items swapped (lines 609-646)", () => {
2439
- const el = document.createElement("div")
2438
+ test('mountFor smallKPlace — few items swapped (lines 609-646)', () => {
2439
+ const el = document.createElement('div')
2440
2440
  document.body.appendChild(el)
2441
2441
 
2442
2442
  type Item = { id: number; label: string }
2443
2443
  const items = signal<Item[]>([
2444
- { id: 1, label: "a" },
2445
- { id: 2, label: "b" },
2446
- { id: 3, label: "c" },
2447
- { id: 4, label: "d" },
2444
+ { id: 1, label: 'a' },
2445
+ { id: 2, label: 'b' },
2446
+ { id: 3, label: 'c' },
2447
+ { id: 4, label: 'd' },
2448
2448
  ])
2449
2449
 
2450
2450
  mount(
2451
2451
  h(For, {
2452
2452
  each: items,
2453
2453
  by: (item: Item) => item.id,
2454
- children: (item: Item) => h("span", null, item.label),
2454
+ children: (item: Item) => h('span', null, item.label),
2455
2455
  }),
2456
2456
  el,
2457
2457
  )
2458
- expect(el.textContent).toBe("abcd")
2458
+ expect(el.textContent).toBe('abcd')
2459
2459
 
2460
2460
  // Swap 2 items — triggers small-k path (≤ SMALL_K diffs)
2461
2461
  items.set([
2462
- { id: 1, label: "a" },
2463
- { id: 4, label: "d" },
2464
- { id: 3, label: "c" },
2465
- { id: 2, label: "b" },
2462
+ { id: 1, label: 'a' },
2463
+ { id: 4, label: 'd' },
2464
+ { id: 3, label: 'c' },
2465
+ { id: 2, label: 'b' },
2466
2466
  ])
2467
- expect(el.textContent).toBe("adcb")
2467
+ expect(el.textContent).toBe('adcb')
2468
2468
 
2469
2469
  el.remove()
2470
2470
  })