@afixt/test-utils 1.1.2 → 1.1.4
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 +6 -2
- package/.github/workflows/test.yml +26 -0
- package/BROWSER_TESTING.md +109 -0
- package/CLAUDE.md +22 -0
- package/package.json +6 -8
- package/playwright.config.js +27 -0
- package/src/domUtils.js +1 -1
- package/src/getAccessibleName.js +8 -4
- package/src/getCSSGeneratedContent.js +9 -5
- package/src/getFocusableElements.js +13 -4
- package/src/getImageText.js +4 -1
- package/src/testContrast.js +5 -1
- package/test/__screenshots__/getImageText.test.js/getImageText-should-be-an-async-function-1.png +0 -0
- package/test/__screenshots__/getImageText.test.js/getImageText-should-be-defined-and-exported-from-the-module-1.png +0 -0
- package/test/__screenshots__/getImageText.test.js/getImageText-should-handle-empty-string-input-gracefully-1.png +0 -0
- package/test/__screenshots__/getImageText.test.js/getImageText-should-handle-invalid-image-paths-gracefully-1.png +0 -0
- package/test/__screenshots__/getImageText.test.js/getImageText-should-handle-null-or-undefined-input-gracefully-1.png +0 -0
- package/test/__screenshots__/getImageText.test.js/getImageText-should-log-errors-in-non-test-environments-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-call-original-addEventListener-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-added-event-listeners-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-listeners-for-different-event-types-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-multiple-listeners-for-the-same-event-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-options-parameter-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getEventListeners-should-return-all-event-listeners-for-an-element-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getEventListeners-should-return-empty-object-for-elements-without-listeners-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getXPath-should-generate-XPath-for-elements-without-id-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getXPath-should-handle-multiple-siblings-correctly-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getXPath-should-return-XPath-for-element-with-id-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-event-listeners-on-child-elements-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-event-listeners-on-root-element-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-listeners-from-multiple-elements-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-multiple-event-types-on-same-element-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-return-empty-array-when-no-event-listeners-exist-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-use-document-as-default-root-element-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-work-with-custom-root-element-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-call-original-removeEventListener-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-handle-removing-non-existent-listeners-gracefully-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-only-remove-the-specified-listener-1.png +0 -0
- package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-remove-tracked-event-listeners-1.png +0 -0
- package/test/arrayUtils.test.js +22 -0
- package/test/domUtils.test.js +241 -0
- package/test/getAccessibleName.test.js +182 -0
- package/test/getAccessibleText.test.js +350 -79
- package/test/getCSSGeneratedContent.test.js +175 -1
- package/test/getFocusableElements.test.js +106 -35
- package/test/getImageText.test.js +95 -12
- package/test/getStyleObject.test.js +19 -1
- package/test/hasCSSGeneratedContent.test.js +7 -2
- package/test/hasParent.test.js +116 -0
- package/test/hasValidAriaRole.test.js +64 -2
- package/test/index.test.js +165 -0
- package/test/interactiveRoles.test.js +60 -0
- package/test/isAriaAttributesValid.test.js +36 -0
- package/test/isDataTable.test.js +492 -0
- package/test/isFocusable.test.js +94 -1
- package/test/isValidUrl.test.js +31 -19
- package/test/isVisible.test.js +121 -3
- package/test/playwright/css-pseudo-elements.spec.js +155 -0
- package/test/playwright/fixtures/css-pseudo-elements.html +77 -0
- package/test/setup.js +9 -1
- package/test/stringUtils.test.js +277 -1
- package/test/testContrast.test.js +614 -9
- package/test/testLang.test.js +152 -11
- package/test/testOrder.integration.test.js +369 -0
- package/test/testOrder.test.js +756 -21
- package/todo.md +11 -1
- package/vitest.config.js +8 -1
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -51
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -161
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/test-utils/docs/scripts/core.js.html +0 -2263
- package/coverage/test-utils/docs/scripts/core.min.js.html +0 -151
- package/coverage/test-utils/docs/scripts/index.html +0 -176
- package/coverage/test-utils/docs/scripts/resize.js.html +0 -355
- package/coverage/test-utils/docs/scripts/search.js.html +0 -880
- package/coverage/test-utils/docs/scripts/search.min.js.html +0 -100
- package/coverage/test-utils/docs/scripts/third-party/fuse.js.html +0 -109
- package/coverage/test-utils/docs/scripts/third-party/hljs-line-num-original.js.html +0 -1192
- package/coverage/test-utils/docs/scripts/third-party/hljs-line-num.js.html +0 -85
- package/coverage/test-utils/docs/scripts/third-party/hljs-original.js.html +0 -15598
- package/coverage/test-utils/docs/scripts/third-party/hljs.js.html +0 -85
- package/coverage/test-utils/docs/scripts/third-party/index.html +0 -236
- package/coverage/test-utils/docs/scripts/third-party/popper.js.html +0 -100
- package/coverage/test-utils/docs/scripts/third-party/tippy.js.html +0 -88
- package/coverage/test-utils/docs/scripts/third-party/tocbot.js.html +0 -2098
- package/coverage/test-utils/docs/scripts/third-party/tocbot.min.js.html +0 -85
- package/coverage/test-utils/index.html +0 -131
- package/coverage/test-utils/src/arrayUtils.js.html +0 -283
- package/coverage/test-utils/src/domUtils.js.html +0 -622
- package/coverage/test-utils/src/getAccessibleName.js.html +0 -1444
- package/coverage/test-utils/src/getAccessibleText.js.html +0 -271
- package/coverage/test-utils/src/getAriaAttributesByElement.js.html +0 -142
- package/coverage/test-utils/src/getCSSGeneratedContent.js.html +0 -265
- package/coverage/test-utils/src/getComputedRole.js.html +0 -592
- package/coverage/test-utils/src/getFocusableElements.js.html +0 -163
- package/coverage/test-utils/src/getGeneratedContent.js.html +0 -130
- package/coverage/test-utils/src/getImageText.js.html +0 -160
- package/coverage/test-utils/src/getStyleObject.js.html +0 -220
- package/coverage/test-utils/src/hasAccessibleName.js.html +0 -166
- package/coverage/test-utils/src/hasAttribute.js.html +0 -130
- package/coverage/test-utils/src/hasCSSGeneratedContent.js.html +0 -145
- package/coverage/test-utils/src/hasHiddenParent.js.html +0 -172
- package/coverage/test-utils/src/hasParent.js.html +0 -247
- package/coverage/test-utils/src/hasValidAriaAttributes.js.html +0 -175
- package/coverage/test-utils/src/hasValidAriaRole.js.html +0 -172
- package/coverage/test-utils/src/index.html +0 -611
- package/coverage/test-utils/src/index.js.html +0 -274
- package/coverage/test-utils/src/interactiveRoles.js.html +0 -145
- package/coverage/test-utils/src/isAriaAttributesValid.js.html +0 -304
- package/coverage/test-utils/src/isComplexTable.js.html +0 -412
- package/coverage/test-utils/src/isDataTable.js.html +0 -799
- package/coverage/test-utils/src/isFocusable.js.html +0 -187
- package/coverage/test-utils/src/isHidden.js.html +0 -136
- package/coverage/test-utils/src/isOffScreen.js.html +0 -133
- package/coverage/test-utils/src/isValidUrl.js.html +0 -124
- package/coverage/test-utils/src/isVisible.js.html +0 -271
- package/coverage/test-utils/src/listEventListeners.js.html +0 -370
- package/coverage/test-utils/src/queryCache.js.html +0 -1156
- package/coverage/test-utils/src/stringUtils.js.html +0 -535
- package/coverage/test-utils/src/testContrast.js.html +0 -784
- package/coverage/test-utils/src/testLang.js.html +0 -1810
- package/coverage/test-utils/src/testOrder.js.html +0 -355
- package/coverage/test-utils/vitest.config.browser.js.html +0 -133
- package/coverage/test-utils/vitest.config.js.html +0 -157
- package/test/browser-setup.js +0 -68
- package/vitest.config.browser.js +0 -17
|
@@ -1,94 +1,365 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach,
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { getAccessibleText } from '../src/getAccessibleText.js';
|
|
3
3
|
|
|
4
|
-
// Mock the isEmpty function since it's not imported
|
|
5
|
-
const originalGetAccessibleText = getAccessibleText;
|
|
6
|
-
vi.mock('../src/getAccessibleText.js', () => ({
|
|
7
|
-
getAccessibleText: (el) => {
|
|
8
|
-
if (!el) return "";
|
|
9
|
-
|
|
10
|
-
let text = [];
|
|
11
|
-
|
|
12
|
-
// Get text from aria-label attributes
|
|
13
|
-
const elementsWithAriaLabel = el.querySelectorAll('[aria-label]');
|
|
14
|
-
elementsWithAriaLabel.forEach(element => {
|
|
15
|
-
text.push(element.getAttribute('aria-label'));
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Get text from alt attributes on img elements
|
|
19
|
-
const imgElements = el.querySelectorAll('img[alt]');
|
|
20
|
-
imgElements.forEach(img => {
|
|
21
|
-
text.push(img.getAttribute('alt').trim());
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Also check the element itself for aria-label or alt
|
|
25
|
-
if (el.hasAttribute && el.hasAttribute('aria-label')) {
|
|
26
|
-
text.push(el.getAttribute('aria-label'));
|
|
27
|
-
} else if (el.tagName && el.tagName.toLowerCase() === 'img' && el.hasAttribute('alt')) {
|
|
28
|
-
text.push(el.getAttribute('alt').trim());
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return text.join(' ').trim();
|
|
32
|
-
}
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
4
|
describe('getAccessibleText', () => {
|
|
36
5
|
beforeEach(() => {
|
|
37
6
|
document.body.innerHTML = '';
|
|
38
7
|
});
|
|
39
8
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
document.body.innerHTML = '';
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('input validation', () => {
|
|
14
|
+
it('should return empty string when no element is provided', () => {
|
|
15
|
+
expect(getAccessibleText(null)).toBe('');
|
|
16
|
+
expect(getAccessibleText(undefined)).toBe('');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return empty string for non-Element objects', () => {
|
|
20
|
+
expect(getAccessibleText('string')).toBe('');
|
|
21
|
+
expect(getAccessibleText(123)).toBe('');
|
|
22
|
+
expect(getAccessibleText({})).toBe('');
|
|
23
|
+
expect(getAccessibleText([])).toBe('');
|
|
24
|
+
expect(getAccessibleText(document.createTextNode('text'))).toBe(''); // Text node, not Element
|
|
25
|
+
expect(getAccessibleText(document.createComment('comment'))).toBe(''); // Comment node
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return empty string for disconnected elements', () => {
|
|
29
|
+
const div = document.createElement('div');
|
|
30
|
+
div.textContent = 'Test content';
|
|
31
|
+
// Element is not connected to DOM
|
|
32
|
+
expect(getAccessibleText(div)).toBe('');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('aria-label handling', () => {
|
|
37
|
+
it('should prioritize aria-label when present', () => {
|
|
38
|
+
const div = document.createElement('div');
|
|
39
|
+
div.setAttribute('aria-label', 'Accessible Label');
|
|
40
|
+
div.textContent = 'Visual Text';
|
|
41
|
+
document.body.appendChild(div);
|
|
42
|
+
|
|
43
|
+
expect(getAccessibleText(div)).toBe('Accessible Label');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should handle empty aria-label by falling back to text content', () => {
|
|
47
|
+
const div = document.createElement('div');
|
|
48
|
+
div.setAttribute('aria-label', '');
|
|
49
|
+
div.textContent = 'Visual Text';
|
|
50
|
+
document.body.appendChild(div);
|
|
51
|
+
|
|
52
|
+
expect(getAccessibleText(div)).toBe('Visual Text');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle aria-label with only whitespace', () => {
|
|
56
|
+
const div = document.createElement('div');
|
|
57
|
+
div.setAttribute('aria-label', ' ');
|
|
58
|
+
div.textContent = 'Visual Text';
|
|
59
|
+
document.body.appendChild(div);
|
|
60
|
+
|
|
61
|
+
expect(getAccessibleText(div)).toBe('Visual Text');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return empty aria-label when it exists but is empty after trim', () => {
|
|
65
|
+
const div = document.createElement('div');
|
|
66
|
+
div.setAttribute('aria-label', '');
|
|
67
|
+
div.textContent = '';
|
|
68
|
+
document.body.appendChild(div);
|
|
69
|
+
|
|
70
|
+
expect(getAccessibleText(div)).toBe('');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('img alt text handling', () => {
|
|
75
|
+
it('should get alt text from img elements', () => {
|
|
76
|
+
const img = document.createElement('img');
|
|
77
|
+
img.setAttribute('alt', 'Image description');
|
|
78
|
+
img.setAttribute('src', 'test.jpg');
|
|
79
|
+
document.body.appendChild(img);
|
|
80
|
+
|
|
81
|
+
expect(getAccessibleText(img)).toBe('Image description');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle img with empty alt attribute', () => {
|
|
85
|
+
const img = document.createElement('img');
|
|
86
|
+
img.setAttribute('alt', '');
|
|
87
|
+
img.setAttribute('src', 'test.jpg');
|
|
88
|
+
document.body.appendChild(img);
|
|
89
|
+
|
|
90
|
+
expect(getAccessibleText(img)).toBe('');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle img without alt attribute', () => {
|
|
94
|
+
const img = document.createElement('img');
|
|
95
|
+
img.setAttribute('src', 'test.jpg');
|
|
96
|
+
document.body.appendChild(img);
|
|
97
|
+
|
|
98
|
+
expect(getAccessibleText(img)).toBe('');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should trim whitespace from img alt text', () => {
|
|
102
|
+
const img = document.createElement('img');
|
|
103
|
+
img.setAttribute('alt', ' Image description ');
|
|
104
|
+
img.setAttribute('src', 'test.jpg');
|
|
105
|
+
document.body.appendChild(img);
|
|
106
|
+
|
|
107
|
+
expect(getAccessibleText(img)).toBe('Image description');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should prioritize aria-label over alt text on img', () => {
|
|
111
|
+
const img = document.createElement('img');
|
|
112
|
+
img.setAttribute('alt', 'Alt text');
|
|
113
|
+
img.setAttribute('aria-label', 'Aria label');
|
|
114
|
+
img.setAttribute('src', 'test.jpg');
|
|
115
|
+
document.body.appendChild(img);
|
|
116
|
+
|
|
117
|
+
expect(getAccessibleText(img)).toBe('Aria label');
|
|
118
|
+
});
|
|
43
119
|
});
|
|
44
120
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
121
|
+
describe('text content extraction', () => {
|
|
122
|
+
it('should get text content from simple elements', () => {
|
|
123
|
+
const div = document.createElement('div');
|
|
124
|
+
div.textContent = 'Simple text content';
|
|
125
|
+
document.body.appendChild(div);
|
|
126
|
+
|
|
127
|
+
expect(getAccessibleText(div)).toBe('Simple text content');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should trim whitespace from text content', () => {
|
|
131
|
+
const div = document.createElement('div');
|
|
132
|
+
div.textContent = ' Padded text ';
|
|
133
|
+
document.body.appendChild(div);
|
|
134
|
+
|
|
135
|
+
expect(getAccessibleText(div)).toBe('Padded text');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should concatenate text from multiple child nodes', () => {
|
|
139
|
+
const container = document.createElement('div');
|
|
140
|
+
container.innerHTML = `
|
|
141
|
+
<span>First part</span>
|
|
142
|
+
<span>Second part</span>
|
|
143
|
+
<span>Third part</span>
|
|
144
|
+
`;
|
|
145
|
+
document.body.appendChild(container);
|
|
146
|
+
|
|
147
|
+
const result = getAccessibleText(container);
|
|
148
|
+
expect(result).toContain('First part');
|
|
149
|
+
expect(result).toContain('Second part');
|
|
150
|
+
expect(result).toContain('Third part');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle nested elements', () => {
|
|
154
|
+
const container = document.createElement('div');
|
|
155
|
+
container.innerHTML = `
|
|
156
|
+
<div>
|
|
157
|
+
<span>Nested <strong>text</strong> content</span>
|
|
158
|
+
</div>
|
|
159
|
+
`;
|
|
160
|
+
document.body.appendChild(container);
|
|
161
|
+
|
|
162
|
+
const result = getAccessibleText(container);
|
|
163
|
+
expect(result).toContain('Nested');
|
|
164
|
+
expect(result).toContain('text');
|
|
165
|
+
expect(result).toContain('content');
|
|
166
|
+
});
|
|
56
167
|
});
|
|
57
168
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
169
|
+
describe('TreeWalker edge cases', () => {
|
|
170
|
+
it('should handle elements with no initial text content using TreeWalker', () => {
|
|
171
|
+
const container = document.createElement('div');
|
|
172
|
+
// Create element with no textContent initially
|
|
173
|
+
container.textContent = '';
|
|
174
|
+
|
|
175
|
+
const span1 = document.createElement('span');
|
|
176
|
+
const span2 = document.createElement('span');
|
|
177
|
+
|
|
178
|
+
// Create text nodes directly
|
|
179
|
+
span1.appendChild(document.createTextNode('Text 1'));
|
|
180
|
+
span2.appendChild(document.createTextNode('Text 2'));
|
|
181
|
+
|
|
182
|
+
container.appendChild(span1);
|
|
183
|
+
container.appendChild(document.createTextNode(' ')); // whitespace node
|
|
184
|
+
container.appendChild(span2);
|
|
185
|
+
|
|
186
|
+
document.body.appendChild(container);
|
|
187
|
+
|
|
188
|
+
const result = getAccessibleText(container);
|
|
189
|
+
expect(result).toContain('Text 1');
|
|
190
|
+
expect(result).toContain('Text 2');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should filter empty text nodes using TreeWalker', () => {
|
|
194
|
+
const container = document.createElement('div');
|
|
195
|
+
container.innerHTML = ''; // Start with empty container
|
|
196
|
+
|
|
197
|
+
// Add mix of empty and non-empty text nodes
|
|
198
|
+
container.appendChild(document.createTextNode(''));
|
|
199
|
+
container.appendChild(document.createTextNode('Valid text'));
|
|
200
|
+
container.appendChild(document.createTextNode(' ')); // Only whitespace
|
|
201
|
+
container.appendChild(document.createTextNode('More text'));
|
|
202
|
+
|
|
203
|
+
document.body.appendChild(container);
|
|
204
|
+
|
|
205
|
+
const result = getAccessibleText(container);
|
|
206
|
+
expect(result).toContain('Valid text');
|
|
207
|
+
expect(result).toContain('More text');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should handle complex DOM with mixed content', () => {
|
|
211
|
+
const container = document.createElement('div');
|
|
212
|
+
container.innerHTML = `
|
|
213
|
+
<header>
|
|
214
|
+
<h1>Title</h1>
|
|
215
|
+
<nav>
|
|
216
|
+
<a href="#">Link 1</a>
|
|
217
|
+
<a href="#">Link 2</a>
|
|
218
|
+
</nav>
|
|
219
|
+
</header>
|
|
220
|
+
<main>
|
|
221
|
+
<p>Paragraph text</p>
|
|
222
|
+
<ul>
|
|
223
|
+
<li>Item 1</li>
|
|
224
|
+
<li>Item 2</li>
|
|
225
|
+
</ul>
|
|
226
|
+
</main>
|
|
227
|
+
`;
|
|
228
|
+
document.body.appendChild(container);
|
|
229
|
+
|
|
230
|
+
const result = getAccessibleText(container);
|
|
231
|
+
expect(result).toContain('Title');
|
|
232
|
+
expect(result).toContain('Link 1');
|
|
233
|
+
expect(result).toContain('Link 2');
|
|
234
|
+
expect(result).toContain('Paragraph text');
|
|
235
|
+
expect(result).toContain('Item 1');
|
|
236
|
+
expect(result).toContain('Item 2');
|
|
237
|
+
});
|
|
71
238
|
});
|
|
72
239
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
240
|
+
describe('special cases', () => {
|
|
241
|
+
it('should handle elements with only child elements and no text', () => {
|
|
242
|
+
const container = document.createElement('div');
|
|
243
|
+
const child1 = document.createElement('div');
|
|
244
|
+
const child2 = document.createElement('div');
|
|
245
|
+
|
|
246
|
+
container.appendChild(child1);
|
|
247
|
+
container.appendChild(child2);
|
|
248
|
+
document.body.appendChild(container);
|
|
249
|
+
|
|
250
|
+
expect(getAccessibleText(container)).toBe('');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should use TreeWalker when textContent is empty but has text nodes', () => {
|
|
254
|
+
const container = document.createElement('div');
|
|
255
|
+
document.body.appendChild(container);
|
|
256
|
+
|
|
257
|
+
// Create a scenario where textContent initially appears empty
|
|
258
|
+
// but TreeWalker can find text nodes
|
|
259
|
+
Object.defineProperty(container, 'textContent', {
|
|
260
|
+
get: function() { return ''; },
|
|
261
|
+
configurable: true
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Add actual text nodes that TreeWalker should find
|
|
265
|
+
const text1 = document.createTextNode('Hidden text 1');
|
|
266
|
+
const text2 = document.createTextNode('Hidden text 2');
|
|
267
|
+
container.appendChild(text1);
|
|
268
|
+
container.appendChild(text2);
|
|
269
|
+
|
|
270
|
+
const result = getAccessibleText(container);
|
|
271
|
+
expect(result).toBe('Hidden text 1 Hidden text 2');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should handle self-closing elements', () => {
|
|
275
|
+
const br = document.createElement('br');
|
|
276
|
+
document.body.appendChild(br);
|
|
277
|
+
|
|
278
|
+
expect(getAccessibleText(br)).toBe('');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should handle input elements with value', () => {
|
|
282
|
+
const input = document.createElement('input');
|
|
283
|
+
input.type = 'text';
|
|
284
|
+
input.value = 'Input value';
|
|
285
|
+
document.body.appendChild(input);
|
|
286
|
+
|
|
287
|
+
// Input elements don't have textContent
|
|
288
|
+
expect(getAccessibleText(input)).toBe('');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should handle elements with comment nodes', () => {
|
|
292
|
+
const container = document.createElement('div');
|
|
293
|
+
container.appendChild(document.createTextNode('Before'));
|
|
294
|
+
container.appendChild(document.createComment('This is a comment'));
|
|
295
|
+
container.appendChild(document.createTextNode('After'));
|
|
296
|
+
document.body.appendChild(container);
|
|
297
|
+
|
|
298
|
+
const result = getAccessibleText(container);
|
|
299
|
+
expect(result).toContain('Before');
|
|
300
|
+
expect(result).toContain('After');
|
|
301
|
+
expect(result).not.toContain('comment');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should handle TreeWalker acceptNode with element nodes that should be rejected', () => {
|
|
305
|
+
const container = document.createElement('div');
|
|
306
|
+
container.textContent = ''; // Ensure no textContent initially
|
|
307
|
+
|
|
308
|
+
// Create an element node (should be rejected by acceptNode)
|
|
309
|
+
const span = document.createElement('span');
|
|
310
|
+
span.appendChild(document.createTextNode('Text inside span'));
|
|
311
|
+
container.appendChild(span);
|
|
312
|
+
|
|
313
|
+
// Create a direct text node (should be accepted)
|
|
314
|
+
container.appendChild(document.createTextNode('Direct text'));
|
|
315
|
+
|
|
316
|
+
document.body.appendChild(container);
|
|
317
|
+
|
|
318
|
+
const result = getAccessibleText(container);
|
|
319
|
+
expect(result).toContain('Text inside span');
|
|
320
|
+
expect(result).toContain('Direct text');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should handle TreeWalker acceptNode filtering and text node collection', () => {
|
|
324
|
+
const container = document.createElement('div');
|
|
325
|
+
container.textContent = ''; // Force TreeWalker path
|
|
326
|
+
|
|
327
|
+
// Create child elements with text nodes to test TreeWalker
|
|
328
|
+
const child1 = document.createElement('span');
|
|
329
|
+
child1.appendChild(document.createTextNode('First'));
|
|
330
|
+
|
|
331
|
+
const child2 = document.createElement('span');
|
|
332
|
+
child2.appendChild(document.createTextNode('Second'));
|
|
333
|
+
|
|
334
|
+
container.appendChild(child1);
|
|
335
|
+
container.appendChild(document.createTextNode(' ')); // space between
|
|
336
|
+
container.appendChild(child2);
|
|
337
|
+
|
|
338
|
+
document.body.appendChild(container);
|
|
339
|
+
|
|
340
|
+
const result = getAccessibleText(container);
|
|
341
|
+
expect(result).toBe('First Second');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should test TreeWalker acceptNode function with various node types', () => {
|
|
345
|
+
const container = document.createElement('div');
|
|
346
|
+
document.body.appendChild(container);
|
|
347
|
+
|
|
348
|
+
// Override textContent to force TreeWalker path
|
|
349
|
+
Object.defineProperty(container, 'textContent', {
|
|
350
|
+
get: function() { return ''; },
|
|
351
|
+
configurable: true
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Add mix of empty and non-empty text nodes
|
|
355
|
+
container.appendChild(document.createTextNode('')); // Empty - should be rejected
|
|
356
|
+
container.appendChild(document.createTextNode(' ')); // Whitespace only - should be rejected
|
|
357
|
+
container.appendChild(document.createTextNode('Valid')); // Valid - should be accepted
|
|
358
|
+
container.appendChild(document.createTextNode('\n\t')); // Whitespace - should be rejected
|
|
359
|
+
container.appendChild(document.createTextNode('Text')); // Valid - should be accepted
|
|
360
|
+
|
|
361
|
+
const result = getAccessibleText(container);
|
|
362
|
+
expect(result).toBe('Valid Text');
|
|
363
|
+
});
|
|
93
364
|
});
|
|
94
365
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { getCSSGeneratedContent } from '../src/getCSSGeneratedContent';
|
|
3
3
|
|
|
4
4
|
describe('getCSSGeneratedContent', () => {
|
|
@@ -41,7 +41,16 @@ describe('getCSSGeneratedContent', () => {
|
|
|
41
41
|
const div = document.createElement('div');
|
|
42
42
|
document.body.appendChild(div);
|
|
43
43
|
|
|
44
|
+
// Mock getComputedStyle for this test since JSDOM doesn't support it
|
|
45
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
|
46
|
+
window.getComputedStyle = () => ({
|
|
47
|
+
getPropertyValue: () => 'none'
|
|
48
|
+
});
|
|
49
|
+
|
|
44
50
|
expect(getCSSGeneratedContent(div)).toBe(false);
|
|
51
|
+
|
|
52
|
+
// Restore original
|
|
53
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
45
54
|
});
|
|
46
55
|
|
|
47
56
|
it('should get ::before content', () => {
|
|
@@ -89,7 +98,16 @@ describe('getCSSGeneratedContent', () => {
|
|
|
89
98
|
div.className = 'empty-content';
|
|
90
99
|
document.body.appendChild(div);
|
|
91
100
|
|
|
101
|
+
// Mock getComputedStyle to return empty quoted content
|
|
102
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
|
103
|
+
window.getComputedStyle = () => ({
|
|
104
|
+
getPropertyValue: () => '""'
|
|
105
|
+
});
|
|
106
|
+
|
|
92
107
|
expect(getCSSGeneratedContent(div)).toBe(false);
|
|
108
|
+
|
|
109
|
+
// Restore original
|
|
110
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
93
111
|
});
|
|
94
112
|
|
|
95
113
|
it('should return false for content: none', () => {
|
|
@@ -99,4 +117,160 @@ describe('getCSSGeneratedContent', () => {
|
|
|
99
117
|
|
|
100
118
|
expect(getCSSGeneratedContent(div)).toBe(false);
|
|
101
119
|
});
|
|
120
|
+
|
|
121
|
+
it('should handle url content', () => {
|
|
122
|
+
const div = document.createElement('div');
|
|
123
|
+
div.className = 'url-content';
|
|
124
|
+
document.body.appendChild(div);
|
|
125
|
+
|
|
126
|
+
expect(getCSSGeneratedContent(div, 'before')).toBe('url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle undefined and non-string pseudoElement parameter', () => {
|
|
130
|
+
const div = document.createElement('div');
|
|
131
|
+
div.className = 'with-before';
|
|
132
|
+
document.body.appendChild(div);
|
|
133
|
+
|
|
134
|
+
// Should default to 'both' when no pseudoElement specified
|
|
135
|
+
expect(getCSSGeneratedContent(div, undefined)).toBe('Before Content');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('Browser implementation paths', () => {
|
|
139
|
+
let originalGetComputedStyle;
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
// Mock getComputedStyle to simulate real browser behavior
|
|
143
|
+
originalGetComputedStyle = window.getComputedStyle;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
afterEach(() => {
|
|
147
|
+
// Restore original getComputedStyle
|
|
148
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle quote removal in browser implementation', () => {
|
|
152
|
+
// Mock getComputedStyle to return quoted content
|
|
153
|
+
window.getComputedStyle = (el, pseudo) => ({
|
|
154
|
+
getPropertyValue: (prop) => {
|
|
155
|
+
if (prop === 'content' && pseudo === '::before') {
|
|
156
|
+
return '"Quoted Content"';
|
|
157
|
+
}
|
|
158
|
+
return 'none';
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const div = document.createElement('div');
|
|
163
|
+
// Remove classList to bypass JSDOM check
|
|
164
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
165
|
+
document.body.appendChild(div);
|
|
166
|
+
|
|
167
|
+
expect(getCSSGeneratedContent(div, 'before')).toBe('Quoted Content');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle single quote removal in browser implementation', () => {
|
|
171
|
+
window.getComputedStyle = (el, pseudo) => ({
|
|
172
|
+
getPropertyValue: (prop) => {
|
|
173
|
+
if (prop === 'content' && pseudo === '::before') {
|
|
174
|
+
return "'Single Quoted'";
|
|
175
|
+
}
|
|
176
|
+
return 'none';
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const div = document.createElement('div');
|
|
181
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
182
|
+
document.body.appendChild(div);
|
|
183
|
+
|
|
184
|
+
expect(getCSSGeneratedContent(div, 'before')).toBe('Single Quoted');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should handle both before and after content with quotes', () => {
|
|
188
|
+
window.getComputedStyle = (el, pseudo) => ({
|
|
189
|
+
getPropertyValue: (prop) => {
|
|
190
|
+
if (prop === 'content') {
|
|
191
|
+
if (pseudo === '::before') return '"Before"';
|
|
192
|
+
if (pseudo === '::after') return '"After"';
|
|
193
|
+
}
|
|
194
|
+
return 'none';
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const div = document.createElement('div');
|
|
199
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
200
|
+
document.body.appendChild(div);
|
|
201
|
+
|
|
202
|
+
expect(getCSSGeneratedContent(div, 'both')).toBe('Before After');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle after content with quotes', () => {
|
|
206
|
+
window.getComputedStyle = (el, pseudo) => ({
|
|
207
|
+
getPropertyValue: (prop) => {
|
|
208
|
+
if (prop === 'content' && pseudo === '::after') {
|
|
209
|
+
return '"After Content"';
|
|
210
|
+
}
|
|
211
|
+
return 'none';
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const div = document.createElement('div');
|
|
216
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
217
|
+
document.body.appendChild(div);
|
|
218
|
+
|
|
219
|
+
expect(getCSSGeneratedContent(div, 'after')).toBe('After Content');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should handle content: none in browser implementation', () => {
|
|
223
|
+
window.getComputedStyle = (el, pseudo) => ({
|
|
224
|
+
getPropertyValue: (prop) => 'none'
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const div = document.createElement('div');
|
|
228
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
229
|
+
document.body.appendChild(div);
|
|
230
|
+
|
|
231
|
+
expect(getCSSGeneratedContent(div)).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should handle content: normal in browser implementation', () => {
|
|
235
|
+
window.getComputedStyle = (el, pseudo) => ({
|
|
236
|
+
getPropertyValue: (prop) => 'normal'
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const div = document.createElement('div');
|
|
240
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
241
|
+
document.body.appendChild(div);
|
|
242
|
+
|
|
243
|
+
expect(getCSSGeneratedContent(div)).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should handle empty quoted content', () => {
|
|
247
|
+
window.getComputedStyle = (el, pseudo) => ({
|
|
248
|
+
getPropertyValue: (prop) => {
|
|
249
|
+
if (prop === 'content') {
|
|
250
|
+
if (pseudo === '::before') return '""';
|
|
251
|
+
}
|
|
252
|
+
return 'none';
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const div = document.createElement('div');
|
|
257
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
258
|
+
document.body.appendChild(div);
|
|
259
|
+
|
|
260
|
+
expect(getCSSGeneratedContent(div, 'before')).toBe(false);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should handle getComputedStyle error', () => {
|
|
264
|
+
// Mock getComputedStyle to throw an error
|
|
265
|
+
window.getComputedStyle = () => {
|
|
266
|
+
throw new Error('getComputedStyle not supported');
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const div = document.createElement('div');
|
|
270
|
+
Object.defineProperty(div, 'classList', { value: undefined });
|
|
271
|
+
document.body.appendChild(div);
|
|
272
|
+
|
|
273
|
+
expect(getCSSGeneratedContent(div)).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
102
276
|
});
|