@afixt/test-utils 1.3.0 → 2.0.1
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/BROWSER_TESTING.md +42 -22
- package/CHANGELOG.md +40 -0
- package/CLAUDE.md +10 -9
- package/package.json +1 -1
- package/src/constants.js +438 -1
- package/src/domUtils.js +17 -38
- package/src/formUtils.js +7 -24
- package/src/getAccessibleName.js +13 -71
- package/src/getCSSGeneratedContent.js +2 -0
- package/src/getFocusableElements.js +12 -21
- package/src/getGeneratedContent.js +18 -11
- package/src/getImageText.js +22 -7
- package/src/hasValidAriaRole.js +11 -19
- package/src/index.js +4 -4
- package/src/interactiveRoles.js +2 -19
- package/src/isA11yVisible.js +95 -0
- package/src/isAriaAttributesValid.js +5 -64
- package/src/isFocusable.js +30 -10
- package/src/isHidden.js +44 -8
- package/src/listEventListeners.js +115 -10
- package/src/stringUtils.js +54 -98
- package/src/tableUtils.js +4 -36
- package/test/domUtils.test.js +156 -0
- package/test/formUtils.test.js +0 -47
- package/test/getGeneratedContent.test.js +305 -241
- package/test/getImageText.test.js +158 -99
- package/test/index.test.js +54 -17
- package/test/{isVisible.test.js → isA11yVisible.test.js} +39 -33
- package/test/isFocusable.test.js +265 -272
- package/test/isHidden.test.js +257 -153
- package/test/listEventListeners.test.js +163 -44
- package/test/playwright/css-pseudo-elements.spec.js +3 -13
- package/test/stringUtils.test.js +115 -228
- package/todo.md +2 -2
- package/src/isVisible.js +0 -103
package/test/isFocusable.test.js
CHANGED
|
@@ -1,275 +1,268 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { isFocusable } from '../src/isFocusable.js';
|
|
3
3
|
|
|
4
4
|
describe('isFocusable', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
area.setAttribute('tabindex', '-1');
|
|
270
|
-
document.body.appendChild(area);
|
|
271
|
-
|
|
272
|
-
// Act & Assert
|
|
273
|
-
expect(isFocusable(area)).toBe(false);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return false for null or undefined elements', () => {
|
|
10
|
+
expect(isFocusable(null)).toBe(false);
|
|
11
|
+
expect(isFocusable(undefined)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return true for focusable input elements', () => {
|
|
15
|
+
const input = document.createElement('input');
|
|
16
|
+
input.type = 'text';
|
|
17
|
+
document.body.appendChild(input);
|
|
18
|
+
|
|
19
|
+
expect(isFocusable(input)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return false for disabled input elements', () => {
|
|
23
|
+
const input = document.createElement('input');
|
|
24
|
+
input.type = 'text';
|
|
25
|
+
input.disabled = true;
|
|
26
|
+
document.body.appendChild(input);
|
|
27
|
+
|
|
28
|
+
expect(isFocusable(input)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should return true for focusable button elements', () => {
|
|
32
|
+
const button = document.createElement('button');
|
|
33
|
+
document.body.appendChild(button);
|
|
34
|
+
|
|
35
|
+
expect(isFocusable(button)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return false for disabled button elements', () => {
|
|
39
|
+
const button = document.createElement('button');
|
|
40
|
+
button.disabled = true;
|
|
41
|
+
document.body.appendChild(button);
|
|
42
|
+
|
|
43
|
+
expect(isFocusable(button)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return true for select elements', () => {
|
|
47
|
+
const select = document.createElement('select');
|
|
48
|
+
document.body.appendChild(select);
|
|
49
|
+
|
|
50
|
+
expect(isFocusable(select)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should return true for textarea elements', () => {
|
|
54
|
+
const textarea = document.createElement('textarea');
|
|
55
|
+
document.body.appendChild(textarea);
|
|
56
|
+
|
|
57
|
+
expect(isFocusable(textarea)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return true for anchors with href attribute', () => {
|
|
61
|
+
const anchor = document.createElement('a');
|
|
62
|
+
anchor.setAttribute('href', '#');
|
|
63
|
+
document.body.appendChild(anchor);
|
|
64
|
+
|
|
65
|
+
expect(isFocusable(anchor)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return false for anchors without href attribute', () => {
|
|
69
|
+
const anchor = document.createElement('a');
|
|
70
|
+
document.body.appendChild(anchor);
|
|
71
|
+
|
|
72
|
+
expect(isFocusable(anchor)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return true for area elements with href attribute', () => {
|
|
76
|
+
const map = document.createElement('map');
|
|
77
|
+
const area = document.createElement('area');
|
|
78
|
+
area.setAttribute('href', '#');
|
|
79
|
+
map.appendChild(area);
|
|
80
|
+
document.body.appendChild(map);
|
|
81
|
+
|
|
82
|
+
expect(isFocusable(area)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return false for area elements without href attribute', () => {
|
|
86
|
+
const map = document.createElement('map');
|
|
87
|
+
const area = document.createElement('area');
|
|
88
|
+
map.appendChild(area);
|
|
89
|
+
document.body.appendChild(map);
|
|
90
|
+
|
|
91
|
+
expect(isFocusable(area)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should return true for non-form elements with tabindex >= 0', () => {
|
|
95
|
+
const div = document.createElement('div');
|
|
96
|
+
div.setAttribute('tabindex', '0');
|
|
97
|
+
document.body.appendChild(div);
|
|
98
|
+
|
|
99
|
+
expect(isFocusable(div)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should return true for span with tabindex="0"', () => {
|
|
103
|
+
const span = document.createElement('span');
|
|
104
|
+
span.setAttribute('tabindex', '0');
|
|
105
|
+
document.body.appendChild(span);
|
|
106
|
+
|
|
107
|
+
expect(isFocusable(span)).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should return false for elements with tabindex < 0 (not in tab order)', () => {
|
|
111
|
+
const div = document.createElement('div');
|
|
112
|
+
div.setAttribute('tabindex', '-1');
|
|
113
|
+
document.body.appendChild(div);
|
|
114
|
+
|
|
115
|
+
expect(isFocusable(div)).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should return false for regular divs without tabindex', () => {
|
|
119
|
+
const div = document.createElement('div');
|
|
120
|
+
document.body.appendChild(div);
|
|
121
|
+
|
|
122
|
+
expect(isFocusable(div)).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return false for elements with hidden attribute', () => {
|
|
126
|
+
const input = document.createElement('input');
|
|
127
|
+
input.setAttribute('hidden', '');
|
|
128
|
+
document.body.appendChild(input);
|
|
129
|
+
|
|
130
|
+
expect(isFocusable(input)).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should return false for elements with display:none', () => {
|
|
134
|
+
const input = document.createElement('input');
|
|
135
|
+
input.style.display = 'none';
|
|
136
|
+
document.body.appendChild(input);
|
|
137
|
+
|
|
138
|
+
expect(isFocusable(input)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should return false for elements inside a hidden ancestor', () => {
|
|
142
|
+
const wrapper = document.createElement('div');
|
|
143
|
+
wrapper.setAttribute('hidden', '');
|
|
144
|
+
const input = document.createElement('input');
|
|
145
|
+
wrapper.appendChild(input);
|
|
146
|
+
document.body.appendChild(wrapper);
|
|
147
|
+
|
|
148
|
+
expect(isFocusable(input)).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should return true for object elements', () => {
|
|
152
|
+
const object = document.createElement('object');
|
|
153
|
+
document.body.appendChild(object);
|
|
154
|
+
|
|
155
|
+
expect(isFocusable(object)).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should return false for disabled object elements', () => {
|
|
159
|
+
const object = document.createElement('object');
|
|
160
|
+
object.disabled = true;
|
|
161
|
+
document.body.appendChild(object);
|
|
162
|
+
|
|
163
|
+
expect(isFocusable(object)).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle elements with tabindex="0" on form controls', () => {
|
|
167
|
+
const input = document.createElement('input');
|
|
168
|
+
input.setAttribute('tabindex', '0');
|
|
169
|
+
document.body.appendChild(input);
|
|
170
|
+
|
|
171
|
+
expect(isFocusable(input)).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should handle elements with positive tabindex on form controls', () => {
|
|
175
|
+
const button = document.createElement('button');
|
|
176
|
+
button.setAttribute('tabindex', '1');
|
|
177
|
+
document.body.appendChild(button);
|
|
178
|
+
|
|
179
|
+
expect(isFocusable(button)).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should return false for form controls with negative tabindex', () => {
|
|
183
|
+
const input = document.createElement('input');
|
|
184
|
+
input.setAttribute('tabindex', '-1');
|
|
185
|
+
document.body.appendChild(input);
|
|
186
|
+
|
|
187
|
+
expect(isFocusable(input)).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should return false for disabled select elements', () => {
|
|
191
|
+
const select = document.createElement('select');
|
|
192
|
+
select.disabled = true;
|
|
193
|
+
document.body.appendChild(select);
|
|
194
|
+
|
|
195
|
+
expect(isFocusable(select)).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should return false for disabled textarea elements', () => {
|
|
199
|
+
const textarea = document.createElement('textarea');
|
|
200
|
+
textarea.disabled = true;
|
|
201
|
+
document.body.appendChild(textarea);
|
|
202
|
+
|
|
203
|
+
expect(isFocusable(textarea)).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should handle anchors with tabindex >= 0 and href', () => {
|
|
207
|
+
const anchor = document.createElement('a');
|
|
208
|
+
anchor.setAttribute('href', 'https://example.com');
|
|
209
|
+
anchor.setAttribute('tabindex', '0');
|
|
210
|
+
document.body.appendChild(anchor);
|
|
211
|
+
|
|
212
|
+
expect(isFocusable(anchor)).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should return false for anchors with negative tabindex even with href', () => {
|
|
216
|
+
const anchor = document.createElement('a');
|
|
217
|
+
anchor.setAttribute('href', 'https://example.com');
|
|
218
|
+
anchor.setAttribute('tabindex', '-1');
|
|
219
|
+
document.body.appendChild(anchor);
|
|
220
|
+
|
|
221
|
+
expect(isFocusable(anchor)).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return false for area with negative tabindex even with href', () => {
|
|
225
|
+
const area = document.createElement('area');
|
|
226
|
+
area.setAttribute('href', '#map');
|
|
227
|
+
area.setAttribute('tabindex', '-1');
|
|
228
|
+
document.body.appendChild(area);
|
|
229
|
+
|
|
230
|
+
expect(isFocusable(area)).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return true for contenteditable="true" elements', () => {
|
|
234
|
+
const div = document.createElement('div');
|
|
235
|
+
div.setAttribute('contenteditable', 'true');
|
|
236
|
+
document.body.appendChild(div);
|
|
237
|
+
|
|
238
|
+
expect(isFocusable(div)).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should return true for contenteditable="" elements', () => {
|
|
242
|
+
const div = document.createElement('div');
|
|
243
|
+
div.setAttribute('contenteditable', '');
|
|
244
|
+
document.body.appendChild(div);
|
|
245
|
+
|
|
246
|
+
expect(isFocusable(div)).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should return false for hidden contenteditable elements', () => {
|
|
250
|
+
const div = document.createElement('div');
|
|
251
|
+
div.setAttribute('contenteditable', 'true');
|
|
252
|
+
div.setAttribute('hidden', '');
|
|
253
|
+
document.body.appendChild(div);
|
|
254
|
+
|
|
255
|
+
expect(isFocusable(div)).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should return false for contenteditable inside hidden ancestor', () => {
|
|
259
|
+
const wrapper = document.createElement('div');
|
|
260
|
+
wrapper.style.display = 'none';
|
|
261
|
+
const div = document.createElement('div');
|
|
262
|
+
div.setAttribute('contenteditable', 'true');
|
|
263
|
+
wrapper.appendChild(div);
|
|
264
|
+
document.body.appendChild(wrapper);
|
|
265
|
+
|
|
266
|
+
expect(isFocusable(div)).toBe(false);
|
|
267
|
+
});
|
|
268
|
+
});
|