@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
@@ -1,4 +1,4 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
2
  import stringUtils from '../src/stringUtils.js';
3
3
 
4
4
  describe('stringUtils', () => {
@@ -152,151 +152,27 @@ describe('stringUtils', () => {
152
152
  });
153
153
  });
154
154
 
155
- describe('getAllText', () => {
156
- it('should extract text from simple text nodes', () => {
157
- const div = document.createElement('div');
158
- div.textContent = 'Hello World';
159
-
160
- expect(stringUtils.getAllText(div)).toBe('Hello World');
161
- });
162
-
163
- it('should extract text from nested elements', () => {
164
- const div = document.createElement('div');
165
- const span = document.createElement('span');
166
- span.textContent = 'Hello';
167
- const text = document.createTextNode(' World');
168
-
169
- div.appendChild(span);
170
- div.appendChild(text);
171
-
172
- expect(stringUtils.getAllText(div)).toBe('Hello World');
173
- });
174
-
175
- it('should extract aria-label attributes', () => {
176
- const div = document.createElement('div');
177
- const button = document.createElement('button');
178
- button.setAttribute('aria-label', 'Close dialog');
179
- button.textContent = 'X';
180
-
181
- div.appendChild(button);
182
-
183
- const result = stringUtils.getAllText(div);
184
- expect(result).toContain('Close dialog');
185
- expect(result).toContain('X');
186
- });
187
-
188
- it('should extract alt attributes from images', () => {
189
- const div = document.createElement('div');
190
- const img = document.createElement('img');
191
- img.setAttribute('alt', 'Profile picture');
192
- img.setAttribute('src', 'profile.jpg');
193
-
194
- div.appendChild(img);
195
-
196
- expect(stringUtils.getAllText(div)).toBe('Profile picture');
197
- });
198
-
199
- it('should handle mixed content with text, aria-labels, and alt text', () => {
200
- const div = document.createElement('div');
201
-
202
- // Add some text
203
- const textNode = document.createTextNode('Welcome ');
204
- div.appendChild(textNode);
205
-
206
- // Add element with aria-label
207
- const button = document.createElement('button');
208
- button.setAttribute('aria-label', 'Help');
209
- button.textContent = '?';
210
- div.appendChild(button);
211
-
212
- // Add some more text
213
- const moreText = document.createTextNode(' to our site ');
214
- div.appendChild(moreText);
215
-
216
- // Add image with alt text
217
- const img = document.createElement('img');
218
- img.setAttribute('alt', 'Company logo');
219
- div.appendChild(img);
220
-
221
- const result = stringUtils.getAllText(div);
222
- expect(result).toContain('Welcome');
223
- expect(result).toContain('Help');
224
- expect(result).toContain('?');
225
- expect(result).toContain('to our site');
226
- expect(result).toContain('Company logo');
227
- });
228
-
229
- it('should handle empty elements', () => {
230
- const div = document.createElement('div');
231
- expect(stringUtils.getAllText(div)).toBe('');
232
- });
233
-
234
- it('should trim whitespace from text nodes', () => {
235
- const div = document.createElement('div');
236
- const textNode = document.createTextNode(' Hello World ');
237
- div.appendChild(textNode);
238
-
239
- expect(stringUtils.getAllText(div)).toBe('Hello World');
240
- });
241
-
242
- it('should handle elements with only whitespace', () => {
243
- const div = document.createElement('div');
244
- const textNode = document.createTextNode(' ');
245
- div.appendChild(textNode);
246
-
247
- // Should use textContent.trim() as fallback for empty text
248
- expect(stringUtils.getAllText(div)).toBe('');
249
- });
250
-
251
- it('should handle deeply nested structures', () => {
252
- const div = document.createElement('div');
253
- const section = document.createElement('section');
254
- const article = document.createElement('article');
255
- const p = document.createElement('p');
256
-
257
- p.textContent = 'Deep content';
258
- article.appendChild(p);
259
- section.appendChild(article);
260
- div.appendChild(section);
261
-
262
- expect(stringUtils.getAllText(div)).toBe('Deep content');
263
- });
264
-
265
- it('should handle images without alt attributes', () => {
266
- const div = document.createElement('div');
267
- const img = document.createElement('img');
268
- img.setAttribute('src', 'image.jpg');
269
- // No alt attribute
270
-
271
- const text = document.createTextNode('Some text');
272
- div.appendChild(text);
273
- div.appendChild(img);
274
-
275
- expect(stringUtils.getAllText(div)).toBe('Some text');
155
+ describe('hasText', () => {
156
+ afterEach(() => {
157
+ document.body.innerHTML = '';
276
158
  });
277
159
 
278
- it('should handle elements without aria-label', () => {
279
- const div = document.createElement('div');
280
- const button = document.createElement('button');
281
- button.textContent = 'Click me';
282
- // No aria-label attribute
283
-
284
- div.appendChild(button);
285
-
286
- expect(stringUtils.getAllText(div)).toBe('Click me');
160
+ it('should return false for null or undefined', () => {
161
+ expect(stringUtils.hasText(null)).toBe(false);
162
+ expect(stringUtils.hasText(undefined)).toBe(false);
287
163
  });
288
- });
289
164
 
290
- describe('hasText', () => {
291
165
  it('should return true for elements with text content', () => {
292
166
  const div = document.createElement('div');
293
167
  div.textContent = 'Hello World';
168
+ document.body.appendChild(div);
294
169
 
295
170
  expect(stringUtils.hasText(div)).toBe(true);
296
171
  });
297
172
 
298
173
  it('should return false for empty elements', () => {
299
174
  const div = document.createElement('div');
175
+ document.body.appendChild(div);
300
176
 
301
177
  expect(stringUtils.hasText(div)).toBe(false);
302
178
  });
@@ -304,16 +180,15 @@ describe('stringUtils', () => {
304
180
  it('should return false for elements with only whitespace', () => {
305
181
  const div = document.createElement('div');
306
182
  div.textContent = ' \n\t ';
183
+ document.body.appendChild(div);
307
184
 
308
185
  expect(stringUtils.hasText(div)).toBe(false);
309
186
  });
310
187
 
311
188
  it('should return true for elements with aria-label', () => {
312
189
  const div = document.createElement('div');
313
- const button = document.createElement('button');
314
- button.setAttribute('aria-label', 'Close');
315
-
316
- div.appendChild(button);
190
+ div.setAttribute('aria-label', 'Close');
191
+ document.body.appendChild(div);
317
192
 
318
193
  expect(stringUtils.hasText(div)).toBe(true);
319
194
  });
@@ -322,8 +197,8 @@ describe('stringUtils', () => {
322
197
  const div = document.createElement('div');
323
198
  const img = document.createElement('img');
324
199
  img.setAttribute('alt', 'Profile picture');
325
-
326
200
  div.appendChild(img);
201
+ document.body.appendChild(div);
327
202
 
328
203
  expect(stringUtils.hasText(div)).toBe(true);
329
204
  });
@@ -332,8 +207,8 @@ describe('stringUtils', () => {
332
207
  const div = document.createElement('div');
333
208
  const span = document.createElement('span');
334
209
  span.textContent = 'Nested text';
335
-
336
210
  div.appendChild(span);
211
+ document.body.appendChild(div);
337
212
 
338
213
  expect(stringUtils.hasText(div)).toBe(true);
339
214
  });
@@ -342,9 +217,9 @@ describe('stringUtils', () => {
342
217
  const div = document.createElement('div');
343
218
  const span = document.createElement('span');
344
219
  const p = document.createElement('p');
345
-
346
220
  div.appendChild(span);
347
221
  span.appendChild(p);
222
+ document.body.appendChild(div);
348
223
 
349
224
  expect(stringUtils.hasText(div)).toBe(false);
350
225
  });
@@ -361,6 +236,7 @@ describe('stringUtils', () => {
361
236
  const input = document.createElement('input');
362
237
  input.type = 'text';
363
238
  input.value = '';
239
+ document.body.appendChild(input);
364
240
 
365
241
  expect(stringUtils.hasText(input)).toBe(false);
366
242
  });
@@ -382,48 +258,6 @@ describe('stringUtils', () => {
382
258
  });
383
259
  });
384
260
 
385
- describe('getAllText edge cases', () => {
386
- it('should handle text nodes with empty nodeValue but non-empty textContent', () => {
387
- const div = document.createElement('div');
388
- const textNode = document.createTextNode(' ');
389
- div.appendChild(textNode);
390
-
391
- // This tests the else branch where nodeValue.trim() is empty
392
- const result = stringUtils.getAllText(div);
393
- expect(typeof result).toBe('string');
394
- });
395
-
396
- it('should handle mixed content with whitespace text nodes', () => {
397
- const div = document.createElement('div');
398
- div.innerHTML = ' <span>Text</span> ';
399
-
400
- const result = stringUtils.getAllText(div);
401
- expect(result).toContain('Text');
402
- });
403
-
404
- it('should handle elements with both aria-label and text content', () => {
405
- const div = document.createElement('div');
406
- const button = document.createElement('button');
407
- button.textContent = 'Visual Text';
408
- button.setAttribute('aria-label', 'Accessible Label');
409
- div.appendChild(button);
410
-
411
- const result = stringUtils.getAllText(div);
412
- expect(result).toContain('Accessible Label');
413
- expect(result).toContain('Visual Text');
414
- });
415
-
416
- it('should handle img without alt attribute', () => {
417
- const div = document.createElement('div');
418
- const img = document.createElement('img');
419
- // No alt attribute
420
- div.appendChild(img);
421
-
422
- const result = stringUtils.getAllText(div);
423
- expect(typeof result).toBe('string');
424
- });
425
- });
426
-
427
261
  describe('isEmptyOrWhitespace', () => {
428
262
  it('should return true for null/undefined/empty', () => {
429
263
  expect(stringUtils.isEmptyOrWhitespace(null)).toBe(true);
@@ -493,6 +327,45 @@ describe('stringUtils', () => {
493
327
  it('should trim whitespace', () => {
494
328
  expect(stringUtils.isGenericTitle(' iframe ')).toBe(true);
495
329
  });
330
+
331
+ it('should detect browser/authoring tool default titles', () => {
332
+ expect(stringUtils.isGenericTitle('untitled document')).toBe(true);
333
+ expect(stringUtils.isGenericTitle('new page')).toBe(true);
334
+ expect(stringUtils.isGenericTitle('blank')).toBe(true);
335
+ expect(stringUtils.isGenericTitle('no title')).toBe(true);
336
+ expect(stringUtils.isGenericTitle('default')).toBe(true);
337
+ });
338
+
339
+ it('should detect CMS and structural generic titles', () => {
340
+ expect(stringUtils.isGenericTitle('placeholder')).toBe(true);
341
+ expect(stringUtils.isGenericTitle('sample page')).toBe(true);
342
+ expect(stringUtils.isGenericTitle('banner')).toBe(true);
343
+ expect(stringUtils.isGenericTitle('navigation')).toBe(true);
344
+ expect(stringUtils.isGenericTitle('advertisement')).toBe(true);
345
+ });
346
+
347
+ it('should match numbered variants with spaces', () => {
348
+ expect(stringUtils.isGenericTitle('untitled 1')).toBe(true);
349
+ expect(stringUtils.isGenericTitle('frame 2')).toBe(true);
350
+ expect(stringUtils.isGenericTitle('page 3')).toBe(true);
351
+ expect(stringUtils.isGenericTitle('section 42')).toBe(true);
352
+ expect(stringUtils.isGenericTitle('document 5')).toBe(true);
353
+ expect(stringUtils.isGenericTitle('tab 1')).toBe(true);
354
+ });
355
+
356
+ it('should match expanded numbered pattern base words', () => {
357
+ expect(stringUtils.isGenericTitle('slide1')).toBe(true);
358
+ expect(stringUtils.isGenericTitle('sheet2')).toBe(true);
359
+ expect(stringUtils.isGenericTitle('panel 3')).toBe(true);
360
+ expect(stringUtils.isGenericTitle('window4')).toBe(true);
361
+ });
362
+
363
+ it('should still return false for descriptive titles', () => {
364
+ expect(stringUtils.isGenericTitle('Contact Us Form')).toBe(false);
365
+ expect(stringUtils.isGenericTitle('Product Catalog')).toBe(false);
366
+ expect(stringUtils.isGenericTitle('John Smith Profile')).toBe(false);
367
+ expect(stringUtils.isGenericTitle('Q4 Sales Report')).toBe(false);
368
+ });
496
369
  });
497
370
 
498
371
  describe('isGenericLinkText', () => {
@@ -599,50 +472,4 @@ describe('stringUtils', () => {
599
472
  expect(stringUtils.hasNewWindowWarning(undefined)).toBe(false);
600
473
  });
601
474
  });
602
-
603
- describe('textIncludingImgAlt', () => {
604
- it('should return text content from text nodes', () => {
605
- const el = document.createElement('div');
606
- el.textContent = 'Hello World';
607
- expect(stringUtils.textIncludingImgAlt(el).trim()).toBe('Hello World');
608
- });
609
-
610
- it('should include img alt text', () => {
611
- const el = document.createElement('div');
612
- el.innerHTML = 'Text <img alt="photo"> more text';
613
- const result = stringUtils.textIncludingImgAlt(el);
614
- expect(result).toContain('Text');
615
- expect(result).toContain('photo');
616
- expect(result).toContain('more text');
617
- });
618
-
619
- it('should handle img without alt', () => {
620
- const el = document.createElement('div');
621
- el.innerHTML = 'Text <img src="img.png"> more';
622
- const result = stringUtils.textIncludingImgAlt(el);
623
- expect(result).toContain('Text');
624
- expect(result).toContain('more');
625
- });
626
-
627
- it('should not include aria-label text', () => {
628
- const el = document.createElement('div');
629
- el.innerHTML = '<button aria-label="Close">X</button>';
630
- const result = stringUtils.textIncludingImgAlt(el);
631
- expect(result).toContain('X');
632
- expect(result).not.toContain('Close');
633
- });
634
-
635
- it('should handle empty element', () => {
636
- const el = document.createElement('div');
637
- expect(stringUtils.textIncludingImgAlt(el)).toBe('');
638
- });
639
-
640
- it('should concatenate text from nested elements', () => {
641
- const el = document.createElement('div');
642
- el.innerHTML = '<span>First</span> <span>Second</span>';
643
- const result = stringUtils.textIncludingImgAlt(el);
644
- expect(result).toContain('First');
645
- expect(result).toContain('Second');
646
- });
647
- });
648
475
  });
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
2
  import {
3
3
  testContrast,
4
+ hasBackgroundImage,
4
5
  getComputedBackgroundColor,
5
6
  luminance,
6
7
  parseRGB,
@@ -94,17 +95,49 @@ describe('testContrast', () => {
94
95
  expect(testContrast(div)).toBe(true);
95
96
  });
96
97
 
97
- it('should handle elements with background images', () => {
98
+ it('should skip elements with a background image', () => {
98
99
  const div = document.createElement('div');
99
100
  div.textContent = 'Text with background image';
100
101
  div.style.color = 'rgb(0, 0, 0)';
101
102
  div.style.backgroundImage = 'url(image.jpg)';
102
103
  document.body.appendChild(div);
103
104
 
104
- // Should return true because background color can't be determined
105
+ // Should skip because contrast can't be reliably tested against background images
105
106
  expect(testContrast(div)).toBe(true);
106
107
  });
107
108
 
109
+ it('should skip elements whose ancestor has a background image', () => {
110
+ const parent = document.createElement('div');
111
+ parent.style.backgroundImage = 'url(bg.jpg)';
112
+
113
+ const child = document.createElement('div');
114
+ child.textContent = 'Text over parent background image';
115
+ child.style.color = 'rgb(0, 0, 0)';
116
+ child.style.backgroundColor = 'rgba(0, 0, 0, 0)';
117
+
118
+ parent.appendChild(child);
119
+ document.body.appendChild(parent);
120
+
121
+ expect(testContrast(child)).toBe(true);
122
+ });
123
+
124
+ it('should not skip elements with opaque background over ancestor background image', () => {
125
+ const parent = document.createElement('div');
126
+ parent.style.backgroundImage = 'url(bg.jpg)';
127
+
128
+ const child = document.createElement('div');
129
+ child.textContent = 'Text with opaque background';
130
+ child.style.color = 'rgb(0, 0, 0)';
131
+ child.style.backgroundColor = 'rgb(255, 255, 255)';
132
+
133
+ parent.appendChild(child);
134
+ document.body.appendChild(parent);
135
+
136
+ // The child has an opaque background, so the parent's background image
137
+ // is not visible through it. Contrast should be tested normally.
138
+ expect(testContrast(child)).toBe(true);
139
+ });
140
+
108
141
  it('should handle invisible elements', () => {
109
142
  const div = document.createElement('div');
110
143
  div.textContent = 'Hidden text';
@@ -499,6 +532,75 @@ describe('getComputedBackgroundColor', () => {
499
532
  });
500
533
  });
501
534
 
535
+ describe('hasBackgroundImage', () => {
536
+ beforeEach(() => {
537
+ document.body.innerHTML = '';
538
+ });
539
+
540
+ it('should return false for null element', () => {
541
+ expect(hasBackgroundImage(null)).toBe(false);
542
+ });
543
+
544
+ it('should return false for document node', () => {
545
+ expect(hasBackgroundImage(document)).toBe(false);
546
+ });
547
+
548
+ it('should return true for element with background-image', () => {
549
+ const div = document.createElement('div');
550
+ div.style.backgroundImage = 'url(test.jpg)';
551
+ document.body.appendChild(div);
552
+
553
+ expect(hasBackgroundImage(div)).toBe(true);
554
+ });
555
+
556
+ it('should return false for element without background-image', () => {
557
+ const div = document.createElement('div');
558
+ div.style.backgroundColor = 'rgb(255, 255, 255)';
559
+ document.body.appendChild(div);
560
+
561
+ expect(hasBackgroundImage(div)).toBe(false);
562
+ });
563
+
564
+ it('should return true when ancestor has a background image and element is transparent', () => {
565
+ const parent = document.createElement('div');
566
+ parent.style.backgroundImage = 'url(bg.jpg)';
567
+
568
+ const child = document.createElement('div');
569
+ child.style.backgroundColor = 'rgba(0, 0, 0, 0)';
570
+
571
+ parent.appendChild(child);
572
+ document.body.appendChild(parent);
573
+
574
+ expect(hasBackgroundImage(child)).toBe(true);
575
+ });
576
+
577
+ it('should return false when ancestor has background image but element has opaque background', () => {
578
+ const parent = document.createElement('div');
579
+ parent.style.backgroundImage = 'url(bg.jpg)';
580
+
581
+ const child = document.createElement('div');
582
+ child.style.backgroundColor = 'rgb(255, 255, 255)';
583
+
584
+ parent.appendChild(child);
585
+ document.body.appendChild(parent);
586
+
587
+ expect(hasBackgroundImage(child)).toBe(false);
588
+ });
589
+
590
+ it('should return false if getComputedStyle is not available', () => {
591
+ const div = document.createElement('div');
592
+ div.style.backgroundImage = 'url(test.jpg)';
593
+ document.body.appendChild(div);
594
+
595
+ const originalGetComputedStyle = window.getComputedStyle;
596
+ window.getComputedStyle = null;
597
+
598
+ expect(hasBackgroundImage(div)).toBe(false);
599
+
600
+ window.getComputedStyle = originalGetComputedStyle;
601
+ });
602
+ });
603
+
502
604
  describe('luminance', () => {
503
605
  it('should calculate luminance for pure black (0, 0, 0)', () => {
504
606
  const result = luminance(0, 0, 0);
package/todo.md CHANGED
@@ -6,6 +6,6 @@ _No pending tasks_
6
6
 
7
7
  ## Completed
8
8
 
9
- - ✅ Test coverage improvements (76.4% line coverage, 87.53% branch coverage)
9
+ - ✅ Test coverage improvements (87.69% line coverage, 82.11% branch coverage)
10
10
  - ✅ Playwright integration for CSS pseudo-element tests
11
- - ✅ 666 total tests passing (656 JSDOM + 10 Playwright)
11
+ - ✅ 963 total tests passing (943 JSDOM + 20 Playwright)
package/src/isVisible.js DELETED
@@ -1,103 +0,0 @@
1
- /**
2
- * Checks if the selected item is visible to assistive technologies.
3
- * @param {Element} element - The element to check.
4
- * @param {boolean} strict - Option to be more strict about visibility of aria-hidden="true" in addition to CSS display: none.
5
- * @returns {boolean}
6
- */
7
- function isVisible(element, strict = false) {
8
- // Add null check at the beginning
9
- if (!element || !(element instanceof Element)) {
10
- return false;
11
- }
12
-
13
- // Check if element is still connected to the DOM
14
- if (!element.isConnected) {
15
- return false;
16
- }
17
-
18
- const id = element.id;
19
- let visible = true;
20
-
21
- // These elements are inherently not visible
22
- const nonVisibleSelectors = [
23
- 'base',
24
- 'head',
25
- 'meta',
26
- 'title',
27
- 'link',
28
- 'style',
29
- 'script',
30
- 'br',
31
- 'nobr',
32
- 'col',
33
- 'embed',
34
- 'input[type="hidden"]',
35
- 'keygen',
36
- 'source',
37
- 'track',
38
- 'wbr',
39
- 'datalist',
40
- 'area',
41
- 'param',
42
- 'noframes',
43
- 'ruby > rp',
44
- ];
45
-
46
- if (nonVisibleSelectors.some(selector => element.matches(selector))) {
47
- return true;
48
- }
49
-
50
- const optionalAriaHidden = (el, strictCheck) =>
51
- strictCheck && el.getAttribute('aria-hidden') === 'true';
52
-
53
- const isElemHiddenByCSS = el => {
54
- const style = window.getComputedStyle(el);
55
- return style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0';
56
- };
57
-
58
- const isHidden = () => {
59
- if (isElemHiddenByCSS(element)) {
60
- return true;
61
- }
62
-
63
- let parent = element.parentElement;
64
- while (parent) {
65
- if (isElemHiddenByCSS(parent)) {
66
- return true;
67
- }
68
- parent = parent.parentElement;
69
- }
70
- return optionalAriaHidden(element, strict);
71
- };
72
-
73
- if (isHidden()) {
74
- visible = false;
75
- }
76
-
77
- // Check if element is referenced by aria-labelledby or aria-describedby
78
- document
79
- .querySelectorAll(`*[aria-labelledby~="${id}"], *[aria-describedby~="${id}"]`)
80
- .forEach(referencingElement => {
81
- if (window.getComputedStyle(referencingElement).display !== 'none') {
82
- visible = true;
83
- }
84
- });
85
-
86
- // Check if any parent has aria-hidden="true" when strict mode is on
87
- if (visible && strict) {
88
- let parent = element.parentElement;
89
- while (parent) {
90
- if (optionalAriaHidden(parent, strict)) {
91
- visible = false;
92
- break;
93
- }
94
- parent = parent.parentElement;
95
- }
96
- }
97
-
98
- return visible;
99
- }
100
-
101
- module.exports = {
102
- isVisible,
103
- };