@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.
@@ -1,44 +1,44 @@
1
- import { signal } from "@pyreon/reactivity"
2
- import { DELEGATED_EVENTS, delegatedPropName, setupDelegation } from "../delegate"
3
- import { applyProp, applyProps, sanitizeHtml, setSanitizer } from "../props"
1
+ import { signal } from '@pyreon/reactivity'
2
+ import { DELEGATED_EVENTS, delegatedPropName, setupDelegation } from '../delegate'
3
+ import { applyProp, applyProps, sanitizeHtml, setSanitizer } from '../props'
4
4
 
5
5
  // ─── applyProps ──────────────────────────────────────────────────────────────
6
6
 
7
- describe("applyProps", () => {
8
- test("skips key, ref, and children props", () => {
9
- const el = document.createElement("div")
7
+ describe('applyProps', () => {
8
+ test('skips key, ref, and children props', () => {
9
+ const el = document.createElement('div')
10
10
  const cleanup = applyProps(el, {
11
- key: "k1",
11
+ key: 'k1',
12
12
  ref: { current: null },
13
- children: "text",
14
- id: "test",
13
+ children: 'text',
14
+ id: 'test',
15
15
  })
16
- expect(el.getAttribute("id")).toBe("test")
16
+ expect(el.getAttribute('id')).toBe('test')
17
17
  // key, ref, children should not appear as attributes
18
- expect(el.hasAttribute("key")).toBe(false)
19
- expect(el.hasAttribute("ref")).toBe(false)
20
- expect(el.hasAttribute("children")).toBe(false)
18
+ expect(el.hasAttribute('key')).toBe(false)
19
+ expect(el.hasAttribute('ref')).toBe(false)
20
+ expect(el.hasAttribute('children')).toBe(false)
21
21
  cleanup?.()
22
22
  })
23
23
 
24
- test("returns null when no props produce cleanup", () => {
25
- const el = document.createElement("div")
26
- const cleanup = applyProps(el, { id: "static", title: "hello" })
24
+ test('returns null when no props produce cleanup', () => {
25
+ const el = document.createElement('div')
26
+ const cleanup = applyProps(el, { id: 'static', title: 'hello' })
27
27
  expect(cleanup).toBeNull()
28
28
  })
29
29
 
30
- test("returns single cleanup when one prop needs it", () => {
31
- const el = document.createElement("div")
30
+ test('returns single cleanup when one prop needs it', () => {
31
+ const el = document.createElement('div')
32
32
  const cleanup = applyProps(el, { onClick: () => {} })
33
33
  expect(cleanup).not.toBeNull()
34
- expect(typeof cleanup).toBe("function")
34
+ expect(typeof cleanup).toBe('function')
35
35
  cleanup?.()
36
36
  })
37
37
 
38
- test("returns chained cleanup when multiple props need cleanup", () => {
39
- const el = document.createElement("div")
40
- const s1 = signal("a")
41
- const s2 = signal("b")
38
+ test('returns chained cleanup when multiple props need cleanup', () => {
39
+ const el = document.createElement('div')
40
+ const s1 = signal('a')
41
+ const s2 = signal('b')
42
42
  const cleanup = applyProps(el, {
43
43
  onClick: () => {},
44
44
  class: s1,
@@ -48,223 +48,223 @@ describe("applyProps", () => {
48
48
  cleanup?.()
49
49
  })
50
50
 
51
- test("chains 3+ cleanups into array-based cleanup", () => {
52
- const el = document.createElement("div")
51
+ test('chains 3+ cleanups into array-based cleanup', () => {
52
+ const el = document.createElement('div')
53
53
  const cleanup = applyProps(el, {
54
54
  onClick: () => {},
55
55
  onInput: () => {},
56
- class: signal("x"),
56
+ class: signal('x'),
57
57
  })
58
- expect(typeof cleanup).toBe("function")
58
+ expect(typeof cleanup).toBe('function')
59
59
  cleanup?.()
60
60
  })
61
61
  })
62
62
 
63
63
  // ─── applyProp — style ────────────────────────────────────────────────────────
64
64
 
65
- describe("applyProp — style", () => {
66
- test("applies style as string via cssText", () => {
67
- const el = document.createElement("div")
68
- applyProp(el, "style", "color: red; font-size: 14px")
69
- expect(el.style.cssText).toContain("color")
65
+ describe('applyProp — style', () => {
66
+ test('applies style as string via cssText', () => {
67
+ const el = document.createElement('div')
68
+ applyProp(el, 'style', 'color: red; font-size: 14px')
69
+ expect(el.style.cssText).toContain('color')
70
70
  })
71
71
 
72
- test("applies style as object with camelCase properties", () => {
73
- const el = document.createElement("div")
74
- applyProp(el, "style", { fontSize: "14px", color: "blue" })
72
+ test('applies style as object with camelCase properties', () => {
73
+ const el = document.createElement('div')
74
+ applyProp(el, 'style', { fontSize: '14px', color: 'blue' })
75
75
  // Check that setProperty was called (kebab-case conversion)
76
- expect(el.style.getPropertyValue("font-size") || el.style.fontSize).toBeTruthy()
76
+ expect(el.style.getPropertyValue('font-size') || el.style.fontSize).toBeTruthy()
77
77
  })
78
78
 
79
- test("applies style with CSS custom properties (--var)", () => {
80
- const el = document.createElement("div")
81
- applyProp(el, "style", { "--main-color": "red" })
82
- expect(el.style.getPropertyValue("--main-color")).toBe("red")
79
+ test('applies style with CSS custom properties (--var)', () => {
80
+ const el = document.createElement('div')
81
+ applyProp(el, 'style', { '--main-color': 'red' })
82
+ expect(el.style.getPropertyValue('--main-color')).toBe('red')
83
83
  })
84
84
 
85
- test("ignores null/undefined style", () => {
86
- const el = document.createElement("div")
85
+ test('ignores null/undefined style', () => {
86
+ const el = document.createElement('div')
87
87
  // null style removes the attribute
88
- applyProp(el, "style", null)
89
- expect(el.hasAttribute("style")).toBe(false)
88
+ applyProp(el, 'style', null)
89
+ expect(el.hasAttribute('style')).toBe(false)
90
90
  })
91
91
  })
92
92
 
93
93
  // ─── applyProp — class ───────────────────────────────────────────────────────
94
94
 
95
- describe("applyProp — class", () => {
96
- test("applies class as string", () => {
97
- const el = document.createElement("div")
98
- applyProp(el, "class", "foo bar")
99
- expect(el.getAttribute("class")).toBe("foo bar")
95
+ describe('applyProp — class', () => {
96
+ test('applies class as string', () => {
97
+ const el = document.createElement('div')
98
+ applyProp(el, 'class', 'foo bar')
99
+ expect(el.getAttribute('class')).toBe('foo bar')
100
100
  })
101
101
 
102
- test("applies class as array", () => {
103
- const el = document.createElement("div")
104
- applyProp(el, "class", ["foo", "bar"])
105
- expect(el.getAttribute("class")).toBe("foo bar")
102
+ test('applies class as array', () => {
103
+ const el = document.createElement('div')
104
+ applyProp(el, 'class', ['foo', 'bar'])
105
+ expect(el.getAttribute('class')).toBe('foo bar')
106
106
  })
107
107
 
108
- test("applies class as object (conditionals)", () => {
109
- const el = document.createElement("div")
110
- applyProp(el, "class", { active: true, disabled: false, highlight: true })
111
- const cls = el.getAttribute("class") ?? ""
112
- expect(cls).toContain("active")
113
- expect(cls).toContain("highlight")
114
- expect(cls).not.toContain("disabled")
108
+ test('applies class as object (conditionals)', () => {
109
+ const el = document.createElement('div')
110
+ applyProp(el, 'class', { active: true, disabled: false, highlight: true })
111
+ const cls = el.getAttribute('class') ?? ''
112
+ expect(cls).toContain('active')
113
+ expect(cls).toContain('highlight')
114
+ expect(cls).not.toContain('disabled')
115
115
  })
116
116
 
117
- test("applies className as alias for class", () => {
118
- const el = document.createElement("div")
119
- applyProp(el, "className", "my-class")
120
- expect(el.getAttribute("class")).toBe("my-class")
117
+ test('applies className as alias for class', () => {
118
+ const el = document.createElement('div')
119
+ applyProp(el, 'className', 'my-class')
120
+ expect(el.getAttribute('class')).toBe('my-class')
121
121
  })
122
122
 
123
- test("sets empty class attribute when value resolves to empty", () => {
124
- const el = document.createElement("div")
125
- applyProp(el, "class", "")
126
- expect(el.getAttribute("class")).toBe("")
123
+ test('sets empty class attribute when value resolves to empty', () => {
124
+ const el = document.createElement('div')
125
+ applyProp(el, 'class', '')
126
+ expect(el.getAttribute('class')).toBe('')
127
127
  })
128
128
  })
129
129
 
130
130
  // ─── applyProp — events ──────────────────────────────────────────────────────
131
131
 
132
- describe("applyProp — events", () => {
133
- test("adds non-delegated event listener and returns cleanup", () => {
134
- const el = document.createElement("div")
132
+ describe('applyProp — events', () => {
133
+ test('adds non-delegated event listener and returns cleanup', () => {
134
+ const el = document.createElement('div')
135
135
  const handler = vi.fn()
136
136
  // onScroll is non-delegated (scroll doesn't bubble)
137
- const cleanup = applyProp(el, "onScroll", handler)
137
+ const cleanup = applyProp(el, 'onScroll', handler)
138
138
  expect(cleanup).not.toBeNull()
139
139
  // Dispatch scroll event — non-delegated events use addEventListener directly
140
- el.dispatchEvent(new Event("scroll"))
140
+ el.dispatchEvent(new Event('scroll'))
141
141
  expect(handler).toHaveBeenCalled()
142
142
  cleanup?.()
143
143
  })
144
144
 
145
- test("adds delegated event via expando property", () => {
146
- const el = document.createElement("div")
145
+ test('adds delegated event via expando property', () => {
146
+ const el = document.createElement('div')
147
147
  const handler = vi.fn()
148
- const cleanup = applyProp(el, "onClick", handler)
148
+ const cleanup = applyProp(el, 'onClick', handler)
149
149
  expect(cleanup).not.toBeNull()
150
150
  // Check that the delegated expando property is set
151
- const prop = delegatedPropName("click")
152
- expect(typeof (el as unknown as Record<string, unknown>)[prop]).toBe("function")
151
+ const prop = delegatedPropName('click')
152
+ expect(typeof (el as unknown as Record<string, unknown>)[prop]).toBe('function')
153
153
  cleanup?.()
154
154
  // After cleanup, expando should be undefined
155
155
  expect((el as unknown as Record<string, unknown>)[prop]).toBeUndefined()
156
156
  })
157
157
 
158
- test("warns on non-function event handler in dev mode", () => {
159
- const el = document.createElement("div")
160
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
161
- const cleanup = applyProp(el, "onClick", "not-a-function")
162
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("non-function"))
158
+ test('warns on non-function event handler in dev mode', () => {
159
+ const el = document.createElement('div')
160
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
161
+ const cleanup = applyProp(el, 'onClick', 'not-a-function')
162
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('non-function'))
163
163
  expect(cleanup).toBeNull()
164
164
  warnSpy.mockRestore()
165
165
  })
166
166
 
167
- test("cleanup removes non-delegated event listener", () => {
168
- const el = document.createElement("div")
167
+ test('cleanup removes non-delegated event listener', () => {
168
+ const el = document.createElement('div')
169
169
  const handler = vi.fn()
170
- const cleanup = applyProp(el, "onScroll", handler)
170
+ const cleanup = applyProp(el, 'onScroll', handler)
171
171
  cleanup?.()
172
- el.dispatchEvent(new Event("scroll"))
172
+ el.dispatchEvent(new Event('scroll'))
173
173
  expect(handler).not.toHaveBeenCalled()
174
174
  })
175
175
  })
176
176
 
177
177
  // ─── applyProp — reactive (function) values ──────────────────────────────────
178
178
 
179
- describe("applyProp — reactive values", () => {
180
- test("wraps function value in renderEffect", () => {
181
- const el = document.createElement("div")
182
- const s = signal("hello")
183
- const cleanup = applyProp(el, "title", () => s())
184
- expect(el.getAttribute("title")).toBe("hello")
185
- s.set("world")
186
- expect(el.getAttribute("title")).toBe("world")
179
+ describe('applyProp — reactive values', () => {
180
+ test('wraps function value in renderEffect', () => {
181
+ const el = document.createElement('div')
182
+ const s = signal('hello')
183
+ const cleanup = applyProp(el, 'title', () => s())
184
+ expect(el.getAttribute('title')).toBe('hello')
185
+ s.set('world')
186
+ expect(el.getAttribute('title')).toBe('world')
187
187
  cleanup?.()
188
188
  })
189
189
 
190
- test("stops tracking after disposal", () => {
191
- const el = document.createElement("div")
192
- const s = signal("a")
193
- const cleanup = applyProp(el, "title", () => s())
190
+ test('stops tracking after disposal', () => {
191
+ const el = document.createElement('div')
192
+ const s = signal('a')
193
+ const cleanup = applyProp(el, 'title', () => s())
194
194
  cleanup?.()
195
- s.set("b")
196
- expect(el.getAttribute("title")).toBe("a")
195
+ s.set('b')
196
+ expect(el.getAttribute('title')).toBe('a')
197
197
  })
198
198
  })
199
199
 
200
200
  // ─── applyProp — static values ───────────────────────────────────────────────
201
201
 
202
- describe("applyProp — static values", () => {
203
- test("sets string attribute", () => {
204
- const el = document.createElement("div")
205
- applyProp(el, "data-testid", "hello")
206
- expect(el.getAttribute("data-testid")).toBe("hello")
202
+ describe('applyProp — static values', () => {
203
+ test('sets string attribute', () => {
204
+ const el = document.createElement('div')
205
+ applyProp(el, 'data-testid', 'hello')
206
+ expect(el.getAttribute('data-testid')).toBe('hello')
207
207
  })
208
208
 
209
- test("removes attribute when value is null", () => {
210
- const el = document.createElement("div")
211
- el.setAttribute("data-x", "val")
212
- applyProp(el, "data-x", null)
213
- expect(el.hasAttribute("data-x")).toBe(false)
209
+ test('removes attribute when value is null', () => {
210
+ const el = document.createElement('div')
211
+ el.setAttribute('data-x', 'val')
212
+ applyProp(el, 'data-x', null)
213
+ expect(el.hasAttribute('data-x')).toBe(false)
214
214
  })
215
215
 
216
- test("removes attribute when value is undefined", () => {
217
- const el = document.createElement("div")
218
- el.setAttribute("data-x", "val")
219
- applyProp(el, "data-x", undefined)
220
- expect(el.hasAttribute("data-x")).toBe(false)
216
+ test('removes attribute when value is undefined', () => {
217
+ const el = document.createElement('div')
218
+ el.setAttribute('data-x', 'val')
219
+ applyProp(el, 'data-x', undefined)
220
+ expect(el.hasAttribute('data-x')).toBe(false)
221
221
  })
222
222
 
223
- test("sets boolean true as empty attribute", () => {
224
- const el = document.createElement("input") as HTMLInputElement
225
- applyProp(el, "disabled", true)
226
- expect(el.hasAttribute("disabled")).toBe(true)
227
- expect(el.getAttribute("disabled")).toBe("")
223
+ test('sets boolean true as empty attribute', () => {
224
+ const el = document.createElement('input') as HTMLInputElement
225
+ applyProp(el, 'disabled', true)
226
+ expect(el.hasAttribute('disabled')).toBe(true)
227
+ expect(el.getAttribute('disabled')).toBe('')
228
228
  })
229
229
 
230
- test("removes attribute for boolean false", () => {
231
- const el = document.createElement("input") as HTMLInputElement
232
- el.setAttribute("disabled", "")
233
- applyProp(el, "disabled", false)
234
- expect(el.hasAttribute("disabled")).toBe(false)
230
+ test('removes attribute for boolean false', () => {
231
+ const el = document.createElement('input') as HTMLInputElement
232
+ el.setAttribute('disabled', '')
233
+ applyProp(el, 'disabled', false)
234
+ expect(el.hasAttribute('disabled')).toBe(false)
235
235
  })
236
236
 
237
- test("sets DOM property directly when key exists on element", () => {
238
- const el = document.createElement("input") as HTMLInputElement
239
- applyProp(el, "value", "hello")
240
- expect(el.value).toBe("hello")
237
+ test('sets DOM property directly when key exists on element', () => {
238
+ const el = document.createElement('input') as HTMLInputElement
239
+ applyProp(el, 'value', 'hello')
240
+ expect(el.value).toBe('hello')
241
241
  })
242
242
 
243
- test("falls back to setAttribute for unknown attributes", () => {
244
- const el = document.createElement("div")
245
- applyProp(el, "data-custom", 42)
246
- expect(el.getAttribute("data-custom")).toBe("42")
243
+ test('falls back to setAttribute for unknown attributes', () => {
244
+ const el = document.createElement('div')
245
+ applyProp(el, 'data-custom', 42)
246
+ expect(el.getAttribute('data-custom')).toBe('42')
247
247
  })
248
248
  })
249
249
 
250
250
  // ─── applyProp — innerHTML / dangerouslySetInnerHTML ─────────────────────────
251
251
 
252
- describe("applyProp — innerHTML", () => {
253
- test("innerHTML is sanitized", () => {
254
- const el = document.createElement("div")
255
- applyProp(el, "innerHTML", '<b>bold</b><script>alert("xss")</script>')
252
+ describe('applyProp — innerHTML', () => {
253
+ test('innerHTML is sanitized', () => {
254
+ const el = document.createElement('div')
255
+ applyProp(el, 'innerHTML', '<b>bold</b><script>alert("xss")</script>')
256
256
  // Script tag should be stripped by sanitizer
257
- expect(el.innerHTML).toContain("<b>bold</b>")
258
- expect(el.innerHTML).not.toContain("<script>")
257
+ expect(el.innerHTML).toContain('<b>bold</b>')
258
+ expect(el.innerHTML).not.toContain('<script>')
259
259
  })
260
260
 
261
- test("dangerouslySetInnerHTML bypasses sanitization", () => {
262
- const el = document.createElement("div")
263
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
264
- applyProp(el, "dangerouslySetInnerHTML", { __html: "<em>raw</em>" })
265
- expect(el.innerHTML).toBe("<em>raw</em>")
261
+ test('dangerouslySetInnerHTML bypasses sanitization', () => {
262
+ const el = document.createElement('div')
263
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
264
+ applyProp(el, 'dangerouslySetInnerHTML', { __html: '<em>raw</em>' })
265
+ expect(el.innerHTML).toBe('<em>raw</em>')
266
266
  expect(warnSpy).toHaveBeenCalledWith(
267
- expect.stringContaining("dangerouslySetInnerHTML bypasses sanitization"),
267
+ expect.stringContaining('dangerouslySetInnerHTML bypasses sanitization'),
268
268
  )
269
269
  warnSpy.mockRestore()
270
270
  })
@@ -272,101 +272,101 @@ describe("applyProp — innerHTML", () => {
272
272
 
273
273
  // ─── applyProp — URL safety ──────────────────────────────────────────────────
274
274
 
275
- describe("applyProp — URL safety", () => {
276
- test("blocks javascript: in href", () => {
277
- const el = document.createElement("a")
278
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
279
- applyProp(el, "href", "javascript:alert(1)")
280
- expect(el.hasAttribute("href")).toBe(false)
275
+ describe('applyProp — URL safety', () => {
276
+ test('blocks javascript: in href', () => {
277
+ const el = document.createElement('a')
278
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
279
+ applyProp(el, 'href', 'javascript:alert(1)')
280
+ expect(el.hasAttribute('href')).toBe(false)
281
281
  warnSpy.mockRestore()
282
282
  })
283
283
 
284
- test("blocks data: in src", () => {
285
- const el = document.createElement("img")
286
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
287
- applyProp(el, "src", "data:text/html,<script>alert(1)</script>")
288
- expect(el.hasAttribute("src")).toBe(false)
284
+ test('blocks data: in src', () => {
285
+ const el = document.createElement('img')
286
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
287
+ applyProp(el, 'src', 'data:text/html,<script>alert(1)</script>')
288
+ expect(el.hasAttribute('src')).toBe(false)
289
289
  warnSpy.mockRestore()
290
290
  })
291
291
 
292
- test("allows safe URLs", () => {
293
- const el = document.createElement("a")
294
- applyProp(el, "href", "https://example.com")
295
- expect(el.getAttribute("href")).toBe("https://example.com")
292
+ test('allows safe URLs', () => {
293
+ const el = document.createElement('a')
294
+ applyProp(el, 'href', 'https://example.com')
295
+ expect(el.getAttribute('href')).toBe('https://example.com')
296
296
  })
297
297
  })
298
298
 
299
299
  // ─── sanitizeHtml ────────────────────────────────────────────────────────────
300
300
 
301
- describe("sanitizeHtml", () => {
302
- test("strips script tags", () => {
301
+ describe('sanitizeHtml', () => {
302
+ test('strips script tags', () => {
303
303
  const result = sanitizeHtml('<div>hello</div><script>alert("xss")</script>')
304
- expect(result).not.toContain("<script>")
305
- expect(result).toContain("hello")
304
+ expect(result).not.toContain('<script>')
305
+ expect(result).toContain('hello')
306
306
  })
307
307
 
308
- test("strips event handler attributes", () => {
308
+ test('strips event handler attributes', () => {
309
309
  const result = sanitizeHtml('<div onclick="alert(1)">text</div>')
310
- expect(result).not.toContain("onclick")
311
- expect(result).toContain("text")
310
+ expect(result).not.toContain('onclick')
311
+ expect(result).toContain('text')
312
312
  })
313
313
 
314
- test("strips javascript: URLs from href", () => {
314
+ test('strips javascript: URLs from href', () => {
315
315
  const result = sanitizeHtml('<a href="javascript:alert(1)">click</a>')
316
- expect(result).not.toContain("javascript:")
316
+ expect(result).not.toContain('javascript:')
317
317
  })
318
318
 
319
- test("allows safe HTML tags", () => {
320
- const result = sanitizeHtml("<b>bold</b> <em>italic</em> <p>paragraph</p>")
321
- expect(result).toContain("<b>bold</b>")
322
- expect(result).toContain("<em>italic</em>")
323
- expect(result).toContain("<p>paragraph</p>")
319
+ test('allows safe HTML tags', () => {
320
+ const result = sanitizeHtml('<b>bold</b> <em>italic</em> <p>paragraph</p>')
321
+ expect(result).toContain('<b>bold</b>')
322
+ expect(result).toContain('<em>italic</em>')
323
+ expect(result).toContain('<p>paragraph</p>')
324
324
  })
325
325
 
326
- test("uses custom sanitizer when set", () => {
327
- const custom = vi.fn((html: string) => html.replace(/<[^>]+>/g, ""))
326
+ test('uses custom sanitizer when set', () => {
327
+ const custom = vi.fn((html: string) => html.replace(/<[^>]+>/g, ''))
328
328
  setSanitizer(custom)
329
- const result = sanitizeHtml("<div>test</div>")
330
- expect(custom).toHaveBeenCalledWith("<div>test</div>")
331
- expect(result).toBe("test")
329
+ const result = sanitizeHtml('<div>test</div>')
330
+ expect(custom).toHaveBeenCalledWith('<div>test</div>')
331
+ expect(result).toBe('test')
332
332
  setSanitizer(null)
333
333
  })
334
334
  })
335
335
 
336
336
  // ─── delegate.ts ─────────────────────────────────────────────────────────────
337
337
 
338
- describe("delegate", () => {
339
- test("DELEGATED_EVENTS contains common bubbling events", () => {
340
- expect(DELEGATED_EVENTS.has("click")).toBe(true)
341
- expect(DELEGATED_EVENTS.has("input")).toBe(true)
342
- expect(DELEGATED_EVENTS.has("keydown")).toBe(true)
343
- expect(DELEGATED_EVENTS.has("submit")).toBe(true)
338
+ describe('delegate', () => {
339
+ test('DELEGATED_EVENTS contains common bubbling events', () => {
340
+ expect(DELEGATED_EVENTS.has('click')).toBe(true)
341
+ expect(DELEGATED_EVENTS.has('input')).toBe(true)
342
+ expect(DELEGATED_EVENTS.has('keydown')).toBe(true)
343
+ expect(DELEGATED_EVENTS.has('submit')).toBe(true)
344
344
  })
345
345
 
346
- test("DELEGATED_EVENTS does not contain non-bubbling events", () => {
347
- expect(DELEGATED_EVENTS.has("focus")).toBe(false)
348
- expect(DELEGATED_EVENTS.has("blur")).toBe(false)
349
- expect(DELEGATED_EVENTS.has("mouseenter")).toBe(false)
350
- expect(DELEGATED_EVENTS.has("mouseleave")).toBe(false)
351
- expect(DELEGATED_EVENTS.has("load")).toBe(false)
352
- expect(DELEGATED_EVENTS.has("scroll")).toBe(false)
346
+ test('DELEGATED_EVENTS does not contain non-bubbling events', () => {
347
+ expect(DELEGATED_EVENTS.has('focus')).toBe(false)
348
+ expect(DELEGATED_EVENTS.has('blur')).toBe(false)
349
+ expect(DELEGATED_EVENTS.has('mouseenter')).toBe(false)
350
+ expect(DELEGATED_EVENTS.has('mouseleave')).toBe(false)
351
+ expect(DELEGATED_EVENTS.has('load')).toBe(false)
352
+ expect(DELEGATED_EVENTS.has('scroll')).toBe(false)
353
353
  })
354
354
 
355
- test("delegatedPropName returns __ev_{eventName}", () => {
356
- expect(delegatedPropName("click")).toBe("__ev_click")
357
- expect(delegatedPropName("input")).toBe("__ev_input")
355
+ test('delegatedPropName returns __ev_{eventName}', () => {
356
+ expect(delegatedPropName('click')).toBe('__ev_click')
357
+ expect(delegatedPropName('input')).toBe('__ev_input')
358
358
  })
359
359
 
360
- test("setupDelegation installs listeners and dispatches to expandos", () => {
361
- const container = document.createElement("div")
360
+ test('setupDelegation installs listeners and dispatches to expandos', () => {
361
+ const container = document.createElement('div')
362
362
  document.body.appendChild(container)
363
363
  setupDelegation(container)
364
364
 
365
- const child = document.createElement("button")
365
+ const child = document.createElement('button')
366
366
  container.appendChild(child)
367
367
 
368
368
  const handler = vi.fn()
369
- const prop = delegatedPropName("click")
369
+ const prop = delegatedPropName('click')
370
370
  ;(child as unknown as Record<string, unknown>)[prop] = handler
371
371
 
372
372
  child.click()
@@ -375,18 +375,18 @@ describe("delegate", () => {
375
375
  container.remove()
376
376
  })
377
377
 
378
- test("setupDelegation is idempotent (safe to call twice)", () => {
379
- const container = document.createElement("div")
378
+ test('setupDelegation is idempotent (safe to call twice)', () => {
379
+ const container = document.createElement('div')
380
380
  document.body.appendChild(container)
381
381
  // Should not throw when called twice
382
382
  setupDelegation(container)
383
383
  setupDelegation(container)
384
384
 
385
- const child = document.createElement("span")
385
+ const child = document.createElement('span')
386
386
  container.appendChild(child)
387
387
 
388
388
  let callCount = 0
389
- const prop = delegatedPropName("click")
389
+ const prop = delegatedPropName('click')
390
390
  ;(child as unknown as Record<string, unknown>)[prop] = () => {
391
391
  callCount++
392
392
  }
@@ -398,19 +398,19 @@ describe("delegate", () => {
398
398
  container.remove()
399
399
  })
400
400
 
401
- test("delegation walks up the DOM tree (ancestor handlers fire)", () => {
402
- const container = document.createElement("div")
401
+ test('delegation walks up the DOM tree (ancestor handlers fire)', () => {
402
+ const container = document.createElement('div')
403
403
  document.body.appendChild(container)
404
404
  setupDelegation(container)
405
405
 
406
- const parent = document.createElement("div")
407
- const child = document.createElement("span")
406
+ const parent = document.createElement('div')
407
+ const child = document.createElement('span')
408
408
  parent.appendChild(child)
409
409
  container.appendChild(parent)
410
410
 
411
411
  const parentHandler = vi.fn()
412
412
  const childHandler = vi.fn()
413
- const prop = delegatedPropName("click")
413
+ const prop = delegatedPropName('click')
414
414
  ;(parent as unknown as Record<string, unknown>)[prop] = parentHandler
415
415
  ;(child as unknown as Record<string, unknown>)[prop] = childHandler
416
416
 
@@ -421,19 +421,19 @@ describe("delegate", () => {
421
421
  container.remove()
422
422
  })
423
423
 
424
- test("delegation respects stopPropagation", () => {
425
- const container = document.createElement("div")
424
+ test('delegation respects stopPropagation', () => {
425
+ const container = document.createElement('div')
426
426
  document.body.appendChild(container)
427
427
  setupDelegation(container)
428
428
 
429
- const parent = document.createElement("div")
430
- const child = document.createElement("span")
429
+ const parent = document.createElement('div')
430
+ const child = document.createElement('span')
431
431
  parent.appendChild(child)
432
432
  container.appendChild(parent)
433
433
 
434
434
  const parentHandler = vi.fn()
435
435
  const childHandler = vi.fn((e: Event) => e.stopPropagation())
436
- const prop = delegatedPropName("click")
436
+ const prop = delegatedPropName('click')
437
437
  ;(parent as unknown as Record<string, unknown>)[prop] = parentHandler
438
438
  ;(child as unknown as Record<string, unknown>)[prop] = childHandler
439
439
 
@@ -444,16 +444,16 @@ describe("delegate", () => {
444
444
  container.remove()
445
445
  })
446
446
 
447
- test("delegation skips non-function expando values", () => {
448
- const container = document.createElement("div")
447
+ test('delegation skips non-function expando values', () => {
448
+ const container = document.createElement('div')
449
449
  document.body.appendChild(container)
450
450
  setupDelegation(container)
451
451
 
452
- const child = document.createElement("span")
452
+ const child = document.createElement('span')
453
453
  container.appendChild(child)
454
454
 
455
- const prop = delegatedPropName("click")
456
- ;(child as unknown as Record<string, unknown>)[prop] = "not-a-function"
455
+ const prop = delegatedPropName('click')
456
+ ;(child as unknown as Record<string, unknown>)[prop] = 'not-a-function'
457
457
 
458
458
  // Should not throw
459
459
  expect(() => child.click()).not.toThrow()