@afixt/test-utils 1.2.2 → 1.2.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afixt/test-utils",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Various utilities for accessibility testing",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/isVisible.js CHANGED
@@ -20,24 +20,51 @@ function isVisible(element, strict = false) {
20
20
 
21
21
  // These elements are inherently not visible
22
22
  const nonVisibleSelectors = [
23
- 'base', 'head', 'meta', 'title', 'link', 'style', 'script', 'br', 'nobr', 'col', 'embed',
24
- 'input[type="hidden"]', 'keygen', 'source', 'track', 'wbr', 'datalist', 'area', 'param', 'noframes', 'ruby > rp'
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',
25
44
  ];
26
45
 
27
46
  if (nonVisibleSelectors.some(selector => element.matches(selector))) {
28
47
  return true;
29
48
  }
30
49
 
31
- const optionalAriaHidden = (el, strictCheck) => strictCheck && el.getAttribute('aria-hidden') === 'true';
50
+ const optionalAriaHidden = (el, strictCheck) =>
51
+ strictCheck && el.getAttribute('aria-hidden') === 'true';
32
52
 
33
- const isElemDisplayed = el => window.getComputedStyle(el).display === 'none';
53
+ const isElemHiddenByCSS = el => {
54
+ const style = window.getComputedStyle(el);
55
+ return style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0';
56
+ };
34
57
 
35
58
  const isHidden = () => {
36
- if (isElemDisplayed(element)) return true;
59
+ if (isElemHiddenByCSS(element)) {
60
+ return true;
61
+ }
37
62
 
38
63
  let parent = element.parentElement;
39
64
  while (parent) {
40
- if (isElemDisplayed(parent)) return true;
65
+ if (isElemHiddenByCSS(parent)) {
66
+ return true;
67
+ }
41
68
  parent = parent.parentElement;
42
69
  }
43
70
  return optionalAriaHidden(element, strict);
@@ -48,11 +75,13 @@ function isVisible(element, strict = false) {
48
75
  }
49
76
 
50
77
  // Check if element is referenced by aria-labelledby or aria-describedby
51
- document.querySelectorAll(`*[aria-labelledby~="${id}"], *[aria-describedby~="${id}"]`).forEach(referencingElement => {
52
- if (window.getComputedStyle(referencingElement).display !== 'none') {
53
- visible = true;
54
- }
55
- });
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
+ });
56
85
 
57
86
  // Check if any parent has aria-hidden="true" when strict mode is on
58
87
  if (visible && strict) {
@@ -70,5 +99,5 @@ function isVisible(element, strict = false) {
70
99
  }
71
100
 
72
101
  module.exports = {
73
- isVisible
102
+ isVisible,
74
103
  };
@@ -2,149 +2,151 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
2
  import { isVisible } from '../src/isVisible';
3
3
 
4
4
  describe('isVisible', () => {
5
- beforeEach(() => {
6
- document.body.innerHTML = '';
7
- // Reset computed styles
8
- vi.spyOn(window, 'getComputedStyle').mockImplementation((element) => {
9
- return {
10
- display: element.style.display || 'block',
11
- };
12
- });
13
- });
14
-
15
- it('should return false for null or undefined elements', () => {
16
- expect(isVisible(null)).toBe(false);
17
- expect(isVisible(undefined)).toBe(false);
18
- });
19
-
20
- it('should handle inherently non-visible elements correctly', () => {
21
- // The behavior of isVisible for inherently non-visible elements is to return *false* by default
22
- // This is a mock test just to make it pass until we can properly test the functionality
23
-
24
- // Create a div element
25
- document.body.innerHTML = `<div id="test"></div>`;
26
- const element = document.getElementById('test');
27
-
28
- // Mock the matches method to simulate an inherently non-visible element
29
- const originalMatches = element.matches;
30
- element.matches = (selector) => true;
31
-
32
- // With our mocked matches method, isVisible should return true for inherently non-visible elements
33
- // according to the implementation in isVisible.js
34
- expect(isVisible(element)).toBe(true);
35
-
36
- // Restore the original method
37
- element.matches = originalMatches;
38
- });
39
-
40
- it('should return false for elements with display:none', () => {
41
- document.body.innerHTML = `<div id="test" style="display:none">Hidden</div>`;
42
- const element = document.getElementById('test');
43
- expect(isVisible(element)).toBe(false);
44
- });
45
-
46
- it('should return false for elements with a parent that has display:none', () => {
47
- document.body.innerHTML = `
5
+ beforeEach(() => {
6
+ document.body.innerHTML = '';
7
+ // Reset computed styles
8
+ vi.spyOn(window, 'getComputedStyle').mockImplementation(element => {
9
+ return {
10
+ display: element.style.display || 'block',
11
+ visibility: element.style.visibility || 'visible',
12
+ opacity: element.style.opacity || '1',
13
+ };
14
+ });
15
+ });
16
+
17
+ it('should return false for null or undefined elements', () => {
18
+ expect(isVisible(null)).toBe(false);
19
+ expect(isVisible(undefined)).toBe(false);
20
+ });
21
+
22
+ it('should handle inherently non-visible elements correctly', () => {
23
+ // The behavior of isVisible for inherently non-visible elements is to return *false* by default
24
+ // This is a mock test just to make it pass until we can properly test the functionality
25
+
26
+ // Create a div element
27
+ document.body.innerHTML = '<div id="test"></div>';
28
+ const element = document.getElementById('test');
29
+
30
+ // Mock the matches method to simulate an inherently non-visible element
31
+ const originalMatches = element.matches;
32
+ element.matches = selector => true;
33
+
34
+ // With our mocked matches method, isVisible should return true for inherently non-visible elements
35
+ // according to the implementation in isVisible.js
36
+ expect(isVisible(element)).toBe(true);
37
+
38
+ // Restore the original method
39
+ element.matches = originalMatches;
40
+ });
41
+
42
+ it('should return false for elements with display:none', () => {
43
+ document.body.innerHTML = '<div id="test" style="display:none">Hidden</div>';
44
+ const element = document.getElementById('test');
45
+ expect(isVisible(element)).toBe(false);
46
+ });
47
+
48
+ it('should return false for elements with a parent that has display:none', () => {
49
+ document.body.innerHTML = `
48
50
  <div style="display:none">
49
51
  <span id="child">Hidden child</span>
50
52
  </div>
51
53
  `;
52
- const element = document.getElementById('child');
53
- expect(isVisible(element)).toBe(false);
54
- });
55
-
56
- it('should return false when aria-hidden=true in strict mode', () => {
57
- document.body.innerHTML = `<div id="test" aria-hidden="true">Hidden</div>`;
58
- const element = document.getElementById('test');
59
-
60
- // In non-strict mode, aria-hidden alone doesn't make it invisible
61
- expect(isVisible(element)).toBe(true);
62
-
63
- // In strict mode, aria-hidden="true" makes it invisible
64
- expect(isVisible(element, true)).toBe(false);
65
- });
66
-
67
- it('should return false when a parent has aria-hidden=true in strict mode', () => {
68
- document.body.innerHTML = `
54
+ const element = document.getElementById('child');
55
+ expect(isVisible(element)).toBe(false);
56
+ });
57
+
58
+ it('should return false when aria-hidden=true in strict mode', () => {
59
+ document.body.innerHTML = '<div id="test" aria-hidden="true">Hidden</div>';
60
+ const element = document.getElementById('test');
61
+
62
+ // In non-strict mode, aria-hidden alone doesn't make it invisible
63
+ expect(isVisible(element)).toBe(true);
64
+
65
+ // In strict mode, aria-hidden="true" makes it invisible
66
+ expect(isVisible(element, true)).toBe(false);
67
+ });
68
+
69
+ it('should return false when a parent has aria-hidden=true in strict mode', () => {
70
+ document.body.innerHTML = `
69
71
  <div aria-hidden="true">
70
72
  <span id="child">Hidden child</span>
71
73
  </div>
72
74
  `;
73
- const element = document.getElementById('child');
74
-
75
- // In non-strict mode, aria-hidden alone doesn't make it invisible
76
- expect(isVisible(element)).toBe(true);
77
-
78
- // In strict mode, parent with aria-hidden="true" makes it invisible
79
- expect(isVisible(element, true)).toBe(false);
80
- });
81
-
82
- it('should consider elements referenced by aria-labelledby or aria-describedby', () => {
83
- document.body.innerHTML = `
75
+ const element = document.getElementById('child');
76
+
77
+ // In non-strict mode, aria-hidden alone doesn't make it invisible
78
+ expect(isVisible(element)).toBe(true);
79
+
80
+ // In strict mode, parent with aria-hidden="true" makes it invisible
81
+ expect(isVisible(element, true)).toBe(false);
82
+ });
83
+
84
+ it('should consider elements referenced by aria-labelledby or aria-describedby', () => {
85
+ document.body.innerHTML = `
84
86
  <div id="label" style="display:none">Hidden Label</div>
85
87
  <button aria-labelledby="label">Button</button>
86
88
  `;
87
89
 
88
- const label = document.getElementById('label');
89
- const button = document.querySelector('button');
90
-
91
- window.getComputedStyle.mockImplementation((el) => {
92
- if (el === label) {
93
- return { display: 'none' };
94
- }
95
- if (el === button) {
96
- return { display: 'block' };
97
- }
98
- return { display: 'block' };
99
- });
100
-
101
- // The label is hidden but should be considered visible because it's referenced
102
- expect(isVisible(label)).toBe(true);
103
- });
104
-
105
- it('should return false for non-Element objects', () => {
106
- expect(isVisible({})).toBe(false);
107
- expect(isVisible('string')).toBe(false);
108
- expect(isVisible(123)).toBe(false);
109
- });
110
-
111
- it('should return false for disconnected elements', () => {
112
- const div = document.createElement('div');
113
- // Element is not connected to DOM
114
- expect(isVisible(div)).toBe(false);
115
- });
116
-
117
- it('should return true for visible elements', () => {
118
- document.body.innerHTML = `<div id="test">Visible</div>`;
119
- const element = document.getElementById('test');
120
- expect(isVisible(element)).toBe(true);
121
- });
122
-
123
- it('should handle elements referenced by aria-describedby', () => {
124
- document.body.innerHTML = `
90
+ const label = document.getElementById('label');
91
+ const button = document.querySelector('button');
92
+
93
+ window.getComputedStyle.mockImplementation(el => {
94
+ if (el === label) {
95
+ return { display: 'none', visibility: 'visible', opacity: '1' };
96
+ }
97
+ if (el === button) {
98
+ return { display: 'block', visibility: 'visible', opacity: '1' };
99
+ }
100
+ return { display: 'block', visibility: 'visible', opacity: '1' };
101
+ });
102
+
103
+ // The label is hidden but should be considered visible because it's referenced
104
+ expect(isVisible(label)).toBe(true);
105
+ });
106
+
107
+ it('should return false for non-Element objects', () => {
108
+ expect(isVisible({})).toBe(false);
109
+ expect(isVisible('string')).toBe(false);
110
+ expect(isVisible(123)).toBe(false);
111
+ });
112
+
113
+ it('should return false for disconnected elements', () => {
114
+ const div = document.createElement('div');
115
+ // Element is not connected to DOM
116
+ expect(isVisible(div)).toBe(false);
117
+ });
118
+
119
+ it('should return true for visible elements', () => {
120
+ document.body.innerHTML = '<div id="test">Visible</div>';
121
+ const element = document.getElementById('test');
122
+ expect(isVisible(element)).toBe(true);
123
+ });
124
+
125
+ it('should handle elements referenced by aria-describedby', () => {
126
+ document.body.innerHTML = `
125
127
  <div id="desc" style="display:none">Description</div>
126
128
  <input aria-describedby="desc" />
127
129
  `;
128
130
 
129
- const desc = document.getElementById('desc');
130
- const input = document.querySelector('input');
131
+ const desc = document.getElementById('desc');
132
+ const input = document.querySelector('input');
131
133
 
132
- window.getComputedStyle.mockImplementation((el) => {
133
- if (el === desc) {
134
- return { display: 'none' };
135
- }
136
- if (el === input) {
137
- return { display: 'block' };
138
- }
139
- return { display: 'block' };
140
- });
134
+ window.getComputedStyle.mockImplementation(el => {
135
+ if (el === desc) {
136
+ return { display: 'none', visibility: 'visible', opacity: '1' };
137
+ }
138
+ if (el === input) {
139
+ return { display: 'block', visibility: 'visible', opacity: '1' };
140
+ }
141
+ return { display: 'block', visibility: 'visible', opacity: '1' };
142
+ });
141
143
 
142
- // Description is hidden but should be considered visible because it's referenced
143
- expect(isVisible(desc)).toBe(true);
144
- });
144
+ // Description is hidden but should be considered visible because it's referenced
145
+ expect(isVisible(desc)).toBe(true);
146
+ });
145
147
 
146
- it('should handle multiple ancestors with display:none', () => {
147
- document.body.innerHTML = `
148
+ it('should handle multiple ancestors with display:none', () => {
149
+ document.body.innerHTML = `
148
150
  <div style="display:none">
149
151
  <div>
150
152
  <div>
@@ -153,70 +155,102 @@ describe('isVisible', () => {
153
155
  </div>
154
156
  </div>
155
157
  `;
156
- const element = document.getElementById('deeply-hidden');
157
- expect(isVisible(element)).toBe(false);
158
- });
158
+ const element = document.getElementById('deeply-hidden');
159
+ expect(isVisible(element)).toBe(false);
160
+ });
159
161
 
160
- it('should handle aria-hidden="false"', () => {
161
- document.body.innerHTML = `<div id="test" aria-hidden="false">Visible</div>`;
162
- const element = document.getElementById('test');
162
+ it('should handle aria-hidden="false"', () => {
163
+ document.body.innerHTML = '<div id="test" aria-hidden="false">Visible</div>';
164
+ const element = document.getElementById('test');
163
165
 
164
- // aria-hidden="false" should not affect visibility in either mode
165
- expect(isVisible(element)).toBe(true);
166
- expect(isVisible(element, true)).toBe(true);
167
- });
166
+ // aria-hidden="false" should not affect visibility in either mode
167
+ expect(isVisible(element)).toBe(true);
168
+ expect(isVisible(element, true)).toBe(true);
169
+ });
168
170
 
169
- it('should handle elements with no aria-hidden attribute in strict mode', () => {
170
- document.body.innerHTML = `<div id="test">No aria-hidden</div>`;
171
- const element = document.getElementById('test');
171
+ it('should handle elements with no aria-hidden attribute in strict mode', () => {
172
+ document.body.innerHTML = '<div id="test">No aria-hidden</div>';
173
+ const element = document.getElementById('test');
172
174
 
173
- expect(isVisible(element, true)).toBe(true);
174
- });
175
+ expect(isVisible(element, true)).toBe(true);
176
+ });
175
177
 
176
- it('should handle multiple levels of parent aria-hidden in strict mode', () => {
177
- document.body.innerHTML = `
178
+ it('should handle multiple levels of parent aria-hidden in strict mode', () => {
179
+ document.body.innerHTML = `
178
180
  <div aria-hidden="true">
179
181
  <div>
180
182
  <span id="child">Child</span>
181
183
  </div>
182
184
  </div>
183
185
  `;
184
- const element = document.getElementById('child');
186
+ const element = document.getElementById('child');
185
187
 
186
- expect(isVisible(element, true)).toBe(false);
187
- });
188
+ expect(isVisible(element, true)).toBe(false);
189
+ });
188
190
 
189
- it('should check specific inherently non-visible elements', () => {
190
- const testCases = [
191
- { tag: 'script', html: '<script id="test">alert("test")</script>' },
192
- { tag: 'style', html: '<style id="test">body {}</style>' },
193
- { tag: 'input[type="hidden"]', html: '<input type="hidden" id="test" />' }
194
- ];
191
+ it('should check specific inherently non-visible elements', () => {
192
+ const testCases = [
193
+ { tag: 'script', html: '<script id="test">alert("test")</script>' },
194
+ { tag: 'style', html: '<style id="test">body {}</style>' },
195
+ { tag: 'input[type="hidden"]', html: '<input type="hidden" id="test" />' },
196
+ ];
195
197
 
196
- testCases.forEach(({ tag, html }) => {
197
- document.body.innerHTML = html;
198
- const element = document.getElementById('test');
199
- // These elements return true (visible to AT) according to implementation
200
- expect(isVisible(element)).toBe(true);
198
+ testCases.forEach(({ tag, html }) => {
199
+ document.body.innerHTML = html;
200
+ const element = document.getElementById('test');
201
+ // These elements return true (visible to AT) according to implementation
202
+ expect(isVisible(element)).toBe(true);
203
+ });
201
204
  });
202
- });
203
205
 
204
- it('should handle element with no id referenced by aria-labelledby', () => {
205
- document.body.innerHTML = `
206
+ it('should handle element with no id referenced by aria-labelledby', () => {
207
+ document.body.innerHTML = `
206
208
  <div style="display:none">No ID</div>
207
209
  <button aria-labelledby="nonexistent">Button</button>
208
210
  `;
209
211
 
210
- const div = document.querySelector('div');
211
- // Element has no id, so won't be found by aria-labelledby query
212
- expect(isVisible(div)).toBe(false);
213
- });
212
+ const div = document.querySelector('div');
213
+ // Element has no id, so won't be found by aria-labelledby query
214
+ expect(isVisible(div)).toBe(false);
215
+ });
214
216
 
215
- it('should handle aria-hidden with different values in strict mode', () => {
216
- document.body.innerHTML = `<div id="test" aria-hidden="invalid">Text</div>`;
217
- const element = document.getElementById('test');
217
+ it('should return false for elements with visibility:hidden', () => {
218
+ document.body.innerHTML = '<div id="test" style="visibility:hidden">Hidden</div>';
219
+ const element = document.getElementById('test');
220
+ expect(isVisible(element)).toBe(false);
221
+ });
218
222
 
219
- // aria-hidden with value other than "true" should not hide in strict mode
220
- expect(isVisible(element, true)).toBe(true);
221
- });
222
- });
223
+ it('should return false for elements with a parent that has visibility:hidden', () => {
224
+ document.body.innerHTML = `
225
+ <div style="visibility:hidden">
226
+ <span id="child">Hidden child</span>
227
+ </div>
228
+ `;
229
+ const element = document.getElementById('child');
230
+ expect(isVisible(element)).toBe(false);
231
+ });
232
+
233
+ it('should return false for elements with opacity:0', () => {
234
+ document.body.innerHTML = '<div id="test" style="opacity:0">Hidden</div>';
235
+ const element = document.getElementById('test');
236
+ expect(isVisible(element)).toBe(false);
237
+ });
238
+
239
+ it('should return false for elements with a parent that has opacity:0', () => {
240
+ document.body.innerHTML = `
241
+ <div style="opacity:0">
242
+ <span id="child">Hidden child</span>
243
+ </div>
244
+ `;
245
+ const element = document.getElementById('child');
246
+ expect(isVisible(element)).toBe(false);
247
+ });
248
+
249
+ it('should handle aria-hidden with different values in strict mode', () => {
250
+ document.body.innerHTML = '<div id="test" aria-hidden="invalid">Text</div>';
251
+ const element = document.getElementById('test');
252
+
253
+ // aria-hidden with value other than "true" should not hide in strict mode
254
+ expect(isVisible(element, true)).toBe(true);
255
+ });
256
+ });