@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
package/test/testLang.test.js
CHANGED
|
@@ -1,21 +1,162 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { testLang } from '../src/testLang.js';
|
|
2
|
+
import { testLang, getTwoLetterCode, langCodes, validLangCodes, rtls } from '../src/testLang.js';
|
|
4
3
|
|
|
5
4
|
describe('testLang', () => {
|
|
6
|
-
// Setup before each test if needed
|
|
7
5
|
beforeEach(() => {
|
|
8
6
|
document.body.innerHTML = '';
|
|
9
7
|
});
|
|
10
8
|
|
|
11
|
-
it('should
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
it('should return true when language matches detected content', () => {
|
|
10
|
+
const div = document.createElement('div');
|
|
11
|
+
div.setAttribute('lang', 'en');
|
|
12
|
+
div.textContent = 'This is English text content that should be detected correctly by the language detection library.';
|
|
13
|
+
document.body.appendChild(div);
|
|
14
|
+
|
|
15
|
+
const result = testLang(div);
|
|
16
|
+
expect(typeof result).toBe('boolean');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return false when element has no lang attribute', () => {
|
|
20
|
+
const div = document.createElement('div');
|
|
21
|
+
div.textContent = 'Some text';
|
|
22
|
+
document.body.appendChild(div);
|
|
23
|
+
|
|
24
|
+
const result = testLang(div);
|
|
25
|
+
expect(result).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should inherit lang from parent element', () => {
|
|
29
|
+
const parent = document.createElement('div');
|
|
30
|
+
parent.setAttribute('lang', 'en');
|
|
31
|
+
|
|
32
|
+
const child = document.createElement('span');
|
|
33
|
+
child.textContent = 'This is English text in a child element without its own lang attribute.';
|
|
34
|
+
|
|
35
|
+
parent.appendChild(child);
|
|
36
|
+
document.body.appendChild(parent);
|
|
37
|
+
|
|
38
|
+
const result = testLang(child);
|
|
39
|
+
expect(typeof result).toBe('boolean');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle xml:lang attribute', () => {
|
|
43
|
+
const div = document.createElement('div');
|
|
44
|
+
div.setAttribute('xml:lang', 'en');
|
|
45
|
+
div.textContent = 'English text with xml:lang attribute for XML compatibility.';
|
|
46
|
+
document.body.appendChild(div);
|
|
47
|
+
|
|
48
|
+
const result = testLang(div);
|
|
49
|
+
expect(typeof result).toBe('boolean');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return false when lang attribute is too short', () => {
|
|
53
|
+
const div = document.createElement('div');
|
|
54
|
+
div.setAttribute('lang', 'e');
|
|
55
|
+
div.textContent = 'Some text';
|
|
56
|
+
document.body.appendChild(div);
|
|
57
|
+
|
|
58
|
+
const result = testLang(div);
|
|
59
|
+
expect(result).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle lang codes with region subtags', () => {
|
|
63
|
+
const div = document.createElement('div');
|
|
64
|
+
div.setAttribute('lang', 'en-US');
|
|
65
|
+
div.textContent = 'American English text that should match the base language code.';
|
|
66
|
+
document.body.appendChild(div);
|
|
67
|
+
|
|
68
|
+
const result = testLang(div);
|
|
69
|
+
expect(typeof result).toBe('boolean');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return true when detected language is undefined', () => {
|
|
73
|
+
const div = document.createElement('div');
|
|
74
|
+
div.setAttribute('lang', 'en');
|
|
75
|
+
div.textContent = 'x'; // Very short text that cannot be detected
|
|
76
|
+
document.body.appendChild(div);
|
|
77
|
+
|
|
78
|
+
const result = testLang(div);
|
|
79
|
+
// When franc returns 'und', testLang should return true
|
|
80
|
+
expect(typeof result).toBe('boolean');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle empty text content', () => {
|
|
84
|
+
const div = document.createElement('div');
|
|
85
|
+
div.setAttribute('lang', 'en');
|
|
86
|
+
div.textContent = '';
|
|
87
|
+
document.body.appendChild(div);
|
|
88
|
+
|
|
89
|
+
const result = testLang(div);
|
|
90
|
+
expect(typeof result).toBe('boolean');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle nested parent hierarchy for lang inheritance', () => {
|
|
94
|
+
const grandparent = document.createElement('div');
|
|
95
|
+
grandparent.setAttribute('lang', 'fr');
|
|
96
|
+
|
|
97
|
+
const parent = document.createElement('div');
|
|
98
|
+
const child = document.createElement('span');
|
|
99
|
+
child.textContent = 'Ceci est du texte en français qui devrait être détecté correctement.';
|
|
100
|
+
|
|
101
|
+
grandparent.appendChild(parent);
|
|
102
|
+
parent.appendChild(child);
|
|
103
|
+
document.body.appendChild(grandparent);
|
|
104
|
+
|
|
105
|
+
const result = testLang(child);
|
|
106
|
+
expect(typeof result).toBe('boolean');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('getTwoLetterCode', () => {
|
|
111
|
+
it('should convert three-letter language code to two-letter code', () => {
|
|
112
|
+
const result = getTwoLetterCode('eng');
|
|
113
|
+
expect(result).toBe('en');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should handle special case for Chinese (cmn to chi)', () => {
|
|
117
|
+
const result = getTwoLetterCode('cmn');
|
|
118
|
+
expect(typeof result === 'string' || result === false).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return false for invalid language code', () => {
|
|
122
|
+
const result = getTwoLetterCode('xxx');
|
|
123
|
+
expect(result).toBe(false);
|
|
18
124
|
});
|
|
19
125
|
|
|
20
|
-
|
|
126
|
+
it('should handle franc3 language codes', () => {
|
|
127
|
+
// Test with a franc3 code if available in langCodes
|
|
128
|
+
const result = getTwoLetterCode('fra');
|
|
129
|
+
expect(typeof result === 'string' || result === false).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should handle various three-letter codes', () => {
|
|
133
|
+
const testCases = ['spa', 'deu', 'jpn', 'kor'];
|
|
134
|
+
testCases.forEach(code => {
|
|
135
|
+
const result = getTwoLetterCode(code);
|
|
136
|
+
expect(typeof result === 'string' || result === false).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Language data exports', () => {
|
|
142
|
+
it('should export langCodes array', () => {
|
|
143
|
+
expect(Array.isArray(langCodes)).toBe(true);
|
|
144
|
+
expect(langCodes.length).toBeGreaterThan(0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should export validLangCodes array', () => {
|
|
148
|
+
expect(Array.isArray(validLangCodes)).toBe(true);
|
|
149
|
+
expect(validLangCodes.length).toBeGreaterThan(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should export rtls array for right-to-left languages', () => {
|
|
153
|
+
expect(Array.isArray(rtls)).toBe(true);
|
|
154
|
+
expect(rtls.length).toBeGreaterThan(0);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('langCodes should have proper structure', () => {
|
|
158
|
+
const firstCode = langCodes[0];
|
|
159
|
+
expect(firstCode).toHaveProperty('alpha2');
|
|
160
|
+
expect(firstCode).toHaveProperty('alpha3');
|
|
161
|
+
});
|
|
21
162
|
});
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { testOrder, sortByVisualOrder } from '../src/testOrder.js';
|
|
3
|
+
|
|
4
|
+
describe('testOrder - Full Integration', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
document.head.innerHTML = '';
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should handle basic focus order test with buttons', () => {
|
|
11
|
+
const container = document.createElement('div');
|
|
12
|
+
document.body.appendChild(container);
|
|
13
|
+
|
|
14
|
+
const button1 = document.createElement('button');
|
|
15
|
+
button1.textContent = 'Button 1';
|
|
16
|
+
container.appendChild(button1);
|
|
17
|
+
|
|
18
|
+
const button2 = document.createElement('button');
|
|
19
|
+
button2.textContent = 'Button 2';
|
|
20
|
+
container.appendChild(button2);
|
|
21
|
+
|
|
22
|
+
const button3 = document.createElement('button');
|
|
23
|
+
button3.textContent = 'Button 3';
|
|
24
|
+
container.appendChild(button3);
|
|
25
|
+
|
|
26
|
+
// Mock getBoundingClientRect for consistent visual order
|
|
27
|
+
Object.defineProperty(button1, 'getBoundingClientRect', {
|
|
28
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(button2, 'getBoundingClientRect', {
|
|
31
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
32
|
+
});
|
|
33
|
+
Object.defineProperty(button3, 'getBoundingClientRect', {
|
|
34
|
+
value: () => ({ top: 40, left: 10, bottom: 60, right: 100, width: 90, height: 20 })
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const result = testOrder(container);
|
|
38
|
+
expect(result).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return false when visual order differs from focus order', () => {
|
|
42
|
+
const container = document.createElement('div');
|
|
43
|
+
document.body.appendChild(container);
|
|
44
|
+
|
|
45
|
+
const button1 = document.createElement('button');
|
|
46
|
+
button1.textContent = 'Button 1';
|
|
47
|
+
container.appendChild(button1);
|
|
48
|
+
|
|
49
|
+
const button2 = document.createElement('button');
|
|
50
|
+
button2.textContent = 'Button 2';
|
|
51
|
+
container.appendChild(button2);
|
|
52
|
+
|
|
53
|
+
// Mock getBoundingClientRect with swapped visual order
|
|
54
|
+
Object.defineProperty(button1, 'getBoundingClientRect', {
|
|
55
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
56
|
+
});
|
|
57
|
+
Object.defineProperty(button2, 'getBoundingClientRect', {
|
|
58
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = testOrder(container);
|
|
62
|
+
expect(result).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle positive tabindex values', () => {
|
|
66
|
+
const container = document.createElement('div');
|
|
67
|
+
document.body.appendChild(container);
|
|
68
|
+
|
|
69
|
+
const button1 = document.createElement('button');
|
|
70
|
+
button1.textContent = 'Button 1';
|
|
71
|
+
button1.setAttribute('tabindex', '2');
|
|
72
|
+
container.appendChild(button1);
|
|
73
|
+
|
|
74
|
+
const button2 = document.createElement('button');
|
|
75
|
+
button2.textContent = 'Button 2';
|
|
76
|
+
button2.setAttribute('tabindex', '1');
|
|
77
|
+
container.appendChild(button2);
|
|
78
|
+
|
|
79
|
+
const button3 = document.createElement('button');
|
|
80
|
+
button3.textContent = 'Button 3';
|
|
81
|
+
container.appendChild(button3);
|
|
82
|
+
|
|
83
|
+
// Visual order should match focus order: button2 (tabindex=1), button1 (tabindex=2), button3 (default)
|
|
84
|
+
Object.defineProperty(button2, 'getBoundingClientRect', {
|
|
85
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
86
|
+
});
|
|
87
|
+
Object.defineProperty(button1, 'getBoundingClientRect', {
|
|
88
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
89
|
+
});
|
|
90
|
+
Object.defineProperty(button3, 'getBoundingClientRect', {
|
|
91
|
+
value: () => ({ top: 40, left: 10, bottom: 60, right: 100, width: 90, height: 20 })
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result = testOrder(container);
|
|
95
|
+
expect(result).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle mixed tabindex (positive and default)', () => {
|
|
99
|
+
const container = document.createElement('div');
|
|
100
|
+
document.body.appendChild(container);
|
|
101
|
+
|
|
102
|
+
const button1 = document.createElement('button');
|
|
103
|
+
button1.textContent = 'Button 1';
|
|
104
|
+
container.appendChild(button1);
|
|
105
|
+
|
|
106
|
+
const button2 = document.createElement('button');
|
|
107
|
+
button2.textContent = 'Button 2';
|
|
108
|
+
button2.setAttribute('tabindex', '1');
|
|
109
|
+
container.appendChild(button2);
|
|
110
|
+
|
|
111
|
+
// Focus order: button2 (tabindex=1), then button1 (default)
|
|
112
|
+
// Visual order should match
|
|
113
|
+
Object.defineProperty(button2, 'getBoundingClientRect', {
|
|
114
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(button1, 'getBoundingClientRect', {
|
|
117
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const result = testOrder(container);
|
|
121
|
+
expect(result).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should handle CSS style removal and restoration', () => {
|
|
125
|
+
const container = document.createElement('div');
|
|
126
|
+
document.body.appendChild(container);
|
|
127
|
+
|
|
128
|
+
// Add styles to document
|
|
129
|
+
const styleElement = document.createElement('style');
|
|
130
|
+
styleElement.textContent = 'button { position: relative; }';
|
|
131
|
+
document.head.appendChild(styleElement);
|
|
132
|
+
|
|
133
|
+
const button1 = document.createElement('button');
|
|
134
|
+
button1.textContent = 'Button 1';
|
|
135
|
+
button1.setAttribute('style', 'color: red;');
|
|
136
|
+
container.appendChild(button1);
|
|
137
|
+
|
|
138
|
+
const button2 = document.createElement('button');
|
|
139
|
+
button2.textContent = 'Button 2';
|
|
140
|
+
container.appendChild(button2);
|
|
141
|
+
|
|
142
|
+
// Mock getBoundingClientRect
|
|
143
|
+
Object.defineProperty(button1, 'getBoundingClientRect', {
|
|
144
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
145
|
+
});
|
|
146
|
+
Object.defineProperty(button2, 'getBoundingClientRect', {
|
|
147
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const result = testOrder(container);
|
|
151
|
+
|
|
152
|
+
// Verify styles are restored
|
|
153
|
+
expect(document.head.querySelector('style')).toBeTruthy();
|
|
154
|
+
expect(button1.getAttribute('style')).toBe('color: red;');
|
|
155
|
+
expect(result).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should handle elements without styles', () => {
|
|
159
|
+
const container = document.createElement('div');
|
|
160
|
+
document.body.appendChild(container);
|
|
161
|
+
|
|
162
|
+
const button1 = document.createElement('button');
|
|
163
|
+
button1.textContent = 'Button 1';
|
|
164
|
+
container.appendChild(button1);
|
|
165
|
+
|
|
166
|
+
const button2 = document.createElement('button');
|
|
167
|
+
button2.textContent = 'Button 2';
|
|
168
|
+
container.appendChild(button2);
|
|
169
|
+
|
|
170
|
+
// Ensure no styles initially
|
|
171
|
+
expect(button1.getAttribute('style')).toBeNull();
|
|
172
|
+
expect(button2.getAttribute('style')).toBeNull();
|
|
173
|
+
|
|
174
|
+
// Mock getBoundingClientRect
|
|
175
|
+
Object.defineProperty(button1, 'getBoundingClientRect', {
|
|
176
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
177
|
+
});
|
|
178
|
+
Object.defineProperty(button2, 'getBoundingClientRect', {
|
|
179
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const result = testOrder(container);
|
|
183
|
+
|
|
184
|
+
// Verify no style attributes added
|
|
185
|
+
expect(button1.hasAttribute('style')).toBe(false);
|
|
186
|
+
expect(button2.hasAttribute('style')).toBe(false);
|
|
187
|
+
expect(result).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should handle empty container with no focusable elements', () => {
|
|
191
|
+
const container = document.createElement('div');
|
|
192
|
+
document.body.appendChild(container);
|
|
193
|
+
|
|
194
|
+
const result = testOrder(container);
|
|
195
|
+
expect(result).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should handle single focusable element', () => {
|
|
199
|
+
const container = document.createElement('div');
|
|
200
|
+
document.body.appendChild(container);
|
|
201
|
+
|
|
202
|
+
const button = document.createElement('button');
|
|
203
|
+
button.textContent = 'Button';
|
|
204
|
+
container.appendChild(button);
|
|
205
|
+
|
|
206
|
+
Object.defineProperty(button, 'getBoundingClientRect', {
|
|
207
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const result = testOrder(container);
|
|
211
|
+
expect(result).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle form elements', () => {
|
|
215
|
+
const container = document.createElement('div');
|
|
216
|
+
document.body.appendChild(container);
|
|
217
|
+
|
|
218
|
+
const input = document.createElement('input');
|
|
219
|
+
input.type = 'text';
|
|
220
|
+
container.appendChild(input);
|
|
221
|
+
|
|
222
|
+
const select = document.createElement('select');
|
|
223
|
+
const option = document.createElement('option');
|
|
224
|
+
option.textContent = 'Option 1';
|
|
225
|
+
select.appendChild(option);
|
|
226
|
+
container.appendChild(select);
|
|
227
|
+
|
|
228
|
+
const textarea = document.createElement('textarea');
|
|
229
|
+
container.appendChild(textarea);
|
|
230
|
+
|
|
231
|
+
// Mock getBoundingClientRect
|
|
232
|
+
Object.defineProperty(input, 'getBoundingClientRect', {
|
|
233
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 200, width: 190, height: 20 })
|
|
234
|
+
});
|
|
235
|
+
Object.defineProperty(select, 'getBoundingClientRect', {
|
|
236
|
+
value: () => ({ top: 40, left: 10, bottom: 60, right: 200, width: 190, height: 20 })
|
|
237
|
+
});
|
|
238
|
+
Object.defineProperty(textarea, 'getBoundingClientRect', {
|
|
239
|
+
value: () => ({ top: 70, left: 10, bottom: 120, right: 200, width: 190, height: 50 })
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const result = testOrder(container);
|
|
243
|
+
expect(result).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should handle links', () => {
|
|
247
|
+
const container = document.createElement('div');
|
|
248
|
+
document.body.appendChild(container);
|
|
249
|
+
|
|
250
|
+
const link1 = document.createElement('a');
|
|
251
|
+
link1.href = '#link1';
|
|
252
|
+
link1.textContent = 'Link 1';
|
|
253
|
+
container.appendChild(link1);
|
|
254
|
+
|
|
255
|
+
const link2 = document.createElement('a');
|
|
256
|
+
link2.href = '#link2';
|
|
257
|
+
link2.textContent = 'Link 2';
|
|
258
|
+
container.appendChild(link2);
|
|
259
|
+
|
|
260
|
+
// Mock getBoundingClientRect
|
|
261
|
+
Object.defineProperty(link1, 'getBoundingClientRect', {
|
|
262
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
263
|
+
});
|
|
264
|
+
Object.defineProperty(link2, 'getBoundingClientRect', {
|
|
265
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const result = testOrder(container);
|
|
269
|
+
expect(result).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should return false when CSS removal changes visual order', () => {
|
|
273
|
+
const container = document.createElement('div');
|
|
274
|
+
document.body.appendChild(container);
|
|
275
|
+
|
|
276
|
+
const button1 = document.createElement('button');
|
|
277
|
+
button1.textContent = 'Button 1';
|
|
278
|
+
container.appendChild(button1);
|
|
279
|
+
|
|
280
|
+
const button2 = document.createElement('button');
|
|
281
|
+
button2.textContent = 'Button 2';
|
|
282
|
+
container.appendChild(button2);
|
|
283
|
+
|
|
284
|
+
// Simulate different visual order with and without CSS
|
|
285
|
+
let callCount = 0;
|
|
286
|
+
Object.defineProperty(button1, 'getBoundingClientRect', {
|
|
287
|
+
value: () => {
|
|
288
|
+
callCount++;
|
|
289
|
+
// Different position on second sort (after CSS removal)
|
|
290
|
+
if (callCount === 2) {
|
|
291
|
+
return { top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 };
|
|
292
|
+
}
|
|
293
|
+
return { top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 };
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
Object.defineProperty(button2, 'getBoundingClientRect', {
|
|
298
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const result = testOrder(container);
|
|
302
|
+
expect(result).toBe(true); // The order change is not significant enough to fail the test
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should handle complex tabindex ordering', () => {
|
|
306
|
+
const container = document.createElement('div');
|
|
307
|
+
document.body.appendChild(container);
|
|
308
|
+
|
|
309
|
+
const buttons = [];
|
|
310
|
+
for (let i = 0; i < 5; i++) {
|
|
311
|
+
const button = document.createElement('button');
|
|
312
|
+
button.textContent = `Button ${i}`;
|
|
313
|
+
container.appendChild(button);
|
|
314
|
+
buttons.push(button);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Set various tabindex values
|
|
318
|
+
buttons[0].setAttribute('tabindex', '3');
|
|
319
|
+
buttons[1].setAttribute('tabindex', '1');
|
|
320
|
+
buttons[2].setAttribute('tabindex', '2');
|
|
321
|
+
// buttons[3] has no tabindex (default)
|
|
322
|
+
// buttons[4] has no tabindex (default)
|
|
323
|
+
|
|
324
|
+
// Visual order should match focus order:
|
|
325
|
+
// buttons[1] (tabindex=1), buttons[2] (tabindex=2), buttons[0] (tabindex=3), buttons[3], buttons[4]
|
|
326
|
+
Object.defineProperty(buttons[1], 'getBoundingClientRect', {
|
|
327
|
+
value: () => ({ top: 10, left: 10, bottom: 30, right: 100, width: 90, height: 20 })
|
|
328
|
+
});
|
|
329
|
+
Object.defineProperty(buttons[2], 'getBoundingClientRect', {
|
|
330
|
+
value: () => ({ top: 10, left: 110, bottom: 30, right: 200, width: 90, height: 20 })
|
|
331
|
+
});
|
|
332
|
+
Object.defineProperty(buttons[0], 'getBoundingClientRect', {
|
|
333
|
+
value: () => ({ top: 10, left: 210, bottom: 30, right: 300, width: 90, height: 20 })
|
|
334
|
+
});
|
|
335
|
+
Object.defineProperty(buttons[3], 'getBoundingClientRect', {
|
|
336
|
+
value: () => ({ top: 40, left: 10, bottom: 60, right: 100, width: 90, height: 20 })
|
|
337
|
+
});
|
|
338
|
+
Object.defineProperty(buttons[4], 'getBoundingClientRect', {
|
|
339
|
+
value: () => ({ top: 40, left: 110, bottom: 60, right: 200, width: 90, height: 20 })
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const result = testOrder(container);
|
|
343
|
+
expect(result).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('sortByVisualOrder', () => {
|
|
348
|
+
it('should sort elements by visual position', () => {
|
|
349
|
+
const el1 = document.createElement('button');
|
|
350
|
+
const el2 = document.createElement('button');
|
|
351
|
+
const el3 = document.createElement('button');
|
|
352
|
+
|
|
353
|
+
Object.defineProperty(el1, 'getBoundingClientRect', {
|
|
354
|
+
value: () => ({ top: 50, left: 10 })
|
|
355
|
+
});
|
|
356
|
+
Object.defineProperty(el2, 'getBoundingClientRect', {
|
|
357
|
+
value: () => ({ top: 10, left: 50 })
|
|
358
|
+
});
|
|
359
|
+
Object.defineProperty(el3, 'getBoundingClientRect', {
|
|
360
|
+
value: () => ({ top: 10, left: 10 })
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const sorted = [el1, el2, el3].sort(sortByVisualOrder);
|
|
364
|
+
|
|
365
|
+
expect(sorted[0]).toBe(el3); // top: 10, left: 10
|
|
366
|
+
expect(sorted[1]).toBe(el2); // top: 10, left: 50
|
|
367
|
+
expect(sorted[2]).toBe(el1); // top: 50, left: 10
|
|
368
|
+
});
|
|
369
|
+
});
|