@afixt/test-utils 1.2.3 → 2.0.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 (39) hide show
  1. package/.claude/settings.local.json +1 -6
  2. package/BROWSER_TESTING.md +42 -22
  3. package/CHANGELOG.md +40 -0
  4. package/CLAUDE.md +10 -9
  5. package/package.json +1 -1
  6. package/src/constants.js +438 -1
  7. package/src/domUtils.js +17 -38
  8. package/src/formUtils.js +7 -24
  9. package/src/getAccessibleName.js +20 -56
  10. package/src/getCSSGeneratedContent.js +2 -0
  11. package/src/getFocusableElements.js +12 -21
  12. package/src/getGeneratedContent.js +18 -11
  13. package/src/getImageText.js +22 -7
  14. package/src/hasValidAriaRole.js +11 -19
  15. package/src/index.js +4 -4
  16. package/src/interactiveRoles.js +2 -19
  17. package/src/isA11yVisible.js +95 -0
  18. package/src/isAriaAttributesValid.js +5 -64
  19. package/src/isFocusable.js +30 -10
  20. package/src/isHidden.js +44 -8
  21. package/src/listEventListeners.js +115 -10
  22. package/src/stringUtils.js +19 -98
  23. package/src/tableUtils.js +4 -36
  24. package/src/testContrast.js +54 -0
  25. package/test/domUtils.test.js +156 -0
  26. package/test/formUtils.test.js +0 -47
  27. package/test/getAccessibleName.test.js +39 -0
  28. package/test/getGeneratedContent.test.js +305 -241
  29. package/test/getImageText.test.js +158 -99
  30. package/test/index.test.js +54 -17
  31. package/test/{isVisible.test.js → isA11yVisible.test.js} +39 -33
  32. package/test/isFocusable.test.js +265 -272
  33. package/test/isHidden.test.js +257 -153
  34. package/test/listEventListeners.test.js +163 -44
  35. package/test/playwright/css-pseudo-elements.spec.js +3 -13
  36. package/test/stringUtils.test.js +55 -228
  37. package/test/testContrast.test.js +104 -2
  38. package/todo.md +2 -2
  39. package/src/isVisible.js +0 -103
@@ -30,9 +30,9 @@ describe('listEventListeners', () => {
30
30
  it('should track added event listeners', () => {
31
31
  const element = document.createElement('button');
32
32
  const listener = vi.fn();
33
-
33
+
34
34
  element.addEventListener('click', listener);
35
-
35
+
36
36
  const listeners = getEventListeners(element);
37
37
  expect(listeners.click).toBeDefined();
38
38
  expect(listeners.click.length).toBe(1);
@@ -43,10 +43,10 @@ describe('listEventListeners', () => {
43
43
  const element = document.createElement('div');
44
44
  const listener1 = vi.fn();
45
45
  const listener2 = vi.fn();
46
-
46
+
47
47
  element.addEventListener('click', listener1);
48
48
  element.addEventListener('click', listener2);
49
-
49
+
50
50
  const listeners = getEventListeners(element);
51
51
  expect(listeners.click.length).toBe(2);
52
52
  expect(listeners.click[0].listener).toBe(listener1);
@@ -57,10 +57,10 @@ describe('listEventListeners', () => {
57
57
  const element = document.createElement('input');
58
58
  const clickListener = vi.fn();
59
59
  const focusListener = vi.fn();
60
-
60
+
61
61
  element.addEventListener('click', clickListener);
62
62
  element.addEventListener('focus', focusListener);
63
-
63
+
64
64
  const listeners = getEventListeners(element);
65
65
  expect(listeners.click).toBeDefined();
66
66
  expect(listeners.focus).toBeDefined();
@@ -72,9 +72,9 @@ describe('listEventListeners', () => {
72
72
  const element = document.createElement('div');
73
73
  const listener = vi.fn();
74
74
  const options = { capture: true, once: true };
75
-
75
+
76
76
  element.addEventListener('click', listener, options);
77
-
77
+
78
78
  const listeners = getEventListeners(element);
79
79
  expect(listeners.click[0].options).toEqual(options);
80
80
  });
@@ -82,9 +82,9 @@ describe('listEventListeners', () => {
82
82
  it('should call original addEventListener', () => {
83
83
  const element = document.createElement('button');
84
84
  const listener = vi.fn();
85
-
85
+
86
86
  element.addEventListener('click', listener);
87
-
87
+
88
88
  // Verify the event listener was actually attached by triggering it
89
89
  element.click();
90
90
  expect(listener).toHaveBeenCalled();
@@ -95,10 +95,10 @@ describe('listEventListeners', () => {
95
95
  it('should remove tracked event listeners', () => {
96
96
  const element = document.createElement('button');
97
97
  const listener = vi.fn();
98
-
98
+
99
99
  element.addEventListener('click', listener);
100
100
  element.removeEventListener('click', listener);
101
-
101
+
102
102
  const listeners = getEventListeners(element);
103
103
  expect(listeners.click.length).toBe(0);
104
104
  });
@@ -107,11 +107,11 @@ describe('listEventListeners', () => {
107
107
  const element = document.createElement('div');
108
108
  const listener1 = vi.fn();
109
109
  const listener2 = vi.fn();
110
-
110
+
111
111
  element.addEventListener('click', listener1);
112
112
  element.addEventListener('click', listener2);
113
113
  element.removeEventListener('click', listener1);
114
-
114
+
115
115
  const listeners = getEventListeners(element);
116
116
  expect(listeners.click.length).toBe(1);
117
117
  expect(listeners.click[0].listener).toBe(listener2);
@@ -120,7 +120,7 @@ describe('listEventListeners', () => {
120
120
  it('should handle removing non-existent listeners gracefully', () => {
121
121
  const element = document.createElement('button');
122
122
  const listener = vi.fn();
123
-
123
+
124
124
  expect(() => {
125
125
  element.removeEventListener('click', listener);
126
126
  }).not.toThrow();
@@ -129,10 +129,10 @@ describe('listEventListeners', () => {
129
129
  it('should call original removeEventListener', () => {
130
130
  const element = document.createElement('button');
131
131
  const listener = vi.fn();
132
-
132
+
133
133
  element.addEventListener('click', listener);
134
134
  element.removeEventListener('click', listener);
135
-
135
+
136
136
  // Verify the event listener was actually removed by triggering it
137
137
  element.click();
138
138
  expect(listener).not.toHaveBeenCalled();
@@ -144,10 +144,10 @@ describe('listEventListeners', () => {
144
144
  const element = document.createElement('div');
145
145
  element.id = 'test-id';
146
146
  document.body.appendChild(element);
147
-
147
+
148
148
  const result = listEventListeners(document.body);
149
149
  element.addEventListener('click', () => {});
150
-
150
+
151
151
  const listeners = listEventListeners(document.body);
152
152
  expect(listeners[0].xpath).toBe('//*[@id="test-id"]');
153
153
  });
@@ -157,9 +157,9 @@ describe('listEventListeners', () => {
157
157
  const child = document.createElement('span');
158
158
  container.appendChild(child);
159
159
  document.body.appendChild(container);
160
-
160
+
161
161
  child.addEventListener('click', () => {});
162
-
162
+
163
163
  const listeners = listEventListeners(document.body);
164
164
  const spanListener = listeners.find(l => l.element === 'span');
165
165
  expect(spanListener.xpath).toMatch(/^\/html\[1\]\/body\[1\]\/div\[1\]\/span\[1\]$/);
@@ -170,14 +170,14 @@ describe('listEventListeners', () => {
170
170
  const span1 = document.createElement('span');
171
171
  const span2 = document.createElement('span');
172
172
  const span3 = document.createElement('span');
173
-
173
+
174
174
  container.appendChild(span1);
175
175
  container.appendChild(span2);
176
176
  container.appendChild(span3);
177
177
  document.body.appendChild(container);
178
-
178
+
179
179
  span2.addEventListener('click', () => {});
180
-
180
+
181
181
  const listeners = listEventListeners(document.body);
182
182
  const spanListener = listeners.find(l => l.element === 'span');
183
183
  expect(spanListener.xpath).toMatch(/span\[2\]/);
@@ -188,7 +188,7 @@ describe('listEventListeners', () => {
188
188
  it('should return empty object for elements without listeners', () => {
189
189
  const element = document.createElement('div');
190
190
  const listeners = getEventListeners(element);
191
-
191
+
192
192
  expect(listeners).toEqual({});
193
193
  });
194
194
 
@@ -196,10 +196,10 @@ describe('listEventListeners', () => {
196
196
  const element = document.createElement('button');
197
197
  const clickListener = vi.fn();
198
198
  const focusListener = vi.fn();
199
-
199
+
200
200
  element.addEventListener('click', clickListener);
201
201
  element.addEventListener('focus', focusListener);
202
-
202
+
203
203
  const listeners = getEventListeners(element);
204
204
  expect(Object.keys(listeners)).toHaveLength(2);
205
205
  expect(listeners.click).toBeDefined();
@@ -216,13 +216,14 @@ describe('listEventListeners', () => {
216
216
  it('should list event listeners on root element', () => {
217
217
  const listener = vi.fn();
218
218
  document.body.addEventListener('click', listener);
219
-
219
+
220
220
  const result = listEventListeners(document.body);
221
221
  expect(result).toHaveLength(1);
222
222
  expect(result[0]).toEqual({
223
223
  element: 'body',
224
224
  xpath: expect.any(String),
225
- event: 'click'
225
+ event: 'click',
226
+ bindingType: 'addEventListener',
226
227
  });
227
228
  });
228
229
 
@@ -231,29 +232,30 @@ describe('listEventListeners', () => {
231
232
  const button = document.createElement('button');
232
233
  div.appendChild(button);
233
234
  document.body.appendChild(div);
234
-
235
+
235
236
  button.addEventListener('click', vi.fn());
236
-
237
+
237
238
  const result = listEventListeners(document.body);
238
239
  expect(result).toHaveLength(1);
239
240
  expect(result[0]).toEqual({
240
241
  element: 'button',
241
242
  xpath: expect.any(String),
242
- event: 'click'
243
+ event: 'click',
244
+ bindingType: 'addEventListener',
243
245
  });
244
246
  });
245
247
 
246
248
  it('should list multiple event types on same element', () => {
247
249
  const input = document.createElement('input');
248
250
  document.body.appendChild(input);
249
-
251
+
250
252
  input.addEventListener('click', vi.fn());
251
253
  input.addEventListener('focus', vi.fn());
252
254
  input.addEventListener('blur', vi.fn());
253
-
255
+
254
256
  const result = listEventListeners(document.body);
255
257
  expect(result).toHaveLength(3);
256
-
258
+
257
259
  const events = result.map(r => r.event).sort();
258
260
  expect(events).toEqual(['blur', 'click', 'focus']);
259
261
  });
@@ -262,18 +264,18 @@ describe('listEventListeners', () => {
262
264
  const div1 = document.createElement('div');
263
265
  const div2 = document.createElement('div');
264
266
  const button = document.createElement('button');
265
-
267
+
266
268
  div1.appendChild(button);
267
269
  document.body.appendChild(div1);
268
270
  document.body.appendChild(div2);
269
-
271
+
270
272
  div1.addEventListener('mouseover', vi.fn());
271
273
  div2.addEventListener('mouseout', vi.fn());
272
274
  button.addEventListener('click', vi.fn());
273
-
275
+
274
276
  const result = listEventListeners(document.body);
275
277
  expect(result).toHaveLength(3);
276
-
278
+
277
279
  const elements = result.map(r => r.element).sort();
278
280
  expect(elements).toEqual(['button', 'div', 'div']);
279
281
  });
@@ -282,7 +284,7 @@ describe('listEventListeners', () => {
282
284
  const button = document.createElement('button');
283
285
  document.body.appendChild(button);
284
286
  button.addEventListener('click', vi.fn());
285
-
287
+
286
288
  const result = listEventListeners();
287
289
  const buttonListener = result.find(l => l.element === 'button');
288
290
  expect(buttonListener).toBeDefined();
@@ -293,18 +295,135 @@ describe('listEventListeners', () => {
293
295
  const container = document.createElement('div');
294
296
  const innerDiv = document.createElement('div');
295
297
  const button = document.createElement('button');
296
-
298
+
297
299
  innerDiv.appendChild(button);
298
300
  container.appendChild(innerDiv);
299
301
  document.body.appendChild(container);
300
-
302
+
301
303
  // Add listeners to elements inside and outside container
302
304
  button.addEventListener('click', vi.fn());
303
305
  document.body.addEventListener('scroll', vi.fn());
304
-
306
+
305
307
  const result = listEventListeners(container);
306
308
  expect(result).toHaveLength(1);
307
309
  expect(result[0].element).toBe('button');
308
310
  });
309
311
  });
310
- });
312
+
313
+ describe('inline attribute detection', () => {
314
+ it('should detect inline onclick attribute', () => {
315
+ const button = document.createElement('button');
316
+ button.setAttribute('onclick', 'alert("hi")');
317
+ document.body.appendChild(button);
318
+
319
+ const result = listEventListeners(document.body);
320
+ expect(result).toHaveLength(1);
321
+ expect(result[0]).toEqual({
322
+ element: 'button',
323
+ xpath: expect.any(String),
324
+ event: 'click',
325
+ bindingType: 'attribute',
326
+ });
327
+ });
328
+
329
+ it('should detect multiple inline attributes on same element', () => {
330
+ const div = document.createElement('div');
331
+ div.setAttribute('onclick', 'doClick()');
332
+ div.setAttribute('onmouseover', 'doHover()');
333
+ div.setAttribute('onfocus', 'doFocus()');
334
+ document.body.appendChild(div);
335
+
336
+ const result = listEventListeners(document.body);
337
+ expect(result).toHaveLength(3);
338
+
339
+ const events = result.map(r => r.event).sort();
340
+ expect(events).toEqual(['click', 'focus', 'mouseover']);
341
+ result.forEach(r => {
342
+ expect(r.bindingType).toBe('attribute');
343
+ });
344
+ });
345
+ });
346
+
347
+ describe('property-based handler detection', () => {
348
+ it('should detect el.onclick = fn property handler', () => {
349
+ const button = document.createElement('button');
350
+ button.onclick = () => {};
351
+ document.body.appendChild(button);
352
+
353
+ const result = listEventListeners(document.body);
354
+ expect(result).toHaveLength(1);
355
+ expect(result[0]).toEqual({
356
+ element: 'button',
357
+ xpath: expect.any(String),
358
+ event: 'click',
359
+ bindingType: 'property',
360
+ });
361
+ });
362
+ });
363
+
364
+ describe('bindingType field', () => {
365
+ it('should add bindingType addEventListener for tracked listeners', () => {
366
+ const button = document.createElement('button');
367
+ document.body.appendChild(button);
368
+ button.addEventListener('click', vi.fn());
369
+
370
+ const result = listEventListeners(document.body);
371
+ expect(result).toHaveLength(1);
372
+ expect(result[0].bindingType).toBe('addEventListener');
373
+ });
374
+
375
+ it('should not double-count attribute handler as property handler', () => {
376
+ const button = document.createElement('button');
377
+ button.setAttribute('onclick', 'doClick()');
378
+ document.body.appendChild(button);
379
+
380
+ const result = listEventListeners(document.body);
381
+ const clickEntries = result.filter(r => r.event === 'click');
382
+ expect(clickEntries).toHaveLength(1);
383
+ expect(clickEntries[0].bindingType).toBe('attribute');
384
+ });
385
+
386
+ it('should detect all three binding types on one element', () => {
387
+ const button = document.createElement('button');
388
+ button.setAttribute('onmouseover', 'doHover()');
389
+ button.onfocus = () => {};
390
+ document.body.appendChild(button);
391
+ button.addEventListener('click', vi.fn());
392
+
393
+ const result = listEventListeners(document.body);
394
+ expect(result).toHaveLength(3);
395
+
396
+ const byEvent = {};
397
+ result.forEach(r => {
398
+ byEvent[r.event] = r.bindingType;
399
+ });
400
+
401
+ expect(byEvent.click).toBe('addEventListener');
402
+ expect(byEvent.mouseover).toBe('attribute');
403
+ expect(byEvent.focus).toBe('property');
404
+ });
405
+ });
406
+
407
+ describe('getEventListeners with inline/property handlers', () => {
408
+ it('should include attribute handlers in getEventListeners result', () => {
409
+ const button = document.createElement('button');
410
+ button.setAttribute('onclick', 'doClick()');
411
+
412
+ const listeners = getEventListeners(button);
413
+ expect(listeners.click).toBeDefined();
414
+ expect(listeners.click.length).toBeGreaterThanOrEqual(1);
415
+ const attrEntry = listeners.click.find(l => l.bindingType === 'attribute');
416
+ expect(attrEntry).toBeDefined();
417
+ });
418
+
419
+ it('should include property handlers in getEventListeners result', () => {
420
+ const button = document.createElement('button');
421
+ button.onclick = () => {};
422
+
423
+ const listeners = getEventListeners(button);
424
+ expect(listeners.click).toBeDefined();
425
+ const propEntry = listeners.click.find(l => l.bindingType === 'property');
426
+ expect(propEntry).toBeDefined();
427
+ });
428
+ });
429
+ });
@@ -18,22 +18,12 @@ test.describe('CSS Pseudo-element Support', () => {
18
18
  await page.addScriptTag({
19
19
  content: `
20
20
  // getGeneratedContent: includes textContent alongside pseudo-element content
21
+ // Delegates to getCSSGeneratedContent for pseudo-element retrieval
21
22
  function getGeneratedContent(el) {
22
23
  if (!el) return false;
23
-
24
- const beforeStyle = window.getComputedStyle(el, '::before');
25
- const afterStyle = window.getComputedStyle(el, '::after');
26
-
27
- let before = beforeStyle.getPropertyValue('content') || '';
28
- let after = afterStyle.getPropertyValue('content') || '';
24
+ const before = getCSSGeneratedContent(el, 'before') || '';
29
25
  const inner = el.textContent || '';
30
-
31
- before = before.replace(/^["']|["']$/g, '');
32
- after = after.replace(/^["']|["']$/g, '');
33
-
34
- if (before === 'none') before = '';
35
- if (after === 'none') after = '';
36
-
26
+ const after = getCSSGeneratedContent(el, 'after') || '';
37
27
  const result = [before, inner, after].filter(Boolean).join(' ').trim();
38
28
  return result || false;
39
29
  }