@tkeron/html-parser 1.0.0 → 1.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.
@@ -1,755 +0,0 @@
1
- import { expect, it, describe } from 'bun:test';
2
- import { tokenize } from '../src/tokenizer';
3
- import { parse, domToAST, ASTNodeType, type ASTNode } from '../src/parser';
4
-
5
- function parseToAST(tokens: any[]): any {
6
- const dom = parse(tokens);
7
- const ast = domToAST(dom);
8
-
9
- const htmlEl = ast.children?.find((c: any) => c.tagName === 'html');
10
- if (htmlEl) {
11
- const bodyEl = htmlEl.children?.find((c: any) => c.tagName === 'body');
12
- if (bodyEl && bodyEl.children) {
13
- return { type: ASTNodeType.Document, children: bodyEl.children };
14
- }
15
- }
16
- return ast;
17
- }
18
-
19
- describe('Custom Elements Support', () => {
20
-
21
- describe('Basic Custom Elements', () => {
22
- it('should parse simple custom element with single hyphen', () => {
23
- const tokens = tokenize('<my-component></my-component>');
24
- const ast = parseToAST(tokens);
25
-
26
- expect(ast.type).toBe(ASTNodeType.Document);
27
- expect(ast.children).toHaveLength(1);
28
-
29
- const element = ast.children![0]!;
30
- expect(element.type).toBe(ASTNodeType.Element);
31
- expect(element.tagName).toBe('my-component');
32
- });
33
-
34
- it('should parse custom element with numbers', () => {
35
- const tokens = tokenize('<my-component-123></my-component-123>');
36
- const ast = parseToAST(tokens);
37
-
38
- const element = ast.children![0]!;
39
- expect(element.type).toBe(ASTNodeType.Element);
40
- expect(element.tagName).toBe('my-component-123');
41
- });
42
-
43
- it('should parse short custom element', () => {
44
- const tokens = tokenize('<x-button></x-button>');
45
- const ast = parseToAST(tokens);
46
-
47
- const element = ast.children![0]!;
48
- expect(element.type).toBe(ASTNodeType.Element);
49
- expect(element.tagName).toBe('x-button');
50
- });
51
-
52
- it('should parse custom element with multiple hyphens', () => {
53
- const tokens = tokenize('<app-header-nav></app-header-nav>');
54
- const ast = parseToAST(tokens);
55
-
56
- const element = ast.children![0]!;
57
- expect(element.type).toBe(ASTNodeType.Element);
58
- expect(element.tagName).toBe('app-header-nav');
59
- });
60
-
61
- it('should parse custom element with many hyphens', () => {
62
- const tokens = tokenize('<my-custom-super-component></my-custom-super-component>');
63
- const ast = parseToAST(tokens);
64
-
65
- const element = ast.children![0]!;
66
- expect(element.type).toBe(ASTNodeType.Element);
67
- expect(element.tagName).toBe('my-custom-super-component');
68
- });
69
-
70
- it('should parse custom element with dots', () => {
71
- const tokens = tokenize('<my-comp.v2></my-comp.v2>');
72
- const ast = parseToAST(tokens);
73
-
74
- const element = ast.children![0]!;
75
- expect(element.type).toBe(ASTNodeType.Element);
76
- expect(element.tagName).toBe('my-comp.v2');
77
- });
78
-
79
- it('should parse custom element with underscores', () => {
80
- const tokens = tokenize('<my-comp_beta></my-comp_beta>');
81
- const ast = parseToAST(tokens);
82
-
83
- const element = ast.children![0]!;
84
- expect(element.type).toBe(ASTNodeType.Element);
85
- expect(element.tagName).toBe('my-comp_beta');
86
- });
87
- });
88
-
89
- describe('Custom Elements with Attributes', () => {
90
- it('should parse custom element with class attribute', () => {
91
- const tokens = tokenize('<my-comp class="test"></my-comp>');
92
- const ast = parseToAST(tokens);
93
-
94
- const element = ast.children![0]!;
95
- expect(element.type).toBe(ASTNodeType.Element);
96
- expect(element.tagName).toBe('my-comp');
97
- expect(element.attributes).toEqual({ class: 'test' });
98
- });
99
-
100
- it('should parse custom element with multiple attributes', () => {
101
- const tokens = tokenize('<my-comp class="test" id="main" data-value="123"></my-comp>');
102
- const ast = parseToAST(tokens);
103
-
104
- const element = ast.children![0]!;
105
- expect(element.type).toBe(ASTNodeType.Element);
106
- expect(element.tagName).toBe('my-comp');
107
- expect(element.attributes).toEqual({
108
- class: 'test',
109
- id: 'main',
110
- 'data-value': '123'
111
- });
112
- });
113
-
114
- it('should parse custom element with custom attributes', () => {
115
- const tokens = tokenize('<user-card name="John" age="30"></user-card>');
116
- const ast = parseToAST(tokens);
117
-
118
- const element = ast.children![0]!;
119
- expect(element.type).toBe(ASTNodeType.Element);
120
- expect(element.tagName).toBe('user-card');
121
- expect(element.attributes).toEqual({
122
- name: 'John',
123
- age: '30'
124
- });
125
- });
126
- });
127
-
128
- describe('Self-Closing Custom Elements', () => {
129
- it('should parse self-closing custom element with space', () => {
130
- const tokens = tokenize('<self-closing />');
131
- const ast = parseToAST(tokens);
132
-
133
- const element = ast.children![0]!;
134
- expect(element.type).toBe(ASTNodeType.Element);
135
- expect(element.tagName).toBe('self-closing');
136
- });
137
-
138
- it('should parse self-closing custom element without space', () => {
139
- const tokens = tokenize('<my-comp/>');
140
- const ast = parseToAST(tokens);
141
-
142
- const element = ast.children![0]!;
143
- expect(element.type).toBe(ASTNodeType.Element);
144
- expect(element.tagName).toBe('my-comp');
145
- });
146
-
147
- it('should parse self-closing custom element with attributes', () => {
148
- const tokens = tokenize('<icon-button type="primary" size="lg" />');
149
- const ast = parseToAST(tokens);
150
-
151
- const element = ast.children![0]!;
152
- expect(element.type).toBe(ASTNodeType.Element);
153
- expect(element.tagName).toBe('icon-button');
154
- expect(element.attributes).toEqual({
155
- type: 'primary',
156
- size: 'lg'
157
- });
158
- });
159
- });
160
-
161
- describe('Nested Custom Elements', () => {
162
- it('should parse nested custom elements', () => {
163
- const tokens = tokenize('<outer-comp><inner-comp>text</inner-comp></outer-comp>');
164
- const ast = parseToAST(tokens);
165
-
166
- const outer = ast.children![0]!;
167
- expect(outer.type).toBe(ASTNodeType.Element);
168
- expect(outer.tagName).toBe('outer-comp');
169
- expect(outer.children).toHaveLength(1);
170
-
171
- const inner = outer.children![0]!;
172
- expect(inner.type).toBe(ASTNodeType.Element);
173
- expect(inner.tagName).toBe('inner-comp');
174
- expect(inner.children).toHaveLength(1);
175
-
176
- const text = inner.children![0]!;
177
- expect(text.type).toBe(ASTNodeType.Text);
178
- expect(text.content).toBe('text');
179
- });
180
-
181
- it('should parse deeply nested custom elements', () => {
182
- const tokens = tokenize('<level-1><level-2><level-3>content</level-3></level-2></level-1>');
183
- const ast = parseToAST(tokens);
184
-
185
- const level1 = ast.children![0]!;
186
- expect(level1.tagName).toBe('level-1');
187
-
188
- const level2 = level1.children![0]!;
189
- expect(level2.tagName).toBe('level-2');
190
-
191
- const level3 = level2.children![0]!;
192
- expect(level3.tagName).toBe('level-3');
193
- });
194
-
195
- it('should parse custom elements mixed with standard elements', () => {
196
- const tokens = tokenize('<div><my-comp><span>text</span></my-comp></div>');
197
- const ast = parseToAST(tokens);
198
-
199
- const div = ast.children![0]!;
200
- expect(div.tagName).toBe('div');
201
-
202
- const myComp = div.children![0]!;
203
- expect(myComp.tagName).toBe('my-comp');
204
-
205
- const span = myComp.children![0]!;
206
- expect(span.tagName).toBe('span');
207
- });
208
- });
209
-
210
- describe('Tag Name Normalization', () => {
211
- it('should normalize custom element tagName to UPPERCASE', () => {
212
- const tokens = tokenize('<my-comp></my-comp>');
213
- const ast = parseToAST(tokens);
214
-
215
- const element = ast.children![0]!;
216
- expect(element.tagName).toBe('my-comp');
217
- });
218
-
219
- it('should normalize nodeName to UPPERCASE', () => {
220
- const tokens = tokenize('<my-comp></my-comp>');
221
- const ast = parseToAST(tokens);
222
-
223
- const element = ast.children![0]!;
224
-
225
- if (element.nodeName) {
226
- expect(element.nodeName).toBe('my-comp');
227
- }
228
- });
229
- });
230
-
231
- describe('Regression Tests - Standard Elements', () => {
232
- it('should still parse standard div element', () => {
233
- const tokens = tokenize('<div></div>');
234
- const ast = parseToAST(tokens);
235
-
236
- const element = ast.children![0]!;
237
- expect(element.type).toBe(ASTNodeType.Element);
238
- expect(element.tagName).toBe('div');
239
- });
240
-
241
- it('should still parse standard header element', () => {
242
- const tokens = tokenize('<header></header>');
243
- const ast = parseToAST(tokens);
244
-
245
- const element = ast.children![0]!;
246
- expect(element.type).toBe(ASTNodeType.Element);
247
- expect(element.tagName).toBe('header');
248
- });
249
-
250
- it('should still parse standard section element', () => {
251
- const tokens = tokenize('<section></section>');
252
- const ast = parseToAST(tokens);
253
-
254
- const element = ast.children![0]!;
255
- expect(element.type).toBe(ASTNodeType.Element);
256
- expect(element.tagName).toBe('section');
257
- });
258
-
259
- it('should distinguish between header tag and header-comp custom element', () => {
260
- const tokens = tokenize('<header></header><header-comp></header-comp>');
261
- const ast = parseToAST(tokens);
262
-
263
- expect(ast.children).toHaveLength(2);
264
-
265
- const header = ast.children![0]!;
266
- expect(header.tagName).toBe('header');
267
-
268
- const headerComp = ast.children![1]!;
269
- expect(headerComp.tagName).toBe('header-comp');
270
- });
271
- });
272
-
273
- describe('Custom Elements with Different Formats', () => {
274
- it('should parse custom element: my-comp', () => {
275
- const tokens = tokenize('<my-comp></my-comp>');
276
- const ast = parseToAST(tokens);
277
-
278
- const element = ast.children![0]!;
279
- expect(element.tagName).toBe('my-comp');
280
- });
281
-
282
- it('should parse custom element: comp-v2', () => {
283
- const tokens = tokenize('<comp-v2></comp-v2>');
284
- const ast = parseToAST(tokens);
285
-
286
- const element = ast.children![0]!;
287
- expect(element.tagName).toBe('comp-v2');
288
- });
289
-
290
- it('should parse custom element: my-comp-123', () => {
291
- const tokens = tokenize('<my-comp-123></my-comp-123>');
292
- const ast = parseToAST(tokens);
293
-
294
- const element = ast.children![0]!;
295
- expect(element.tagName).toBe('my-comp-123');
296
- });
297
-
298
- it('should parse custom element: x-foo', () => {
299
- const tokens = tokenize('<x-foo></x-foo>');
300
- const ast = parseToAST(tokens);
301
-
302
- const element = ast.children![0]!;
303
- expect(element.tagName).toBe('x-foo');
304
- });
305
-
306
- it('should parse custom element with numbers: comp-123-test', () => {
307
- const tokens = tokenize('<comp-123-test></comp-123-test>');
308
- const ast = parseToAST(tokens);
309
-
310
- const element = ast.children![0]!;
311
- expect(element.tagName).toBe('comp-123-test');
312
- });
313
- });
314
-
315
- describe('Edge Cases', () => {
316
- it('should parse custom element with whitespace before closing bracket', () => {
317
- const tokens = tokenize('<my-comp ></my-comp>');
318
- const ast = parseToAST(tokens);
319
-
320
- const element = ast.children![0]!;
321
- expect(element.tagName).toBe('my-comp');
322
- });
323
-
324
- it('should parse multiple custom elements in sequence', () => {
325
- const tokens = tokenize('<first-comp></first-comp><second-comp></second-comp><third-comp></third-comp>');
326
- const ast = parseToAST(tokens);
327
-
328
- expect(ast.children).toHaveLength(3);
329
- expect(ast.children![0]!.tagName).toBe('first-comp');
330
- expect(ast.children![1]!.tagName).toBe('second-comp');
331
- expect(ast.children![2]!.tagName).toBe('third-comp');
332
- });
333
-
334
- it('should parse custom element with text content', () => {
335
- const tokens = tokenize('<user-name>John Doe</user-name>');
336
- const ast = parseToAST(tokens);
337
-
338
- const element = ast.children![0]!;
339
- expect(element.tagName).toBe('user-name');
340
- expect(element.children).toHaveLength(1);
341
- expect(element.children![0]!.type).toBe(ASTNodeType.Text);
342
- expect(element.children![0]!.content).toBe('John Doe');
343
- });
344
-
345
- it('should parse custom element with child elements and text', () => {
346
- const tokens = tokenize('<card-header><h1>Title</h1><sub-title>Subtitle</sub-title></card-header>');
347
- const ast = parseToAST(tokens);
348
-
349
- const cardHeader = ast.children![0]!;
350
- expect(cardHeader.tagName).toBe('card-header');
351
- expect(cardHeader.children).toHaveLength(2);
352
-
353
- expect(cardHeader.children![0]!.tagName).toBe('h1');
354
- expect(cardHeader.children![1]!.tagName).toBe('sub-title');
355
- });
356
-
357
- it('should handle unclosed custom element gracefully', () => {
358
- const tokens = tokenize('<my-comp>');
359
- const ast = parseToAST(tokens);
360
-
361
- const element = ast.children![0]!;
362
- expect(element.tagName).toBe('my-comp');
363
- });
364
-
365
- it('should parse custom element with trailing slash in opening tag', () => {
366
- const tokens = tokenize('<my-comp/>');
367
- const ast = parseToAST(tokens);
368
-
369
- const element = ast.children![0]!;
370
- expect(element.tagName).toBe('my-comp');
371
- });
372
- });
373
-
374
- describe('Complex Real-World Scenarios', () => {
375
- it('should parse web component with shadow DOM structure', () => {
376
- const html = `
377
- <user-profile>
378
- <profile-header>
379
- <avatar-img src="user.jpg" />
380
- <user-name>Jane Smith</user-name>
381
- </profile-header>
382
- <profile-content>
383
- <bio-section>Biography text here</bio-section>
384
- <stats-panel>
385
- <stat-item label="Posts" value="123" />
386
- <stat-item label="Followers" value="456" />
387
- </stats-panel>
388
- </profile-content>
389
- </user-profile>
390
- `;
391
-
392
- const tokens = tokenize(html);
393
- const ast = parseToAST(tokens);
394
-
395
-
396
- const userProfile = ast.children!.find((node: any) => node.type === ASTNodeType.Element)!;
397
- expect(userProfile.tagName).toBe('user-profile');
398
-
399
-
400
- expect(userProfile.children).toBeDefined();
401
- expect(userProfile.children!.length).toBeGreaterThan(0);
402
- });
403
-
404
- it('should parse framework-style component tree', () => {
405
- const html = `
406
- <app-root>
407
- <app-header>
408
- <nav-bar>
409
- <nav-item href="/home">Home</nav-item>
410
- <nav-item href="/about">About</nav-item>
411
- </nav-bar>
412
- </app-header>
413
- <main-content>
414
- <article-list>
415
- <article-card title="Test" />
416
- </article-list>
417
- </main-content>
418
- <app-footer />
419
- </app-root>
420
- `;
421
-
422
- const tokens = tokenize(html);
423
- const ast = parseToAST(tokens);
424
-
425
-
426
- const appRoot = ast.children!.find((node: any) => node.type === ASTNodeType.Element)!;
427
- expect(appRoot.tagName).toBe('app-root');
428
- });
429
-
430
- it('should parse custom elements with data attributes', () => {
431
- const tokens = tokenize('<my-widget data-id="123" data-type="primary" data-config=\'{"key":"value"}\'></my-widget>');
432
- const ast = parseToAST(tokens);
433
-
434
- const element = ast.children![0]!;
435
- expect(element.tagName).toBe('my-widget');
436
- expect(element.attributes).toHaveProperty('data-id', '123');
437
- expect(element.attributes).toHaveProperty('data-type', 'primary');
438
- expect(element.attributes).toHaveProperty('data-config');
439
- });
440
- });
441
-
442
- describe('Custom Element Name Validation Pattern', () => {
443
- it('valid: starts with lowercase letter, contains hyphen', () => {
444
- const validNames = [
445
- 'a-b',
446
- 'my-component',
447
- 'x-button',
448
- 'user-card',
449
- 'app-header',
450
- 'my-comp-123',
451
- 'comp-v2.1',
452
- 'test_element-1'
453
- ];
454
-
455
- validNames.forEach(name => {
456
- const tokens = tokenize(`<${name}></${name}>`);
457
- const ast = parseToAST(tokens);
458
- const element = ast.children![0]!;
459
- expect(element.tagName).toBe(name);
460
- });
461
- });
462
-
463
- it('should handle complex hyphenated names', () => {
464
- const complexNames = [
465
- 'my-super-long-component-name',
466
- 'x-1-2-3-4',
467
- 'component-v2-beta-test',
468
- 'ui-button-primary-large'
469
- ];
470
-
471
- complexNames.forEach(name => {
472
- const tokens = tokenize(`<${name}></${name}>`);
473
- const ast = parseToAST(tokens);
474
- const element = ast.children![0]!;
475
- expect(element.tagName).toBe(name);
476
- });
477
- });
478
- });
479
-
480
- describe('Tokenizer-specific Tests', () => {
481
- it('tokenizer should capture full custom element name', () => {
482
- const tokens = tokenize('<my-component-123></my-component-123>');
483
-
484
-
485
- const openTag = tokens.find(t => t.type === 'TAG_OPEN');
486
- expect(openTag).toBeDefined();
487
- expect(openTag!.value).toBe('my-component-123');
488
-
489
-
490
- const closeTag = tokens.find(t => t.type === 'TAG_CLOSE');
491
- expect(closeTag).toBeDefined();
492
- expect(closeTag!.value).toBe('my-component-123');
493
- });
494
-
495
- it('tokenizer should handle custom element with attributes correctly', () => {
496
- const tokens = tokenize('<my-comp class="test" id="main"></my-comp>');
497
-
498
- const openTag = tokens.find(t => t.type === 'TAG_OPEN');
499
- expect(openTag).toBeDefined();
500
- expect(openTag!.value).toBe('my-comp');
501
- expect(openTag!.attributes).toEqual({
502
- class: 'test',
503
- id: 'main'
504
- });
505
- });
506
-
507
- it('tokenizer should handle self-closing custom elements', () => {
508
- const tokens = tokenize('<my-comp />');
509
-
510
- const openTag = tokens.find(t => t.type === 'TAG_OPEN');
511
- expect(openTag).toBeDefined();
512
- expect(openTag!.value).toBe('my-comp');
513
- expect(openTag!.isSelfClosing).toBe(true);
514
- });
515
- });
516
-
517
- describe('Customized Built-in Elements (is attribute)', () => {
518
- it('should parse button with is attribute', () => {
519
- const tokens = tokenize('<button is="plastic-button">Click Me!</button>');
520
- const ast = parseToAST(tokens);
521
-
522
- const button = ast.children![0]!;
523
- expect(button.type).toBe(ASTNodeType.Element);
524
- expect(button.tagName).toBe('button');
525
- expect(button.attributes).toHaveProperty('is', 'plastic-button');
526
- });
527
-
528
- it('should parse input with is attribute', () => {
529
- const tokens = tokenize('<input is="custom-input" type="text" />');
530
- const ast = parseToAST(tokens);
531
-
532
- const input = ast.children![0]!;
533
- expect(input.tagName).toBe('input');
534
- expect(input.attributes).toHaveProperty('is', 'custom-input');
535
- expect(input.attributes).toHaveProperty('type', 'text');
536
- });
537
-
538
- it('should parse div with is attribute', () => {
539
- const tokens = tokenize('<div is="fancy-div"></div>');
540
- const ast = parseToAST(tokens);
541
-
542
- const div = ast.children![0]!;
543
- expect(div.tagName).toBe('div');
544
- expect(div.attributes).toHaveProperty('is', 'fancy-div');
545
- });
546
- });
547
-
548
- describe('Reserved Custom Element Names', () => {
549
- it('should parse annotation-xml (reserved SVG name)', () => {
550
- const tokens = tokenize('<annotation-xml></annotation-xml>');
551
- const ast = parseToAST(tokens);
552
-
553
- const element = ast.children![0]!;
554
- expect(element.tagName).toBe('annotation-xml');
555
- });
556
-
557
- it('should parse font-face (reserved SVG name)', () => {
558
- const tokens = tokenize('<font-face></font-face>');
559
- const ast = parseToAST(tokens);
560
-
561
- const element = ast.children![0]!;
562
- expect(element.tagName).toBe('font-face');
563
- });
564
-
565
- it('should parse color-profile (reserved SVG name)', () => {
566
- const tokens = tokenize('<color-profile></color-profile>');
567
- const ast = parseToAST(tokens);
568
-
569
- const element = ast.children![0]!;
570
- expect(element.tagName).toBe('color-profile');
571
- });
572
- });
573
-
574
- describe('Unicode Custom Element Names', () => {
575
- it('should parse custom element with Greek letters', () => {
576
- const tokens = tokenize('<math-α></math-α>');
577
- const ast = parseToAST(tokens);
578
-
579
- const element = ast.children![0]!;
580
- expect(element.tagName).toBe('math-α');
581
- });
582
-
583
- it('should parse custom element with emoji', () => {
584
- const tokens = tokenize('<emotion-😍></emotion-😍>');
585
- const ast = parseToAST(tokens);
586
-
587
- const element = ast.children![0]!;
588
- expect(element.tagName).toBe('emotion-😍');
589
- });
590
-
591
- it('should parse custom element with Chinese characters', () => {
592
- const tokens = tokenize('<my-元素></my-元素>');
593
- const ast = parseToAST(tokens);
594
-
595
- const element = ast.children![0]!;
596
- expect(element.tagName).toBe('my-元素');
597
- });
598
-
599
- it('should parse custom element with Arabic characters', () => {
600
- const tokens = tokenize('<my-عنصر></my-عنصر>');
601
- const ast = parseToAST(tokens);
602
-
603
- const element = ast.children![0]!;
604
- expect(element.tagName).toBe('my-عنصر');
605
- });
606
- });
607
-
608
- describe('Extreme Edge Cases', () => {
609
- it('should parse very long custom element name', () => {
610
- const longName = 'my-super-duper-extra-long-custom-component-name-that-keeps-going-and-going';
611
- const tokens = tokenize(`<${longName}></${longName}>`);
612
- const ast = parseToAST(tokens);
613
-
614
- const element = ast.children![0]!;
615
- expect(element.tagName).toBe(longName);
616
- });
617
-
618
- it('should parse custom element with many consecutive hyphens', () => {
619
- const tokens = tokenize('<my---component></my---component>');
620
- const ast = parseToAST(tokens);
621
-
622
- const element = ast.children![0]!;
623
- expect(element.tagName).toBe('my---component');
624
- });
625
-
626
- it('should parse custom element starting with x-', () => {
627
- const tokens = tokenize('<x-></x->');
628
- const ast = parseToAST(tokens);
629
-
630
- const element = ast.children![0]!;
631
- expect(element.tagName).toBe('x-');
632
- });
633
-
634
- it('should handle custom element with newlines in attributes', () => {
635
- const html = `<my-comp
636
- class="test"
637
- id="main"
638
- data-value="123"
639
- ></my-comp>`;
640
-
641
- const tokens = tokenize(html);
642
- const ast = parseToAST(tokens);
643
-
644
- const element = ast.children![0]!;
645
- expect(element.tagName).toBe('my-comp');
646
- expect(element.attributes).toHaveProperty('class', 'test');
647
- expect(element.attributes).toHaveProperty('id', 'main');
648
- });
649
-
650
- it('should parse custom element mixed with comments', () => {
651
- const html = '<!-- comment --><my-comp>text</my-comp><!-- another comment -->';
652
- const tokens = tokenize(html);
653
- const ast = parseToAST(tokens);
654
-
655
-
656
- const myComp = ast.children!.find((node: any) => node.type === ASTNodeType.Element)!;
657
- expect(myComp.tagName).toBe('my-comp');
658
- });
659
-
660
- it('should parse empty custom element', () => {
661
- const tokens = tokenize('<my-comp></my-comp>');
662
- const ast = parseToAST(tokens);
663
-
664
- const element = ast.children![0]!;
665
- expect(element.tagName).toBe('my-comp');
666
- expect(element.children).toBeDefined();
667
- expect(element.children!.length).toBe(0);
668
- });
669
-
670
- it('should parse custom element with only whitespace content', () => {
671
- const tokens = tokenize('<my-comp> \n\t </my-comp>');
672
- const ast = parseToAST(tokens);
673
-
674
- const element = ast.children![0]!;
675
- expect(element.tagName).toBe('my-comp');
676
- expect(element.children).toBeDefined();
677
- expect(element.children!.length).toBeGreaterThan(0);
678
- });
679
- });
680
-
681
- describe('Malformed Custom Elements', () => {
682
- it('should handle mismatched closing tag', () => {
683
- const tokens = tokenize('<my-comp></my-other>');
684
- const ast = parseToAST(tokens);
685
-
686
- const element = ast.children![0]!;
687
- expect(element.tagName).toBe('my-comp');
688
- });
689
-
690
- it('should handle multiple unclosed custom elements', () => {
691
- const tokens = tokenize('<my-comp><nested-comp><deep-comp>');
692
- const ast = parseToAST(tokens);
693
-
694
- const element = ast.children![0]!;
695
- expect(element.tagName).toBe('my-comp');
696
- });
697
-
698
- it('should handle custom element with malformed attributes', () => {
699
- const tokens = tokenize('<my-comp attr-without-value attr="value"></my-comp>');
700
- const ast = parseToAST(tokens);
701
-
702
- const element = ast.children![0]!;
703
- expect(element.tagName).toBe('my-comp');
704
- expect(element.attributes).toBeDefined();
705
- });
706
- });
707
-
708
- describe('Custom Elements in Special Contexts', () => {
709
- it('should parse custom element inside table', () => {
710
- const tokens = tokenize('<table><tr><td><my-cell>content</my-cell></td></tr></table>');
711
- const ast = parseToAST(tokens);
712
-
713
-
714
- const table = ast.children![0]!;
715
- expect(table.tagName).toBe('table');
716
- });
717
-
718
- it('should parse custom element inside list', () => {
719
- const tokens = tokenize('<ul><li><list-item></list-item></li></ul>');
720
- const ast = parseToAST(tokens);
721
-
722
- const ul = ast.children![0]!;
723
- expect(ul.tagName).toBe('ul');
724
- });
725
-
726
- it('should parse custom element inside form', () => {
727
- const tokens = tokenize('<form><form-field name="test"></form-field></form>');
728
- const ast = parseToAST(tokens);
729
-
730
- const form = ast.children![0]!;
731
- expect(form.tagName).toBe('form');
732
- });
733
- });
734
-
735
- describe('ARIA and Accessibility', () => {
736
- it('should parse custom element with ARIA attributes', () => {
737
- const tokens = tokenize('<my-button role="button" aria-label="Click me" aria-disabled="true"></my-button>');
738
- const ast = parseToAST(tokens);
739
-
740
- const element = ast.children![0]!;
741
- expect(element.tagName).toBe('my-button');
742
- expect(element.attributes).toHaveProperty('role', 'button');
743
- expect(element.attributes).toHaveProperty('aria-label', 'Click me');
744
- expect(element.attributes).toHaveProperty('aria-disabled', 'true');
745
- });
746
-
747
- it('should parse custom element with tabindex', () => {
748
- const tokens = tokenize('<my-comp tabindex="0"></my-comp>');
749
- const ast = parseToAST(tokens);
750
-
751
- const element = ast.children![0]!;
752
- expect(element.attributes).toHaveProperty('tabindex', '0');
753
- });
754
- });
755
- });