@afixt/test-utils 2.4.0 → 2.6.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.
- package/package.json +1 -1
- package/src/constants.js +46 -0
- package/src/detectFocusTrap.js +484 -0
- package/src/getAccessibleText.js +32 -2
- package/src/index.js +2 -0
- package/src/shadowDomUtils.js +83 -0
- package/src/stringUtils.js +10 -1
- package/test/detectFocusTrap.test.js +1004 -0
- package/test/getAccessibleText.test.js +74 -0
- package/test/hasValidAriaRole.test.js +270 -151
- package/test/index.test.js +3 -0
- package/test/shadowDomUtils.test.js +98 -0
|
@@ -300,6 +300,32 @@ describe('getAccessibleText', () => {
|
|
|
300
300
|
});
|
|
301
301
|
});
|
|
302
302
|
|
|
303
|
+
describe('child role="img" with aria-label in subtree', () => {
|
|
304
|
+
it('should get aria-label from child elements with role="img"', () => {
|
|
305
|
+
const container = document.createElement('div');
|
|
306
|
+
container.innerHTML = '<div role="img" aria-label="Company Logo"></div>';
|
|
307
|
+
document.body.appendChild(container);
|
|
308
|
+
|
|
309
|
+
expect(getAccessibleText(container)).toBe('Company Logo');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should skip role="img" elements with empty aria-label', () => {
|
|
313
|
+
const container = document.createElement('div');
|
|
314
|
+
container.innerHTML = 'Label <div role="img" aria-label=""></div>';
|
|
315
|
+
document.body.appendChild(container);
|
|
316
|
+
|
|
317
|
+
expect(getAccessibleText(container)).toBe('Label');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should skip role="img" elements without aria-label', () => {
|
|
321
|
+
const container = document.createElement('div');
|
|
322
|
+
container.innerHTML = 'Label <div role="img"></div>';
|
|
323
|
+
document.body.appendChild(container);
|
|
324
|
+
|
|
325
|
+
expect(getAccessibleText(container)).toBe('Label');
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
303
329
|
describe('visibleOnly option', () => {
|
|
304
330
|
it('should skip aria-label when visibleOnly is true', () => {
|
|
305
331
|
const div = document.createElement('div');
|
|
@@ -522,4 +548,52 @@ describe('getAccessibleText', () => {
|
|
|
522
548
|
expect(result).toBe('Valid Text');
|
|
523
549
|
});
|
|
524
550
|
});
|
|
551
|
+
|
|
552
|
+
describe('shadow DOM traversal', () => {
|
|
553
|
+
it('should extract text from inside an open shadow root', () => {
|
|
554
|
+
const host = document.createElement('div');
|
|
555
|
+
document.body.appendChild(host);
|
|
556
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
557
|
+
const inner = document.createElement('span');
|
|
558
|
+
inner.textContent = 'Shadow text';
|
|
559
|
+
shadow.appendChild(inner);
|
|
560
|
+
|
|
561
|
+
expect(getAccessibleText(host)).toBe('Shadow text');
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('should extract text from nested shadow roots', () => {
|
|
565
|
+
const host1 = document.createElement('div');
|
|
566
|
+
document.body.appendChild(host1);
|
|
567
|
+
const shadow1 = host1.attachShadow({ mode: 'open' });
|
|
568
|
+
|
|
569
|
+
const host2 = document.createElement('div');
|
|
570
|
+
shadow1.appendChild(host2);
|
|
571
|
+
const shadow2 = host2.attachShadow({ mode: 'open' });
|
|
572
|
+
|
|
573
|
+
const inner = document.createElement('span');
|
|
574
|
+
inner.textContent = 'Deeply nested text';
|
|
575
|
+
shadow2.appendChild(inner);
|
|
576
|
+
|
|
577
|
+
expect(getAccessibleText(host1)).toBe('Deeply nested text');
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('should prefer aria-label over shadow DOM text', () => {
|
|
581
|
+
const host = document.createElement('div');
|
|
582
|
+
host.setAttribute('aria-label', 'Accessible Label');
|
|
583
|
+
document.body.appendChild(host);
|
|
584
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
585
|
+
const inner = document.createElement('span');
|
|
586
|
+
inner.textContent = 'Shadow text';
|
|
587
|
+
shadow.appendChild(inner);
|
|
588
|
+
|
|
589
|
+
expect(getAccessibleText(host)).toBe('Accessible Label');
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should return empty string for custom element with no content and no shadow root', () => {
|
|
593
|
+
const el = document.createElement('my-widget');
|
|
594
|
+
document.body.appendChild(el);
|
|
595
|
+
|
|
596
|
+
expect(getAccessibleText(el)).toBe('');
|
|
597
|
+
});
|
|
598
|
+
});
|
|
525
599
|
});
|
|
@@ -2,159 +2,278 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
2
2
|
import { hasValidAriaRole } from '../src/hasValidAriaRole.js';
|
|
3
3
|
|
|
4
4
|
describe('hasValidAriaRole', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('should return true for an element with a valid ARIA role', () => {
|
|
10
|
-
// Arrange
|
|
11
|
-
const element = document.createElement('div');
|
|
12
|
-
element.setAttribute('role', 'button');
|
|
13
|
-
document.body.appendChild(element);
|
|
14
|
-
|
|
15
|
-
// Act
|
|
16
|
-
const result = hasValidAriaRole(element);
|
|
17
|
-
|
|
18
|
-
// Assert
|
|
19
|
-
expect(result).toBe(true);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should return false for an element with an invalid ARIA role', () => {
|
|
23
|
-
// Arrange
|
|
24
|
-
const element = document.createElement('div');
|
|
25
|
-
element.setAttribute('role', 'invalid-role');
|
|
26
|
-
document.body.appendChild(element);
|
|
27
|
-
|
|
28
|
-
// Act
|
|
29
|
-
const result = hasValidAriaRole(element);
|
|
30
|
-
|
|
31
|
-
// Assert
|
|
32
|
-
expect(result).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should return false for an element without a role attribute', () => {
|
|
36
|
-
// Arrange
|
|
37
|
-
const element = document.createElement('div');
|
|
38
|
-
document.body.appendChild(element);
|
|
39
|
-
|
|
40
|
-
// Act
|
|
41
|
-
const result = hasValidAriaRole(element);
|
|
42
|
-
|
|
43
|
-
// Assert
|
|
44
|
-
expect(result).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should return false for null or non-element input', () => {
|
|
48
|
-
// Act & Assert
|
|
49
|
-
expect(hasValidAriaRole(null)).toBe(false);
|
|
50
|
-
expect(hasValidAriaRole(undefined)).toBe(false);
|
|
51
|
-
expect(hasValidAriaRole({})).toBe(false);
|
|
52
|
-
expect(hasValidAriaRole('')).toBe(false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should correctly handle elements with multiple roles (should use the first one)', () => {
|
|
56
|
-
// Arrange
|
|
57
|
-
const validMultipleRoles = document.createElement('div');
|
|
58
|
-
validMultipleRoles.setAttribute('role', 'button presentation');
|
|
59
|
-
|
|
60
|
-
const invalidMultipleRoles = document.createElement('div');
|
|
61
|
-
invalidMultipleRoles.setAttribute('role', 'invalid-role button');
|
|
62
|
-
|
|
63
|
-
document.body.appendChild(validMultipleRoles);
|
|
64
|
-
document.body.appendChild(invalidMultipleRoles);
|
|
65
|
-
|
|
66
|
-
// Act & Assert
|
|
67
|
-
expect(hasValidAriaRole(validMultipleRoles)).toBe(true);
|
|
68
|
-
expect(hasValidAriaRole(invalidMultipleRoles)).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should handle roles with extra whitespace', () => {
|
|
72
|
-
// Arrange
|
|
73
|
-
const element = document.createElement('div');
|
|
74
|
-
element.setAttribute('role', ' button ');
|
|
75
|
-
document.body.appendChild(element);
|
|
76
|
-
|
|
77
|
-
// Act
|
|
78
|
-
const result = hasValidAriaRole(element);
|
|
79
|
-
|
|
80
|
-
// Assert
|
|
81
|
-
expect(result).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should recognize various valid roles', () => {
|
|
85
|
-
// Test a sample of different valid roles
|
|
86
|
-
const validRoles = ['alert', 'button', 'checkbox', 'dialog', 'navigation', 'main', 'region'];
|
|
87
|
-
|
|
88
|
-
validRoles.forEach(role => {
|
|
89
|
-
// Arrange
|
|
90
|
-
const element = document.createElement('div');
|
|
91
|
-
element.setAttribute('role', role);
|
|
92
|
-
document.body.appendChild(element);
|
|
93
|
-
|
|
94
|
-
// Act & Assert
|
|
95
|
-
expect(hasValidAriaRole(element)).toBe(true);
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
96
7
|
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
8
|
+
|
|
9
|
+
it('should return true for an element with a valid ARIA role', () => {
|
|
10
|
+
// Arrange
|
|
11
|
+
const element = document.createElement('div');
|
|
12
|
+
element.setAttribute('role', 'button');
|
|
13
|
+
document.body.appendChild(element);
|
|
14
|
+
|
|
15
|
+
// Act
|
|
16
|
+
const result = hasValidAriaRole(element);
|
|
17
|
+
|
|
18
|
+
// Assert
|
|
19
|
+
expect(result).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return false for an element with an invalid ARIA role', () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
const element = document.createElement('div');
|
|
25
|
+
element.setAttribute('role', 'invalid-role');
|
|
26
|
+
document.body.appendChild(element);
|
|
27
|
+
|
|
28
|
+
// Act
|
|
29
|
+
const result = hasValidAriaRole(element);
|
|
30
|
+
|
|
31
|
+
// Assert
|
|
32
|
+
expect(result).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return false for an element without a role attribute', () => {
|
|
36
|
+
// Arrange
|
|
37
|
+
const element = document.createElement('div');
|
|
38
|
+
document.body.appendChild(element);
|
|
39
|
+
|
|
40
|
+
// Act
|
|
41
|
+
const result = hasValidAriaRole(element);
|
|
42
|
+
|
|
43
|
+
// Assert
|
|
44
|
+
expect(result).toBe(false);
|
|
111
45
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
compositeRoles.forEach(role => {
|
|
121
|
-
const element = document.createElement('div');
|
|
122
|
-
element.setAttribute('role', role);
|
|
123
|
-
expect(hasValidAriaRole(element)).toBe(true);
|
|
46
|
+
|
|
47
|
+
it('should return false for null or non-element input', () => {
|
|
48
|
+
// Act & Assert
|
|
49
|
+
expect(hasValidAriaRole(null)).toBe(false);
|
|
50
|
+
expect(hasValidAriaRole(undefined)).toBe(false);
|
|
51
|
+
expect(hasValidAriaRole({})).toBe(false);
|
|
52
|
+
expect(hasValidAriaRole('')).toBe(false);
|
|
124
53
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
54
|
+
|
|
55
|
+
it('should correctly handle elements with multiple roles (should use the first one)', () => {
|
|
56
|
+
// Arrange
|
|
57
|
+
const validMultipleRoles = document.createElement('div');
|
|
58
|
+
validMultipleRoles.setAttribute('role', 'button presentation');
|
|
59
|
+
|
|
60
|
+
const invalidMultipleRoles = document.createElement('div');
|
|
61
|
+
invalidMultipleRoles.setAttribute('role', 'invalid-role button');
|
|
62
|
+
|
|
63
|
+
document.body.appendChild(validMultipleRoles);
|
|
64
|
+
document.body.appendChild(invalidMultipleRoles);
|
|
65
|
+
|
|
66
|
+
// Act & Assert
|
|
67
|
+
expect(hasValidAriaRole(validMultipleRoles)).toBe(true);
|
|
68
|
+
expect(hasValidAriaRole(invalidMultipleRoles)).toBe(false);
|
|
139
69
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
70
|
+
|
|
71
|
+
it('should handle roles with extra whitespace', () => {
|
|
72
|
+
// Arrange
|
|
73
|
+
const element = document.createElement('div');
|
|
74
|
+
element.setAttribute('role', ' button ');
|
|
75
|
+
document.body.appendChild(element);
|
|
76
|
+
|
|
77
|
+
// Act
|
|
78
|
+
const result = hasValidAriaRole(element);
|
|
79
|
+
|
|
80
|
+
// Assert
|
|
81
|
+
expect(result).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should recognize various valid roles', () => {
|
|
85
|
+
// Test a sample of different valid roles
|
|
86
|
+
const validRoles = [
|
|
87
|
+
'alert',
|
|
88
|
+
'button',
|
|
89
|
+
'checkbox',
|
|
90
|
+
'dialog',
|
|
91
|
+
'navigation',
|
|
92
|
+
'main',
|
|
93
|
+
'region',
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
validRoles.forEach(role => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const element = document.createElement('div');
|
|
99
|
+
element.setAttribute('role', role);
|
|
100
|
+
document.body.appendChild(element);
|
|
101
|
+
|
|
102
|
+
// Act & Assert
|
|
103
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should recognize all widget roles', () => {
|
|
108
|
+
const widgetRoles = [
|
|
109
|
+
'alertdialog',
|
|
110
|
+
'gridcell',
|
|
111
|
+
'link',
|
|
112
|
+
'log',
|
|
113
|
+
'marquee',
|
|
114
|
+
'menuitem',
|
|
115
|
+
'menuitemcheckbox',
|
|
116
|
+
'menuitemradio',
|
|
117
|
+
'option',
|
|
118
|
+
'progressbar',
|
|
119
|
+
'radio',
|
|
120
|
+
'scrollbar',
|
|
121
|
+
'searchbox',
|
|
122
|
+
'slider',
|
|
123
|
+
'spinbutton',
|
|
124
|
+
'status',
|
|
125
|
+
'switch',
|
|
126
|
+
'tab',
|
|
127
|
+
'tabpanel',
|
|
128
|
+
'textbox',
|
|
129
|
+
'tooltip',
|
|
130
|
+
'treeitem',
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
widgetRoles.forEach(role => {
|
|
134
|
+
const element = document.createElement('div');
|
|
135
|
+
element.setAttribute('role', role);
|
|
136
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should recognize all composite widget roles', () => {
|
|
141
|
+
const compositeRoles = [
|
|
142
|
+
'combobox',
|
|
143
|
+
'grid',
|
|
144
|
+
'listbox',
|
|
145
|
+
'menu',
|
|
146
|
+
'menubar',
|
|
147
|
+
'radiogroup',
|
|
148
|
+
'tablist',
|
|
149
|
+
'tree',
|
|
150
|
+
'treegrid',
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
compositeRoles.forEach(role => {
|
|
154
|
+
const element = document.createElement('div');
|
|
155
|
+
element.setAttribute('role', role);
|
|
156
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should recognize all document structure roles', () => {
|
|
161
|
+
const structureRoles = [
|
|
162
|
+
'article',
|
|
163
|
+
'cell',
|
|
164
|
+
'columnheader',
|
|
165
|
+
'definition',
|
|
166
|
+
'directory',
|
|
167
|
+
'document',
|
|
168
|
+
'feed',
|
|
169
|
+
'figure',
|
|
170
|
+
'group',
|
|
171
|
+
'heading',
|
|
172
|
+
'img',
|
|
173
|
+
'list',
|
|
174
|
+
'listitem',
|
|
175
|
+
'math',
|
|
176
|
+
'none',
|
|
177
|
+
'note',
|
|
178
|
+
'presentation',
|
|
179
|
+
'row',
|
|
180
|
+
'rowgroup',
|
|
181
|
+
'rowheader',
|
|
182
|
+
'separator',
|
|
183
|
+
'table',
|
|
184
|
+
'term',
|
|
185
|
+
'toolbar',
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
structureRoles.forEach(role => {
|
|
189
|
+
const element = document.createElement('div');
|
|
190
|
+
element.setAttribute('role', role);
|
|
191
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should recognize all landmark roles', () => {
|
|
196
|
+
const landmarkRoles = [
|
|
197
|
+
'application',
|
|
198
|
+
'banner',
|
|
199
|
+
'complementary',
|
|
200
|
+
'contentinfo',
|
|
201
|
+
'form',
|
|
202
|
+
'search',
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
landmarkRoles.forEach(role => {
|
|
206
|
+
const element = document.createElement('div');
|
|
207
|
+
element.setAttribute('role', role);
|
|
208
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should recognize timer role', () => {
|
|
213
|
+
const element = document.createElement('div');
|
|
214
|
+
element.setAttribute('role', 'timer');
|
|
215
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should recognize WAI-ARIA Graphics Module roles', () => {
|
|
219
|
+
const graphicsRoles = ['graphics-document', 'graphics-object', 'graphics-symbol'];
|
|
220
|
+
|
|
221
|
+
graphicsRoles.forEach(role => {
|
|
222
|
+
const element = document.createElement('svg');
|
|
223
|
+
element.setAttribute('role', role);
|
|
224
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should recognize DPub-ARIA roles', () => {
|
|
229
|
+
const dpubRoles = [
|
|
230
|
+
'doc-abstract',
|
|
231
|
+
'doc-acknowledgments',
|
|
232
|
+
'doc-afterword',
|
|
233
|
+
'doc-appendix',
|
|
234
|
+
'doc-backlink',
|
|
235
|
+
'doc-biblioentry',
|
|
236
|
+
'doc-bibliography',
|
|
237
|
+
'doc-biblioref',
|
|
238
|
+
'doc-chapter',
|
|
239
|
+
'doc-colophon',
|
|
240
|
+
'doc-conclusion',
|
|
241
|
+
'doc-cover',
|
|
242
|
+
'doc-credit',
|
|
243
|
+
'doc-credits',
|
|
244
|
+
'doc-dedication',
|
|
245
|
+
'doc-endnote',
|
|
246
|
+
'doc-endnotes',
|
|
247
|
+
'doc-epigraph',
|
|
248
|
+
'doc-epilogue',
|
|
249
|
+
'doc-errata',
|
|
250
|
+
'doc-example',
|
|
251
|
+
'doc-footnote',
|
|
252
|
+
'doc-foreword',
|
|
253
|
+
'doc-glossary',
|
|
254
|
+
'doc-glossref',
|
|
255
|
+
'doc-index',
|
|
256
|
+
'doc-introduction',
|
|
257
|
+
'doc-noteref',
|
|
258
|
+
'doc-notice',
|
|
259
|
+
'doc-pagebreak',
|
|
260
|
+
'doc-pagefooter',
|
|
261
|
+
'doc-pageheader',
|
|
262
|
+
'doc-pagelist',
|
|
263
|
+
'doc-part',
|
|
264
|
+
'doc-preface',
|
|
265
|
+
'doc-prologue',
|
|
266
|
+
'doc-pullquote',
|
|
267
|
+
'doc-qna',
|
|
268
|
+
'doc-subtitle',
|
|
269
|
+
'doc-tip',
|
|
270
|
+
'doc-toc',
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
dpubRoles.forEach(role => {
|
|
274
|
+
const element = document.createElement('div');
|
|
275
|
+
element.setAttribute('role', role);
|
|
276
|
+
expect(hasValidAriaRole(element)).toBe(true);
|
|
277
|
+
});
|
|
152
278
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
it('should recognize timer role', () => {
|
|
156
|
-
const element = document.createElement('div');
|
|
157
|
-
element.setAttribute('role', 'timer');
|
|
158
|
-
expect(hasValidAriaRole(element)).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
279
|
+
});
|
package/test/index.test.js
CHANGED
|
@@ -73,6 +73,7 @@ describe('index.js exports', () => {
|
|
|
73
73
|
it('should export focus management utilities', () => {
|
|
74
74
|
expect(utils.getFocusableElements).toBeDefined();
|
|
75
75
|
expect(utils.isFocusable).toBeDefined();
|
|
76
|
+
expect(utils.detectFocusTrap).toBeDefined();
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
it('should export role computation utilities', () => {
|
|
@@ -145,6 +146,7 @@ describe('index.js exports', () => {
|
|
|
145
146
|
'isA11yVisible',
|
|
146
147
|
'hasParent',
|
|
147
148
|
'getFocusableElements',
|
|
149
|
+
'detectFocusTrap',
|
|
148
150
|
'getComputedRole',
|
|
149
151
|
'getImageText',
|
|
150
152
|
'testContrast',
|
|
@@ -183,6 +185,7 @@ describe('index.js exports', () => {
|
|
|
183
185
|
'hasAttribute',
|
|
184
186
|
'getFocusableElements',
|
|
185
187
|
'isFocusable',
|
|
188
|
+
'detectFocusTrap',
|
|
186
189
|
'getComputedRole',
|
|
187
190
|
'getImageText',
|
|
188
191
|
'testContrast',
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
deepGetElementById,
|
|
4
4
|
deepQuerySelector,
|
|
5
5
|
deepQuerySelectorAll,
|
|
6
|
+
getDeepTextContent,
|
|
7
|
+
hasCustomElementDescendant,
|
|
6
8
|
} from '../src/shadowDomUtils.js';
|
|
7
9
|
|
|
8
10
|
describe('shadowDomUtils', () => {
|
|
@@ -245,4 +247,100 @@ describe('shadowDomUtils', () => {
|
|
|
245
247
|
expect(results).toHaveLength(0);
|
|
246
248
|
});
|
|
247
249
|
});
|
|
250
|
+
|
|
251
|
+
// =========================================================================
|
|
252
|
+
// getDeepTextContent
|
|
253
|
+
// =========================================================================
|
|
254
|
+
describe('getDeepTextContent', () => {
|
|
255
|
+
it('should return empty string for null', () => {
|
|
256
|
+
expect(getDeepTextContent(null)).toBe('');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should return text from light DOM', () => {
|
|
260
|
+
document.body.innerHTML = '<div>Hello world</div>';
|
|
261
|
+
const el = document.querySelector('div');
|
|
262
|
+
expect(getDeepTextContent(el)).toBe('Hello world');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should return text from inside an open shadow root', () => {
|
|
266
|
+
const host = document.createElement('div');
|
|
267
|
+
document.body.appendChild(host);
|
|
268
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
269
|
+
const inner = document.createElement('span');
|
|
270
|
+
inner.textContent = 'Shadow content';
|
|
271
|
+
shadow.appendChild(inner);
|
|
272
|
+
|
|
273
|
+
expect(getDeepTextContent(host)).toBe('Shadow content');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should return text from nested shadow roots', () => {
|
|
277
|
+
const host1 = document.createElement('div');
|
|
278
|
+
document.body.appendChild(host1);
|
|
279
|
+
const shadow1 = host1.attachShadow({ mode: 'open' });
|
|
280
|
+
|
|
281
|
+
const host2 = document.createElement('div');
|
|
282
|
+
shadow1.appendChild(host2);
|
|
283
|
+
const shadow2 = host2.attachShadow({ mode: 'open' });
|
|
284
|
+
|
|
285
|
+
const inner = document.createElement('span');
|
|
286
|
+
inner.textContent = 'Deeply nested';
|
|
287
|
+
shadow2.appendChild(inner);
|
|
288
|
+
|
|
289
|
+
expect(getDeepTextContent(host1)).toBe('Deeply nested');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should return empty string for element with no text', () => {
|
|
293
|
+
document.body.innerHTML = '<div><img src="test.png"></div>';
|
|
294
|
+
const el = document.querySelector('div');
|
|
295
|
+
expect(getDeepTextContent(el)).toBe('');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should combine text from shadow and light DOM', () => {
|
|
299
|
+
const host = document.createElement('div');
|
|
300
|
+
host.textContent = 'Light text';
|
|
301
|
+
document.body.appendChild(host);
|
|
302
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
303
|
+
const inner = document.createElement('span');
|
|
304
|
+
inner.textContent = 'Shadow text';
|
|
305
|
+
shadow.appendChild(inner);
|
|
306
|
+
|
|
307
|
+
const result = getDeepTextContent(host);
|
|
308
|
+
expect(result).toContain('Shadow text');
|
|
309
|
+
expect(result).toContain('Light text');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// =========================================================================
|
|
314
|
+
// hasCustomElementDescendant
|
|
315
|
+
// =========================================================================
|
|
316
|
+
describe('hasCustomElementDescendant', () => {
|
|
317
|
+
it('should return false for null', () => {
|
|
318
|
+
expect(hasCustomElementDescendant(null)).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should return false for element with no custom elements', () => {
|
|
322
|
+
document.body.innerHTML = '<div><span>Text</span></div>';
|
|
323
|
+
const el = document.querySelector('div');
|
|
324
|
+
expect(hasCustomElementDescendant(el)).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should return true when the element itself is a custom element', () => {
|
|
328
|
+
const el = document.createElement('my-component');
|
|
329
|
+
document.body.appendChild(el);
|
|
330
|
+
expect(hasCustomElementDescendant(el)).toBe(true);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should return true when a descendant is a custom element', () => {
|
|
334
|
+
document.body.innerHTML = '<div><store-selector-addon></store-selector-addon></div>';
|
|
335
|
+
const el = document.querySelector('div');
|
|
336
|
+
expect(hasCustomElementDescendant(el)).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should return false for elements with hyphens in attributes but not tag names', () => {
|
|
340
|
+
document.body.innerHTML =
|
|
341
|
+
'<div data-my-attr="value"><span class="my-class">Text</span></div>';
|
|
342
|
+
const el = document.querySelector('div');
|
|
343
|
+
expect(hasCustomElementDescendant(el)).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
248
346
|
});
|