@gjsify/dom-elements 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +31 -0
  2. package/lib/esm/attr.js +31 -0
  3. package/lib/esm/character-data.js +56 -0
  4. package/lib/esm/comment.js +21 -0
  5. package/lib/esm/document-fragment.js +112 -0
  6. package/lib/esm/document.js +83 -0
  7. package/lib/esm/dom-token-list.js +109 -0
  8. package/lib/esm/element.js +237 -0
  9. package/lib/esm/html-canvas-element.js +65 -0
  10. package/lib/esm/html-element.js +346 -0
  11. package/lib/esm/html-image-element.js +184 -0
  12. package/lib/esm/image.js +23 -0
  13. package/lib/esm/index.js +112 -0
  14. package/lib/esm/intersection-observer.js +19 -0
  15. package/lib/esm/mutation-observer.js +14 -0
  16. package/lib/esm/named-node-map.js +124 -0
  17. package/lib/esm/namespace-uri.js +10 -0
  18. package/lib/esm/node-list.js +34 -0
  19. package/lib/esm/node-type.js +14 -0
  20. package/lib/esm/node.js +227 -0
  21. package/lib/esm/property-symbol.js +30 -0
  22. package/lib/esm/resize-observer.js +13 -0
  23. package/lib/esm/text.js +51 -0
  24. package/lib/esm/types/i-html-image-element.js +0 -0
  25. package/lib/esm/types/image-data.js +0 -0
  26. package/lib/esm/types/index.js +3 -0
  27. package/lib/esm/types/predefined-color-space.js +0 -0
  28. package/lib/types/attr.d.ts +22 -0
  29. package/lib/types/character-data.d.ts +24 -0
  30. package/lib/types/comment.d.ts +12 -0
  31. package/lib/types/document-fragment.d.ts +37 -0
  32. package/lib/types/document.d.ts +39 -0
  33. package/lib/types/dom-token-list.d.ts +30 -0
  34. package/lib/types/element.d.ts +58 -0
  35. package/lib/types/html-canvas-element.d.ts +40 -0
  36. package/lib/types/html-element.d.ts +119 -0
  37. package/lib/types/html-image-element.d.ts +65 -0
  38. package/lib/types/image.d.ts +17 -0
  39. package/lib/types/index.d.ts +21 -0
  40. package/lib/types/intersection-observer.d.ts +21 -0
  41. package/lib/types/mutation-observer.d.ts +24 -0
  42. package/lib/types/named-node-map.d.ts +31 -0
  43. package/lib/types/namespace-uri.d.ts +7 -0
  44. package/lib/types/node-list.d.ts +18 -0
  45. package/lib/types/node-type.d.ts +11 -0
  46. package/lib/types/node.d.ts +63 -0
  47. package/lib/types/property-symbol.d.ts +14 -0
  48. package/lib/types/resize-observer.d.ts +13 -0
  49. package/lib/types/text.d.ts +21 -0
  50. package/lib/types/types/i-html-image-element.d.ts +41 -0
  51. package/lib/types/types/image-data.d.ts +11 -0
  52. package/lib/types/types/index.d.ts +3 -0
  53. package/lib/types/types/predefined-color-space.d.ts +1 -0
  54. package/package.json +43 -0
  55. package/src/attr.ts +61 -0
  56. package/src/character-data.ts +79 -0
  57. package/src/comment.ts +31 -0
  58. package/src/document-fragment.ts +137 -0
  59. package/src/document.ts +93 -0
  60. package/src/dom-token-list.ts +140 -0
  61. package/src/element.ts +299 -0
  62. package/src/html-canvas-element.ts +81 -0
  63. package/src/html-element.ts +422 -0
  64. package/src/html-image-element.ts +242 -0
  65. package/src/image.ts +31 -0
  66. package/src/index.spec.ts +897 -0
  67. package/src/index.ts +95 -0
  68. package/src/intersection-observer.ts +42 -0
  69. package/src/mutation-observer.ts +39 -0
  70. package/src/named-node-map.ts +159 -0
  71. package/src/namespace-uri.ts +11 -0
  72. package/src/node-list.ts +52 -0
  73. package/src/node-type.ts +14 -0
  74. package/src/node.ts +250 -0
  75. package/src/property-symbol.ts +23 -0
  76. package/src/resize-observer.ts +28 -0
  77. package/src/test.mts +6 -0
  78. package/src/text.ts +67 -0
  79. package/src/types/i-html-image-element.ts +44 -0
  80. package/src/types/image-data.ts +12 -0
  81. package/src/types/index.ts +3 -0
  82. package/src/types/predefined-color-space.ts +1 -0
  83. package/tsconfig.json +37 -0
  84. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,897 @@
1
+ // Ported from refs/happy-dom/packages/happy-dom/test/nodes/
2
+ // Original: MIT license, Copyright (c) David Ortner (capricorn86)
3
+
4
+ import { describe, it, expect } from '@gjsify/unit';
5
+
6
+ import {
7
+ Node, Element, HTMLElement, NodeType, NamespaceURI, Attr, NamedNodeMap,
8
+ CharacterData, Text, Comment, DocumentFragment, DOMTokenList,
9
+ } from '@gjsify/dom-elements';
10
+ import { Event } from '@gjsify/dom-events';
11
+
12
+ export default async () => {
13
+ // -- Attr --
14
+
15
+ await describe('Attr', async () => {
16
+ await it('should store name and value', async () => {
17
+ const attr = new Attr('id', 'test');
18
+ expect(attr.name).toBe('id');
19
+ expect(attr.value).toBe('test');
20
+ expect(attr.localName).toBe('id');
21
+ expect(attr.namespaceURI).toBeNull();
22
+ expect(attr.prefix).toBeNull();
23
+ expect(attr.specified).toBe(true);
24
+ });
25
+
26
+ await it('should extract localName from prefixed name', async () => {
27
+ const attr = new Attr('xml:lang', 'en', 'http://www.w3.org/XML/1998/namespace', 'xml');
28
+ expect(attr.name).toBe('xml:lang');
29
+ expect(attr.localName).toBe('lang');
30
+ expect(attr.prefix).toBe('xml');
31
+ });
32
+
33
+ await it('should allow value mutation', async () => {
34
+ const attr = new Attr('class', 'old');
35
+ attr.value = 'new';
36
+ expect(attr.value).toBe('new');
37
+ });
38
+ });
39
+
40
+ // -- Node --
41
+
42
+ await describe('Node', async () => {
43
+ await it('should have correct node type constants', async () => {
44
+ expect(Node.ELEMENT_NODE).toBe(1);
45
+ expect(Node.TEXT_NODE).toBe(3);
46
+ expect(Node.COMMENT_NODE).toBe(8);
47
+ expect(Node.DOCUMENT_NODE).toBe(9);
48
+ expect(Node.DOCUMENT_FRAGMENT_NODE).toBe(11);
49
+ });
50
+
51
+ await it('should have instance node type constants', async () => {
52
+ const node = new Node();
53
+ expect(node.ELEMENT_NODE).toBe(1);
54
+ expect(node.TEXT_NODE).toBe(3);
55
+ });
56
+
57
+ await it('should appendChild and set parentNode', async () => {
58
+ const parent = new Node();
59
+ const child = new Node();
60
+ parent.appendChild(child);
61
+ expect(child.parentNode).toBe(parent);
62
+ expect(parent.hasChildNodes()).toBe(true);
63
+ expect(parent.childNodes.length).toBe(1);
64
+ expect(parent.firstChild).toBe(child);
65
+ expect(parent.lastChild).toBe(child);
66
+ });
67
+
68
+ await it('should removeChild and clear parentNode', async () => {
69
+ const parent = new Node();
70
+ const child = new Node();
71
+ parent.appendChild(child);
72
+ parent.removeChild(child);
73
+ expect(child.parentNode).toBeNull();
74
+ expect(parent.hasChildNodes()).toBe(false);
75
+ expect(parent.childNodes.length).toBe(0);
76
+ });
77
+
78
+ await it('should throw when removing a non-child', async () => {
79
+ const parent = new Node();
80
+ const stranger = new Node();
81
+ let threw = false;
82
+ try {
83
+ parent.removeChild(stranger);
84
+ } catch {
85
+ threw = true;
86
+ }
87
+ expect(threw).toBe(true);
88
+ });
89
+
90
+ await it('should insertBefore', async () => {
91
+ const parent = new Node();
92
+ const child1 = new Node();
93
+ const child2 = new Node();
94
+ const child3 = new Node();
95
+ parent.appendChild(child1);
96
+ parent.appendChild(child3);
97
+ parent.insertBefore(child2, child3);
98
+ expect(parent.childNodes.length).toBe(3);
99
+ expect(parent.childNodes.item(0)).toBe(child1);
100
+ expect(parent.childNodes.item(1)).toBe(child2);
101
+ expect(parent.childNodes.item(2)).toBe(child3);
102
+ });
103
+
104
+ await it('should insertBefore with null reference (appends)', async () => {
105
+ const parent = new Node();
106
+ const child = new Node();
107
+ parent.insertBefore(child, null);
108
+ expect(parent.firstChild).toBe(child);
109
+ });
110
+
111
+ await it('should replaceChild', async () => {
112
+ const parent = new Node();
113
+ const oldChild = new Node();
114
+ const newChild = new Node();
115
+ parent.appendChild(oldChild);
116
+ parent.replaceChild(newChild, oldChild);
117
+ expect(parent.firstChild).toBe(newChild);
118
+ expect(oldChild.parentNode).toBeNull();
119
+ });
120
+
121
+ await it('should navigate siblings', async () => {
122
+ const parent = new Node();
123
+ const a = new Node();
124
+ const b = new Node();
125
+ const c = new Node();
126
+ parent.appendChild(a);
127
+ parent.appendChild(b);
128
+ parent.appendChild(c);
129
+ expect(a.previousSibling).toBeNull();
130
+ expect(a.nextSibling).toBe(b);
131
+ expect(b.previousSibling).toBe(a);
132
+ expect(b.nextSibling).toBe(c);
133
+ expect(c.previousSibling).toBe(b);
134
+ expect(c.nextSibling).toBeNull();
135
+ });
136
+
137
+ await it('should contains()', async () => {
138
+ const parent = new Node();
139
+ const child = new Node();
140
+ const grandchild = new Node();
141
+ parent.appendChild(child);
142
+ child.appendChild(grandchild);
143
+ expect(parent.contains(child)).toBe(true);
144
+ expect(parent.contains(grandchild)).toBe(true);
145
+ expect(parent.contains(parent)).toBe(true);
146
+ expect(child.contains(parent)).toBe(false);
147
+ expect(parent.contains(null)).toBe(false);
148
+ });
149
+
150
+ await it('should getRootNode()', async () => {
151
+ const root = new Node();
152
+ const child = new Node();
153
+ const grandchild = new Node();
154
+ root.appendChild(child);
155
+ child.appendChild(grandchild);
156
+ expect(grandchild.getRootNode()).toBe(root);
157
+ expect(root.getRootNode()).toBe(root);
158
+ });
159
+
160
+ await it('should cloneNode shallow', async () => {
161
+ const parent = new Node();
162
+ const child = new Node();
163
+ parent.appendChild(child);
164
+ const clone = parent.cloneNode(false);
165
+ expect(clone.hasChildNodes()).toBe(false);
166
+ });
167
+
168
+ await it('should cloneNode deep', async () => {
169
+ const parent = new Node();
170
+ const child = new Node();
171
+ parent.appendChild(child);
172
+ const clone = parent.cloneNode(true);
173
+ expect(clone.hasChildNodes()).toBe(true);
174
+ expect(clone.firstChild).not.toBe(child);
175
+ });
176
+
177
+ await it('should move child when appending to new parent', async () => {
178
+ const parent1 = new Node();
179
+ const parent2 = new Node();
180
+ const child = new Node();
181
+ parent1.appendChild(child);
182
+ parent2.appendChild(child);
183
+ expect(parent1.hasChildNodes()).toBe(false);
184
+ expect(parent2.firstChild).toBe(child);
185
+ expect(child.parentNode).toBe(parent2);
186
+ });
187
+
188
+ await it('should return null for ownerDocument', async () => {
189
+ const node = new Node();
190
+ expect(node.ownerDocument).toBeNull();
191
+ });
192
+ });
193
+
194
+ // -- Element --
195
+
196
+ await describe('Element', async () => {
197
+ await it('should have ELEMENT_NODE type', async () => {
198
+ const el = new Element();
199
+ expect(el.nodeType).toBe(NodeType.ELEMENT_NODE);
200
+ });
201
+
202
+ await it('should set and get attributes', async () => {
203
+ const el = new Element();
204
+ el.setAttribute('id', 'test');
205
+ expect(el.getAttribute('id')).toBe('test');
206
+ expect(el.hasAttribute('id')).toBe(true);
207
+ });
208
+
209
+ await it('should remove attributes', async () => {
210
+ const el = new Element();
211
+ el.setAttribute('class', 'foo');
212
+ el.removeAttribute('class');
213
+ expect(el.getAttribute('class')).toBeNull();
214
+ expect(el.hasAttribute('class')).toBe(false);
215
+ });
216
+
217
+ await it('should set and get id', async () => {
218
+ const el = new Element();
219
+ el.id = 'myId';
220
+ expect(el.id).toBe('myId');
221
+ expect(el.getAttribute('id')).toBe('myId');
222
+ });
223
+
224
+ await it('should set and get className', async () => {
225
+ const el = new Element();
226
+ el.className = 'foo bar';
227
+ expect(el.className).toBe('foo bar');
228
+ expect(el.getAttribute('class')).toBe('foo bar');
229
+ });
230
+
231
+ await it('should setAttributeNS and getAttributeNS', async () => {
232
+ const el = new Element();
233
+ el.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:lang', 'en');
234
+ expect(el.getAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang')).toBe('en');
235
+ expect(el.hasAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang')).toBe(true);
236
+ });
237
+
238
+ await it('should removeAttributeNS', async () => {
239
+ const el = new Element();
240
+ el.setAttributeNS(null, 'data-foo', 'bar');
241
+ el.removeAttributeNS(null, 'data-foo');
242
+ expect(el.getAttributeNS(null, 'data-foo')).toBeNull();
243
+ });
244
+
245
+ await it('should toggleAttribute', async () => {
246
+ const el = new Element();
247
+ expect(el.toggleAttribute('hidden')).toBe(true);
248
+ expect(el.hasAttribute('hidden')).toBe(true);
249
+ expect(el.toggleAttribute('hidden')).toBe(false);
250
+ expect(el.hasAttribute('hidden')).toBe(false);
251
+ });
252
+
253
+ await it('should toggleAttribute with force', async () => {
254
+ const el = new Element();
255
+ expect(el.toggleAttribute('hidden', true)).toBe(true);
256
+ expect(el.hasAttribute('hidden')).toBe(true);
257
+ expect(el.toggleAttribute('hidden', true)).toBe(true);
258
+ expect(el.hasAttribute('hidden')).toBe(true);
259
+ expect(el.toggleAttribute('hidden', false)).toBe(false);
260
+ expect(el.hasAttribute('hidden')).toBe(false);
261
+ });
262
+
263
+ await it('should hasAttributes', async () => {
264
+ const el = new Element();
265
+ expect(el.hasAttributes()).toBe(false);
266
+ el.setAttribute('id', 'test');
267
+ expect(el.hasAttributes()).toBe(true);
268
+ });
269
+
270
+ await it('should have correct attributes.length', async () => {
271
+ const el = new Element();
272
+ expect(el.attributes.length).toBe(0);
273
+ el.setAttribute('id', 'test');
274
+ el.setAttribute('class', 'foo');
275
+ expect(el.attributes.length).toBe(2);
276
+ });
277
+
278
+ await it('should iterate attributes', async () => {
279
+ const el = new Element();
280
+ el.setAttribute('id', 'test');
281
+ el.setAttribute('class', 'foo');
282
+ const names: string[] = [];
283
+ for (const attr of el.attributes) {
284
+ names.push(attr.name);
285
+ }
286
+ expect(names.length).toBe(2);
287
+ expect(names).toContain('id');
288
+ expect(names).toContain('class');
289
+ });
290
+
291
+ await it('should track element children', async () => {
292
+ const parent = new Element();
293
+ const child1 = new Element();
294
+ const child2 = new Element();
295
+ parent.appendChild(child1);
296
+ parent.appendChild(child2);
297
+ expect(parent.children.length).toBe(2);
298
+ expect(parent.childElementCount).toBe(2);
299
+ expect(parent.firstElementChild).toBe(child1);
300
+ expect(parent.lastElementChild).toBe(child2);
301
+ });
302
+
303
+ await it('should navigate element siblings', async () => {
304
+ const parent = new Element();
305
+ const a = new Element();
306
+ const b = new Element();
307
+ parent.appendChild(a);
308
+ parent.appendChild(b);
309
+ expect(a.nextElementSibling).toBe(b);
310
+ expect(b.previousElementSibling).toBe(a);
311
+ expect(a.previousElementSibling).toBeNull();
312
+ expect(b.nextElementSibling).toBeNull();
313
+ });
314
+
315
+ await it('should clone with attributes', async () => {
316
+ const el = new Element();
317
+ el.setAttribute('id', 'original');
318
+ el.setAttribute('class', 'test');
319
+ const clone = el.cloneNode(false);
320
+ expect(clone.getAttribute('id')).toBe('original');
321
+ expect(clone.getAttribute('class')).toBe('test');
322
+ expect(clone).not.toBe(el);
323
+ });
324
+
325
+ await it('should getElementsByTagName', async () => {
326
+ const root = new Element();
327
+ const div = new Element();
328
+ div[Symbol.for ? Symbol.for('tagName') : 'tagName'] = 'DIV';
329
+ // Use internal symbol access via setting attribute approach
330
+ // Instead, test with the proper API
331
+ root.appendChild(div);
332
+ // getElementsByTagName works on tagName which is '' by default for Element
333
+ const results = root.getElementsByTagName('*');
334
+ expect(results.length).toBe(1);
335
+ });
336
+
337
+ await it('should dispatch events and call on* handlers', async () => {
338
+ const el = new Element();
339
+ let handlerCalled = false;
340
+ let listenerCalled = false;
341
+
342
+ el.addEventListener('click', () => {
343
+ listenerCalled = true;
344
+ });
345
+
346
+ el[Symbol.for ? Symbol.for('propertyEventListeners') : 'propertyEventListeners'] = new Map();
347
+ // Use the propertyEventListeners through proper API in HTMLElement
348
+ // For Element, test addEventListener only
349
+ el.dispatchEvent(new Event('click'));
350
+ expect(listenerCalled).toBe(true);
351
+ });
352
+ });
353
+
354
+ // -- HTMLElement --
355
+
356
+ await describe('HTMLElement', async () => {
357
+ await it('should be an instance of Element and Node', async () => {
358
+ const el = new HTMLElement();
359
+ expect(el instanceof Element).toBe(true);
360
+ expect(el instanceof Node).toBe(true);
361
+ });
362
+
363
+ await it('should have correct [Symbol.toStringTag]', async () => {
364
+ const el = new HTMLElement();
365
+ expect(Object.prototype.toString.call(el)).toBe('[object HTMLElement]');
366
+ });
367
+
368
+ await it('should get/set title', async () => {
369
+ const el = new HTMLElement();
370
+ expect(el.title).toBe('');
371
+ el.title = 'My Title';
372
+ expect(el.title).toBe('My Title');
373
+ expect(el.getAttribute('title')).toBe('My Title');
374
+ });
375
+
376
+ await it('should get/set lang', async () => {
377
+ const el = new HTMLElement();
378
+ expect(el.lang).toBe('');
379
+ el.lang = 'en';
380
+ expect(el.lang).toBe('en');
381
+ expect(el.getAttribute('lang')).toBe('en');
382
+ });
383
+
384
+ await it('should get/set dir', async () => {
385
+ const el = new HTMLElement();
386
+ expect(el.dir).toBe('');
387
+ el.dir = 'rtl';
388
+ expect(el.dir).toBe('rtl');
389
+ expect(el.getAttribute('dir')).toBe('rtl');
390
+ });
391
+
392
+ await it('should get/set hidden (boolean attribute)', async () => {
393
+ const el = new HTMLElement();
394
+ expect(el.hidden).toBe(false);
395
+ el.hidden = true;
396
+ expect(el.hidden).toBe(true);
397
+ expect(el.hasAttribute('hidden')).toBe(true);
398
+ el.hidden = false;
399
+ expect(el.hidden).toBe(false);
400
+ expect(el.hasAttribute('hidden')).toBe(false);
401
+ });
402
+
403
+ await it('should get/set tabIndex', async () => {
404
+ const el = new HTMLElement();
405
+ expect(el.tabIndex).toBe(-1);
406
+ el.tabIndex = 0;
407
+ expect(el.tabIndex).toBe(0);
408
+ expect(el.getAttribute('tabindex')).toBe('0');
409
+ });
410
+
411
+ await it('should get/set draggable', async () => {
412
+ const el = new HTMLElement();
413
+ expect(el.draggable).toBe(false);
414
+ el.draggable = true;
415
+ expect(el.draggable).toBe(true);
416
+ expect(el.getAttribute('draggable')).toBe('true');
417
+ });
418
+
419
+ await it('should get/set contentEditable', async () => {
420
+ const el = new HTMLElement();
421
+ expect(el.contentEditable).toBe('inherit');
422
+ el.contentEditable = 'true';
423
+ expect(el.contentEditable).toBe('true');
424
+ expect(el.isContentEditable).toBe(true);
425
+ el.contentEditable = 'false';
426
+ expect(el.contentEditable).toBe('false');
427
+ expect(el.isContentEditable).toBe(false);
428
+ el.contentEditable = 'inherit';
429
+ expect(el.contentEditable).toBe('inherit');
430
+ });
431
+
432
+ await it('should return 0 for layout properties', async () => {
433
+ const el = new HTMLElement();
434
+ expect(el.offsetHeight).toBe(0);
435
+ expect(el.offsetWidth).toBe(0);
436
+ expect(el.offsetLeft).toBe(0);
437
+ expect(el.offsetTop).toBe(0);
438
+ expect(el.clientHeight).toBe(0);
439
+ expect(el.clientWidth).toBe(0);
440
+ expect(el.clientLeft).toBe(0);
441
+ expect(el.clientTop).toBe(0);
442
+ expect(el.scrollHeight).toBe(0);
443
+ expect(el.scrollWidth).toBe(0);
444
+ expect(el.scrollTop).toBe(0);
445
+ expect(el.scrollLeft).toBe(0);
446
+ expect(el.offsetParent).toBeNull();
447
+ });
448
+
449
+ await it('should click() dispatch click event', async () => {
450
+ const el = new HTMLElement();
451
+ let clicked = false;
452
+ el.addEventListener('click', () => { clicked = true; });
453
+ el.click();
454
+ expect(clicked).toBe(true);
455
+ });
456
+
457
+ await it('should focus() dispatch focus event', async () => {
458
+ const el = new HTMLElement();
459
+ let focused = false;
460
+ el.addEventListener('focus', () => { focused = true; });
461
+ el.focus();
462
+ expect(focused).toBe(true);
463
+ });
464
+
465
+ await it('should blur() dispatch blur event', async () => {
466
+ const el = new HTMLElement();
467
+ let blurred = false;
468
+ el.addEventListener('blur', () => { blurred = true; });
469
+ el.blur();
470
+ expect(blurred).toBe(true);
471
+ });
472
+
473
+ await it('should get/set onload handler', async () => {
474
+ const el = new HTMLElement();
475
+ expect(el.onload).toBeNull();
476
+ const handler = () => {};
477
+ el.onload = handler;
478
+ expect(el.onload).toBe(handler);
479
+ });
480
+
481
+ await it('should call onload handler on dispatchEvent', async () => {
482
+ const el = new HTMLElement();
483
+ let called = false;
484
+ el.onload = () => { called = true; };
485
+ el.dispatchEvent(new Event('load'));
486
+ expect(called).toBe(true);
487
+ });
488
+
489
+ await it('should get/set onerror handler', async () => {
490
+ const el = new HTMLElement();
491
+ expect(el.onerror).toBeNull();
492
+ const handler = () => {};
493
+ el.onerror = handler;
494
+ expect(el.onerror).toBe(handler);
495
+ });
496
+
497
+ await it('should call onerror handler on dispatchEvent', async () => {
498
+ const el = new HTMLElement();
499
+ let called = false;
500
+ el.onerror = () => { called = true; };
501
+ el.dispatchEvent(new Event('error'));
502
+ expect(called).toBe(true);
503
+ });
504
+
505
+ await it('should get/set onclick handler', async () => {
506
+ const el = new HTMLElement();
507
+ expect(el.onclick).toBeNull();
508
+ let clicked = false;
509
+ el.onclick = () => { clicked = true; };
510
+ el.click();
511
+ expect(clicked).toBe(true);
512
+ });
513
+
514
+ await it('should support both addEventListener and on* handler', async () => {
515
+ const el = new HTMLElement();
516
+ const calls: string[] = [];
517
+ el.addEventListener('load', () => { calls.push('listener'); });
518
+ el.onload = () => { calls.push('handler'); };
519
+ el.dispatchEvent(new Event('load'));
520
+ expect(calls.length).toBe(2);
521
+ expect(calls).toContain('listener');
522
+ expect(calls).toContain('handler');
523
+ });
524
+
525
+ await it('should clear on* handler when set to null', async () => {
526
+ const el = new HTMLElement();
527
+ let called = false;
528
+ el.onload = () => { called = true; };
529
+ el.onload = null;
530
+ el.dispatchEvent(new Event('load'));
531
+ expect(called).toBe(false);
532
+ });
533
+
534
+ await it('should clone HTMLElement', async () => {
535
+ const el = new HTMLElement();
536
+ el.title = 'test';
537
+ el.hidden = true;
538
+ const clone = el.cloneNode(false);
539
+ expect(clone instanceof HTMLElement).toBe(true);
540
+ expect(clone.getAttribute('title')).toBe('test');
541
+ expect(clone.hasAttribute('hidden')).toBe(true);
542
+ });
543
+ });
544
+
545
+ // -- NamedNodeMap --
546
+
547
+ await describe('NamedNodeMap', async () => {
548
+ await it('should set and get items', async () => {
549
+ const el = new Element();
550
+ el.setAttribute('id', 'test');
551
+ const attr = el.attributes.getNamedItem('id');
552
+ expect(attr).not.toBeNull();
553
+ expect(attr!.name).toBe('id');
554
+ expect(attr!.value).toBe('test');
555
+ });
556
+
557
+ await it('should item() by index', async () => {
558
+ const el = new Element();
559
+ el.setAttribute('id', 'test');
560
+ el.setAttribute('class', 'foo');
561
+ expect(el.attributes.item(0)!.name).toBe('id');
562
+ expect(el.attributes.item(1)!.name).toBe('class');
563
+ expect(el.attributes.item(2)).toBeNull();
564
+ });
565
+
566
+ await it('should remove items', async () => {
567
+ const el = new Element();
568
+ el.setAttribute('id', 'test');
569
+ el.attributes.removeNamedItem('id');
570
+ expect(el.attributes.length).toBe(0);
571
+ });
572
+
573
+ await it('should throw on removing non-existent item', async () => {
574
+ const el = new Element();
575
+ let threw = false;
576
+ try {
577
+ el.attributes.removeNamedItem('nonexistent');
578
+ } catch {
579
+ threw = true;
580
+ }
581
+ expect(threw).toBe(true);
582
+ });
583
+
584
+ await it('should iterate with for...of', async () => {
585
+ const el = new Element();
586
+ el.setAttribute('a', '1');
587
+ el.setAttribute('b', '2');
588
+ const names: string[] = [];
589
+ for (const attr of el.attributes) {
590
+ names.push(attr.name);
591
+ }
592
+ expect(names.length).toBe(2);
593
+ expect(names[0]).toBe('a');
594
+ expect(names[1]).toBe('b');
595
+ });
596
+ });
597
+
598
+ // -- CharacterData --
599
+
600
+ await describe('CharacterData', async () => {
601
+ await it('should create with default empty data', async () => {
602
+ const cd = new CharacterData();
603
+ expect(cd.data).toBe('');
604
+ expect(cd.length).toBe(0);
605
+ });
606
+
607
+ await it('should create with initial data', async () => {
608
+ const cd = new CharacterData('hello');
609
+ expect(cd.data).toBe('hello');
610
+ expect(cd.length).toBe(5);
611
+ });
612
+
613
+ await it('should sync data, textContent, and nodeValue', async () => {
614
+ const cd = new CharacterData('abc');
615
+ expect(cd.textContent).toBe('abc');
616
+ expect(cd.nodeValue).toBe('abc');
617
+ cd.data = 'xyz';
618
+ expect(cd.textContent).toBe('xyz');
619
+ expect(cd.nodeValue).toBe('xyz');
620
+ cd.textContent = '123';
621
+ expect(cd.data).toBe('123');
622
+ cd.nodeValue = '456';
623
+ expect(cd.data).toBe('456');
624
+ });
625
+
626
+ await it('should appendData', async () => {
627
+ const cd = new CharacterData('hello');
628
+ cd.appendData(' world');
629
+ expect(cd.data).toBe('hello world');
630
+ });
631
+
632
+ await it('should insertData', async () => {
633
+ const cd = new CharacterData('helo');
634
+ cd.insertData(2, 'l');
635
+ expect(cd.data).toBe('hello');
636
+ });
637
+
638
+ await it('should deleteData', async () => {
639
+ const cd = new CharacterData('hello');
640
+ cd.deleteData(1, 3);
641
+ expect(cd.data).toBe('ho');
642
+ });
643
+
644
+ await it('should replaceData', async () => {
645
+ const cd = new CharacterData('hello');
646
+ cd.replaceData(1, 3, 'a');
647
+ expect(cd.data).toBe('hao');
648
+ });
649
+
650
+ await it('should substringData', async () => {
651
+ const cd = new CharacterData('hello');
652
+ expect(cd.substringData(1, 3)).toBe('ell');
653
+ });
654
+
655
+ await it('should cloneNode', async () => {
656
+ const cd = new CharacterData('hello');
657
+ const clone = cd.cloneNode();
658
+ expect(clone.data).toBe('hello');
659
+ expect(clone).not.toBe(cd);
660
+ });
661
+
662
+ await it('should have correct Symbol.toStringTag', async () => {
663
+ const cd = new CharacterData();
664
+ expect(Object.prototype.toString.call(cd)).toBe('[object CharacterData]');
665
+ });
666
+ });
667
+
668
+ // -- Text --
669
+
670
+ await describe('Text', async () => {
671
+ await it('should create with data', async () => {
672
+ const t = new Text('hello');
673
+ expect(t.data).toBe('hello');
674
+ expect(t.nodeType).toBe(Node.TEXT_NODE);
675
+ expect(t.nodeName).toBe('#text');
676
+ });
677
+
678
+ await it('should create with empty default', async () => {
679
+ const t = new Text();
680
+ expect(t.data).toBe('');
681
+ });
682
+
683
+ await it('should be instanceof CharacterData and Node', async () => {
684
+ const t = new Text('hi');
685
+ expect(t instanceof CharacterData).toBe(true);
686
+ expect(t instanceof Node).toBe(true);
687
+ });
688
+
689
+ await it('should splitText', async () => {
690
+ const parent = new Element();
691
+ const t = new Text('hello world');
692
+ parent.appendChild(t);
693
+ const newNode = t.splitText(5);
694
+ expect(t.data).toBe('hello');
695
+ expect(newNode.data).toBe(' world');
696
+ expect(parent.childNodes.length).toBe(2);
697
+ expect(parent.childNodes.item(1)).toBe(newNode);
698
+ });
699
+
700
+ await it('should splitText without parent', async () => {
701
+ const t = new Text('hello world');
702
+ const newNode = t.splitText(5);
703
+ expect(t.data).toBe('hello');
704
+ expect(newNode.data).toBe(' world');
705
+ expect(newNode.parentNode).toBeNull();
706
+ });
707
+
708
+ await it('should wholeText with adjacent siblings', async () => {
709
+ const parent = new Element();
710
+ const t1 = new Text('hello');
711
+ const t2 = new Text(' ');
712
+ const t3 = new Text('world');
713
+ parent.appendChild(t1);
714
+ parent.appendChild(t2);
715
+ parent.appendChild(t3);
716
+ expect(t2.wholeText).toBe('hello world');
717
+ });
718
+
719
+ await it('should wholeText with no siblings', async () => {
720
+ const t = new Text('alone');
721
+ expect(t.wholeText).toBe('alone');
722
+ });
723
+
724
+ await it('should cloneNode', async () => {
725
+ const t = new Text('hello');
726
+ const clone = t.cloneNode();
727
+ expect(clone.data).toBe('hello');
728
+ expect(clone instanceof Text).toBe(true);
729
+ expect(clone).not.toBe(t);
730
+ });
731
+
732
+ await it('should have correct Symbol.toStringTag', async () => {
733
+ const t = new Text();
734
+ expect(Object.prototype.toString.call(t)).toBe('[object Text]');
735
+ });
736
+ });
737
+
738
+ // -- Comment --
739
+
740
+ await describe('Comment', async () => {
741
+ await it('should create with data', async () => {
742
+ const c = new Comment('a comment');
743
+ expect(c.data).toBe('a comment');
744
+ expect(c.nodeType).toBe(Node.COMMENT_NODE);
745
+ expect(c.nodeName).toBe('#comment');
746
+ });
747
+
748
+ await it('should be instanceof CharacterData and Node', async () => {
749
+ const c = new Comment();
750
+ expect(c instanceof CharacterData).toBe(true);
751
+ expect(c instanceof Node).toBe(true);
752
+ });
753
+
754
+ await it('should cloneNode', async () => {
755
+ const c = new Comment('test');
756
+ const clone = c.cloneNode();
757
+ expect(clone.data).toBe('test');
758
+ expect(clone instanceof Comment).toBe(true);
759
+ expect(clone).not.toBe(c);
760
+ });
761
+
762
+ await it('should have correct Symbol.toStringTag', async () => {
763
+ const c = new Comment();
764
+ expect(Object.prototype.toString.call(c)).toBe('[object Comment]');
765
+ });
766
+ });
767
+
768
+ // -- DocumentFragment --
769
+
770
+ await describe('DocumentFragment', async () => {
771
+ await it('should have correct nodeType and nodeName', async () => {
772
+ const frag = new DocumentFragment();
773
+ expect(frag.nodeType).toBe(Node.DOCUMENT_FRAGMENT_NODE);
774
+ expect(frag.nodeName).toBe('#document-fragment');
775
+ });
776
+
777
+ await it('should appendChild and track children', async () => {
778
+ const frag = new DocumentFragment();
779
+ const el = new Element();
780
+ frag.appendChild(el);
781
+ expect(frag.childNodes.length).toBe(1);
782
+ expect(frag.children.length).toBe(1);
783
+ expect(frag.childElementCount).toBe(1);
784
+ expect(frag.firstElementChild).toBe(el);
785
+ expect(frag.lastElementChild).toBe(el);
786
+ });
787
+
788
+ await it('should track textContent', async () => {
789
+ const frag = new DocumentFragment();
790
+ const t1 = new Text('hello');
791
+ const t2 = new Text(' world');
792
+ frag.appendChild(t1);
793
+ frag.appendChild(t2);
794
+ expect(frag.textContent).toBe('hello world');
795
+ });
796
+
797
+ await it('should set textContent replacing children', async () => {
798
+ const frag = new DocumentFragment();
799
+ frag.appendChild(new Element());
800
+ frag.appendChild(new Text('old'));
801
+ frag.textContent = 'new text';
802
+ expect(frag.childNodes.length).toBe(1);
803
+ expect(frag.textContent).toBe('new text');
804
+ });
805
+
806
+ await it('should be instanceof Node', async () => {
807
+ const frag = new DocumentFragment();
808
+ expect(frag instanceof Node).toBe(true);
809
+ });
810
+ });
811
+
812
+ // -- DOMTokenList --
813
+
814
+ await describe('DOMTokenList', async () => {
815
+ await it('should add tokens', async () => {
816
+ const el = new Element();
817
+ const classList = new DOMTokenList(el, 'class');
818
+ classList.add('foo', 'bar');
819
+ expect(classList.length).toBe(2);
820
+ expect(classList.contains('foo')).toBe(true);
821
+ expect(classList.contains('bar')).toBe(true);
822
+ expect(el.getAttribute('class')).toBe('foo bar');
823
+ });
824
+
825
+ await it('should remove tokens', async () => {
826
+ const el = new Element();
827
+ el.setAttribute('class', 'foo bar baz');
828
+ const classList = new DOMTokenList(el, 'class');
829
+ classList.remove('bar');
830
+ expect(classList.length).toBe(2);
831
+ expect(classList.contains('bar')).toBe(false);
832
+ expect(classList.contains('foo')).toBe(true);
833
+ expect(classList.contains('baz')).toBe(true);
834
+ });
835
+
836
+ await it('should toggle tokens', async () => {
837
+ const el = new Element();
838
+ const classList = new DOMTokenList(el, 'class');
839
+ const added = classList.toggle('active');
840
+ expect(added).toBe(true);
841
+ expect(classList.contains('active')).toBe(true);
842
+ const removed = classList.toggle('active');
843
+ expect(removed).toBe(false);
844
+ expect(classList.contains('active')).toBe(false);
845
+ });
846
+
847
+ await it('should toggle with force', async () => {
848
+ const el = new Element();
849
+ const classList = new DOMTokenList(el, 'class');
850
+ classList.toggle('active', true);
851
+ expect(classList.contains('active')).toBe(true);
852
+ classList.toggle('active', true);
853
+ expect(classList.contains('active')).toBe(true);
854
+ classList.toggle('active', false);
855
+ expect(classList.contains('active')).toBe(false);
856
+ });
857
+
858
+ await it('should item() by index', async () => {
859
+ const el = new Element();
860
+ el.setAttribute('class', 'a b c');
861
+ const classList = new DOMTokenList(el, 'class');
862
+ expect(classList.item(0)).toBe('a');
863
+ expect(classList.item(1)).toBe('b');
864
+ expect(classList.item(2)).toBe('c');
865
+ expect(classList.item(3)).toBeNull();
866
+ });
867
+
868
+ await it('should return correct value', async () => {
869
+ const el = new Element();
870
+ el.setAttribute('class', 'foo bar');
871
+ const classList = new DOMTokenList(el, 'class');
872
+ expect(classList.value).toBe('foo bar');
873
+ });
874
+
875
+ await it('should set value', async () => {
876
+ const el = new Element();
877
+ const classList = new DOMTokenList(el, 'class');
878
+ classList.value = 'x y z';
879
+ expect(classList.length).toBe(3);
880
+ expect(el.getAttribute('class')).toBe('x y z');
881
+ });
882
+
883
+ await it('should report empty when no attribute', async () => {
884
+ const el = new Element();
885
+ const classList = new DOMTokenList(el, 'class');
886
+ expect(classList.length).toBe(0);
887
+ expect(classList.value).toBe('');
888
+ });
889
+
890
+ await it('should toString', async () => {
891
+ const el = new Element();
892
+ el.setAttribute('class', 'a b');
893
+ const classList = new DOMTokenList(el, 'class');
894
+ expect(classList.toString()).toBe('a b');
895
+ });
896
+ });
897
+ };