@afixt/test-utils 2.3.0 → 2.5.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/.claude/settings.local.json +2 -1
- package/package.json +1 -1
- package/src/constants.js +46 -0
- package/src/cssUtils.js +116 -0
- package/src/detectFocusTrap.js +484 -0
- package/src/getAccessibleText.js +26 -1
- package/src/index.js +2 -0
- package/test/cssUtils.test.js +252 -0
- package/test/detectFocusTrap.test.js +1004 -0
- package/test/getAccessibleText.test.js +26 -0
- package/test/hasValidAriaRole.test.js +270 -151
- package/test/index.test.js +3 -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');
|
|
@@ -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',
|