@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.
Files changed (132) hide show
  1. package/.claude/settings.local.json +6 -2
  2. package/.github/workflows/test.yml +26 -0
  3. package/BROWSER_TESTING.md +109 -0
  4. package/CLAUDE.md +22 -0
  5. package/package.json +6 -8
  6. package/playwright.config.js +27 -0
  7. package/src/domUtils.js +1 -1
  8. package/src/getAccessibleName.js +8 -4
  9. package/src/getCSSGeneratedContent.js +9 -5
  10. package/src/getFocusableElements.js +13 -4
  11. package/src/getImageText.js +4 -1
  12. package/src/testContrast.js +5 -1
  13. package/test/__screenshots__/getImageText.test.js/getImageText-should-be-an-async-function-1.png +0 -0
  14. package/test/__screenshots__/getImageText.test.js/getImageText-should-be-defined-and-exported-from-the-module-1.png +0 -0
  15. package/test/__screenshots__/getImageText.test.js/getImageText-should-handle-empty-string-input-gracefully-1.png +0 -0
  16. package/test/__screenshots__/getImageText.test.js/getImageText-should-handle-invalid-image-paths-gracefully-1.png +0 -0
  17. package/test/__screenshots__/getImageText.test.js/getImageText-should-handle-null-or-undefined-input-gracefully-1.png +0 -0
  18. package/test/__screenshots__/getImageText.test.js/getImageText-should-log-errors-in-non-test-environments-1.png +0 -0
  19. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-call-original-addEventListener-1.png +0 -0
  20. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-added-event-listeners-1.png +0 -0
  21. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-listeners-for-different-event-types-1.png +0 -0
  22. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-multiple-listeners-for-the-same-event-1.png +0 -0
  23. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-addEventListener-override-should-track-options-parameter-1.png +0 -0
  24. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getEventListeners-should-return-all-event-listeners-for-an-element-1.png +0 -0
  25. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getEventListeners-should-return-empty-object-for-elements-without-listeners-1.png +0 -0
  26. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getXPath-should-generate-XPath-for-elements-without-id-1.png +0 -0
  27. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getXPath-should-handle-multiple-siblings-correctly-1.png +0 -0
  28. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-getXPath-should-return-XPath-for-element-with-id-1.png +0 -0
  29. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-event-listeners-on-child-elements-1.png +0 -0
  30. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-event-listeners-on-root-element-1.png +0 -0
  31. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-listeners-from-multiple-elements-1.png +0 -0
  32. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-list-multiple-event-types-on-same-element-1.png +0 -0
  33. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-return-empty-array-when-no-event-listeners-exist-1.png +0 -0
  34. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-use-document-as-default-root-element-1.png +0 -0
  35. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-listEventListeners-should-work-with-custom-root-element-1.png +0 -0
  36. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-call-original-removeEventListener-1.png +0 -0
  37. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-handle-removing-non-existent-listeners-gracefully-1.png +0 -0
  38. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-only-remove-the-specified-listener-1.png +0 -0
  39. package/test/__screenshots__/listEventListeners.test.js/listEventListeners-removeEventListener-override-should-remove-tracked-event-listeners-1.png +0 -0
  40. package/test/arrayUtils.test.js +22 -0
  41. package/test/domUtils.test.js +241 -0
  42. package/test/getAccessibleName.test.js +182 -0
  43. package/test/getAccessibleText.test.js +350 -79
  44. package/test/getCSSGeneratedContent.test.js +175 -1
  45. package/test/getFocusableElements.test.js +106 -35
  46. package/test/getImageText.test.js +95 -12
  47. package/test/getStyleObject.test.js +19 -1
  48. package/test/hasCSSGeneratedContent.test.js +7 -2
  49. package/test/hasParent.test.js +116 -0
  50. package/test/hasValidAriaRole.test.js +64 -2
  51. package/test/index.test.js +165 -0
  52. package/test/interactiveRoles.test.js +60 -0
  53. package/test/isAriaAttributesValid.test.js +36 -0
  54. package/test/isDataTable.test.js +492 -0
  55. package/test/isFocusable.test.js +94 -1
  56. package/test/isValidUrl.test.js +31 -19
  57. package/test/isVisible.test.js +121 -3
  58. package/test/playwright/css-pseudo-elements.spec.js +155 -0
  59. package/test/playwright/fixtures/css-pseudo-elements.html +77 -0
  60. package/test/setup.js +9 -1
  61. package/test/stringUtils.test.js +277 -1
  62. package/test/testContrast.test.js +614 -9
  63. package/test/testLang.test.js +152 -11
  64. package/test/testOrder.integration.test.js +369 -0
  65. package/test/testOrder.test.js +756 -21
  66. package/todo.md +11 -1
  67. package/vitest.config.js +8 -1
  68. package/coverage/base.css +0 -224
  69. package/coverage/block-navigation.js +0 -87
  70. package/coverage/coverage-final.json +0 -51
  71. package/coverage/favicon.png +0 -0
  72. package/coverage/index.html +0 -161
  73. package/coverage/prettify.css +0 -1
  74. package/coverage/prettify.js +0 -2
  75. package/coverage/sort-arrow-sprite.png +0 -0
  76. package/coverage/sorter.js +0 -196
  77. package/coverage/test-utils/docs/scripts/core.js.html +0 -2263
  78. package/coverage/test-utils/docs/scripts/core.min.js.html +0 -151
  79. package/coverage/test-utils/docs/scripts/index.html +0 -176
  80. package/coverage/test-utils/docs/scripts/resize.js.html +0 -355
  81. package/coverage/test-utils/docs/scripts/search.js.html +0 -880
  82. package/coverage/test-utils/docs/scripts/search.min.js.html +0 -100
  83. package/coverage/test-utils/docs/scripts/third-party/fuse.js.html +0 -109
  84. package/coverage/test-utils/docs/scripts/third-party/hljs-line-num-original.js.html +0 -1192
  85. package/coverage/test-utils/docs/scripts/third-party/hljs-line-num.js.html +0 -85
  86. package/coverage/test-utils/docs/scripts/third-party/hljs-original.js.html +0 -15598
  87. package/coverage/test-utils/docs/scripts/third-party/hljs.js.html +0 -85
  88. package/coverage/test-utils/docs/scripts/third-party/index.html +0 -236
  89. package/coverage/test-utils/docs/scripts/third-party/popper.js.html +0 -100
  90. package/coverage/test-utils/docs/scripts/third-party/tippy.js.html +0 -88
  91. package/coverage/test-utils/docs/scripts/third-party/tocbot.js.html +0 -2098
  92. package/coverage/test-utils/docs/scripts/third-party/tocbot.min.js.html +0 -85
  93. package/coverage/test-utils/index.html +0 -131
  94. package/coverage/test-utils/src/arrayUtils.js.html +0 -283
  95. package/coverage/test-utils/src/domUtils.js.html +0 -622
  96. package/coverage/test-utils/src/getAccessibleName.js.html +0 -1444
  97. package/coverage/test-utils/src/getAccessibleText.js.html +0 -271
  98. package/coverage/test-utils/src/getAriaAttributesByElement.js.html +0 -142
  99. package/coverage/test-utils/src/getCSSGeneratedContent.js.html +0 -265
  100. package/coverage/test-utils/src/getComputedRole.js.html +0 -592
  101. package/coverage/test-utils/src/getFocusableElements.js.html +0 -163
  102. package/coverage/test-utils/src/getGeneratedContent.js.html +0 -130
  103. package/coverage/test-utils/src/getImageText.js.html +0 -160
  104. package/coverage/test-utils/src/getStyleObject.js.html +0 -220
  105. package/coverage/test-utils/src/hasAccessibleName.js.html +0 -166
  106. package/coverage/test-utils/src/hasAttribute.js.html +0 -130
  107. package/coverage/test-utils/src/hasCSSGeneratedContent.js.html +0 -145
  108. package/coverage/test-utils/src/hasHiddenParent.js.html +0 -172
  109. package/coverage/test-utils/src/hasParent.js.html +0 -247
  110. package/coverage/test-utils/src/hasValidAriaAttributes.js.html +0 -175
  111. package/coverage/test-utils/src/hasValidAriaRole.js.html +0 -172
  112. package/coverage/test-utils/src/index.html +0 -611
  113. package/coverage/test-utils/src/index.js.html +0 -274
  114. package/coverage/test-utils/src/interactiveRoles.js.html +0 -145
  115. package/coverage/test-utils/src/isAriaAttributesValid.js.html +0 -304
  116. package/coverage/test-utils/src/isComplexTable.js.html +0 -412
  117. package/coverage/test-utils/src/isDataTable.js.html +0 -799
  118. package/coverage/test-utils/src/isFocusable.js.html +0 -187
  119. package/coverage/test-utils/src/isHidden.js.html +0 -136
  120. package/coverage/test-utils/src/isOffScreen.js.html +0 -133
  121. package/coverage/test-utils/src/isValidUrl.js.html +0 -124
  122. package/coverage/test-utils/src/isVisible.js.html +0 -271
  123. package/coverage/test-utils/src/listEventListeners.js.html +0 -370
  124. package/coverage/test-utils/src/queryCache.js.html +0 -1156
  125. package/coverage/test-utils/src/stringUtils.js.html +0 -535
  126. package/coverage/test-utils/src/testContrast.js.html +0 -784
  127. package/coverage/test-utils/src/testLang.js.html +0 -1810
  128. package/coverage/test-utils/src/testOrder.js.html +0 -355
  129. package/coverage/test-utils/vitest.config.browser.js.html +0 -133
  130. package/coverage/test-utils/vitest.config.js.html +0 -157
  131. package/test/browser-setup.js +0 -68
  132. package/vitest.config.browser.js +0 -17
@@ -1,52 +1,787 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import { testOrder, sortByVisualOrder } from '../src/testOrder.js';
3
3
 
4
- // Note: Mocking CommonJS modules from ES6 test files in Vitest has compatibility issues
5
- // The actual implementation works correctly, but these tests are skipped due to mock limitations
4
+ // Use manual mock without vi.mock for simplicity
5
+ const mockedGetFocusableElements = vi.fn(() => []);
6
6
 
7
- describe('testOrder', () => {
7
+ // Manually replace the import
8
+ vi.doMock('../src/getFocusableElements.js', () => ({
9
+ getFocusableElements: mockedGetFocusableElements
10
+ }));
11
+
12
+ describe('sortByVisualOrder', () => {
8
13
  beforeEach(() => {
9
- document.body.innerHTML = '';
10
- document.head.innerHTML = '';
11
14
  vi.clearAllMocks();
12
15
  });
13
16
 
14
- it('should sort elements by visual order (top to bottom, left to right)', () => {
15
- // Arrange: Create elements with different positions
17
+ it('should sort elements by top position first', () => {
18
+ // Arrange: Create elements with different top positions
16
19
  const el1 = document.createElement('button');
17
20
  Object.defineProperty(el1, 'getBoundingClientRect', {
18
- value: () => ({ top: 10, left: 10 })
21
+ value: () => ({ top: 100, left: 10 })
19
22
  });
20
23
 
21
24
  const el2 = document.createElement('button');
22
25
  Object.defineProperty(el2, 'getBoundingClientRect', {
26
+ value: () => ({ top: 50, left: 10 })
27
+ });
28
+
29
+ const el3 = document.createElement('button');
30
+ Object.defineProperty(el3, 'getBoundingClientRect', {
31
+ value: () => ({ top: 10, left: 10 })
32
+ });
33
+
34
+ // Act: Sort the elements
35
+ const result = [el1, el2, el3].sort(sortByVisualOrder);
36
+
37
+ // Assert: Elements should be sorted by top position (ascending)
38
+ expect(result[0]).toBe(el3); // top: 10
39
+ expect(result[1]).toBe(el2); // top: 50
40
+ expect(result[2]).toBe(el1); // top: 100
41
+ });
42
+
43
+ it('should sort elements by left position when top positions are equal', () => {
44
+ // Arrange: Create elements with same top but different left positions
45
+ const el1 = document.createElement('button');
46
+ Object.defineProperty(el1, 'getBoundingClientRect', {
23
47
  value: () => ({ top: 10, left: 100 })
24
48
  });
25
49
 
50
+ const el2 = document.createElement('button');
51
+ Object.defineProperty(el2, 'getBoundingClientRect', {
52
+ value: () => ({ top: 10, left: 50 })
53
+ });
54
+
26
55
  const el3 = document.createElement('button');
27
56
  Object.defineProperty(el3, 'getBoundingClientRect', {
28
- value: () => ({ top: 100, left: 10 })
57
+ value: () => ({ top: 10, left: 10 })
29
58
  });
30
59
 
31
60
  // Act: Sort the elements
32
61
  const result = [el1, el2, el3].sort(sortByVisualOrder);
33
62
 
34
- // Assert: The elements should be sorted by visual order
35
- expect(result[0]).toBe(el1); // Top left
36
- expect(result[1]).toBe(el2); // Top right
37
- expect(result[2]).toBe(el3); // Bottom
63
+ // Assert: Elements should be sorted by left position (ascending)
64
+ expect(result[0]).toBe(el3); // left: 10
65
+ expect(result[1]).toBe(el2); // left: 50
66
+ expect(result[2]).toBe(el1); // left: 100
67
+ });
68
+
69
+ it('should handle mixed top and left positioning correctly', () => {
70
+ // Arrange: Create a realistic grid layout
71
+ const el1 = document.createElement('button');
72
+ Object.defineProperty(el1, 'getBoundingClientRect', {
73
+ value: () => ({ top: 10, left: 100 }) // top row, right
74
+ });
75
+
76
+ const el2 = document.createElement('button');
77
+ Object.defineProperty(el2, 'getBoundingClientRect', {
78
+ value: () => ({ top: 10, left: 10 }) // top row, left
79
+ });
80
+
81
+ const el3 = document.createElement('button');
82
+ Object.defineProperty(el3, 'getBoundingClientRect', {
83
+ value: () => ({ top: 50, left: 10 }) // bottom row, left
84
+ });
85
+
86
+ const el4 = document.createElement('button');
87
+ Object.defineProperty(el4, 'getBoundingClientRect', {
88
+ value: () => ({ top: 50, left: 100 }) // bottom row, right
89
+ });
90
+
91
+ // Act: Sort the elements
92
+ const result = [el1, el2, el3, el4].sort(sortByVisualOrder);
93
+
94
+ // Assert: Should be sorted by visual reading order (top-left to bottom-right)
95
+ expect(result[0]).toBe(el2); // top row, left
96
+ expect(result[1]).toBe(el1); // top row, right
97
+ expect(result[2]).toBe(el3); // bottom row, left
98
+ expect(result[3]).toBe(el4); // bottom row, right
99
+ });
100
+
101
+ it('should handle elements with negative positions', () => {
102
+ // Arrange: Create elements with negative positions
103
+ const el1 = document.createElement('button');
104
+ Object.defineProperty(el1, 'getBoundingClientRect', {
105
+ value: () => ({ top: -10, left: 10 })
106
+ });
107
+
108
+ const el2 = document.createElement('button');
109
+ Object.defineProperty(el2, 'getBoundingClientRect', {
110
+ value: () => ({ top: 10, left: -10 })
111
+ });
112
+
113
+ const el3 = document.createElement('button');
114
+ Object.defineProperty(el3, 'getBoundingClientRect', {
115
+ value: () => ({ top: 0, left: 0 })
116
+ });
117
+
118
+ // Act: Sort the elements
119
+ const result = [el1, el2, el3].sort(sortByVisualOrder);
120
+
121
+ // Assert: Should handle negative positions correctly
122
+ expect(result[0]).toBe(el1); // top: -10
123
+ expect(result[1]).toBe(el3); // top: 0
124
+ expect(result[2]).toBe(el2); // top: 10
125
+ });
126
+ });
127
+
128
+ describe('testOrder', () => {
129
+ beforeEach(() => {
130
+ document.body.innerHTML = '';
131
+ document.head.innerHTML = '';
132
+ vi.clearAllMocks();
133
+ });
134
+
135
+ it('should return true when focus order matches visual order', () => {
136
+ // Arrange: Create elements in proper visual order
137
+ const container = document.createElement('div');
138
+ const button1 = document.createElement('button');
139
+ const button2 = document.createElement('button');
140
+ const button3 = document.createElement('button');
141
+
142
+ // Mock getBoundingClientRect for visual ordering
143
+ Object.defineProperty(button1, 'getBoundingClientRect', {
144
+ value: () => ({ top: 10, left: 10 })
145
+ });
146
+ Object.defineProperty(button2, 'getBoundingClientRect', {
147
+ value: () => ({ top: 10, left: 100 })
148
+ });
149
+ Object.defineProperty(button3, 'getBoundingClientRect', {
150
+ value: () => ({ top: 100, left: 10 })
151
+ });
152
+
153
+ // Mock getFocusableElements to return elements in DOM order (which matches visual order)
154
+ mockedGetFocusableElements.mockReturnValue([button1, button2, button3]);
155
+
156
+ // Act
157
+ const result = testOrder(container);
158
+
159
+ // Assert
160
+ expect(result).toBe(true);
161
+ // Note: We can't easily test the mock call due to CommonJS/ES6 mocking complexity
162
+ // Instead we verify the function behavior through the result
163
+ });
164
+
165
+
166
+ it('should handle elements with positive tabindex correctly', () => {
167
+ // Arrange: Create elements with different tabindex values
168
+ const container = document.createElement('div');
169
+ const button1 = document.createElement('button');
170
+ const button2 = document.createElement('button');
171
+ const button3 = document.createElement('button');
172
+
173
+ button1.setAttribute('tabindex', '3');
174
+ button2.setAttribute('tabindex', '1');
175
+ button3.setAttribute('tabindex', '2');
176
+
177
+ // Mock getBoundingClientRect to match expected tabindex order
178
+ Object.defineProperty(button2, 'getBoundingClientRect', {
179
+ value: () => ({ top: 10, left: 10 }) // tabindex 1, should be first
180
+ });
181
+ Object.defineProperty(button3, 'getBoundingClientRect', {
182
+ value: () => ({ top: 10, left: 100 }) // tabindex 2, should be second
183
+ });
184
+ Object.defineProperty(button1, 'getBoundingClientRect', {
185
+ value: () => ({ top: 100, left: 10 }) // tabindex 3, should be third
186
+ });
187
+
188
+ // Mock getFocusableElements to return elements in DOM order
189
+ mockedGetFocusableElements.mockReturnValue([button1, button2, button3]);
190
+
191
+ // Act
192
+ const result = testOrder(container);
193
+
194
+ // Assert
195
+ expect(result).toBe(true);
196
+ });
197
+
198
+ it('should handle elements with mixed tabindex values (positive and default)', () => {
199
+ // Arrange: Create elements with mixed tabindex
200
+ const container = document.createElement('div');
201
+ const button1 = document.createElement('button'); // no tabindex (default 0)
202
+ const button2 = document.createElement('button');
203
+ const button3 = document.createElement('button'); // no tabindex (default 0)
204
+
205
+ button2.setAttribute('tabindex', '1'); // positive tabindex should come first
206
+
207
+ // Mock getBoundingClientRect - positive tabindex should come first in visual order
208
+ Object.defineProperty(button2, 'getBoundingClientRect', {
209
+ value: () => ({ top: 10, left: 10 }) // tabindex 1, should be first
210
+ });
211
+ Object.defineProperty(button1, 'getBoundingClientRect', {
212
+ value: () => ({ top: 10, left: 100 }) // default tabindex, should be second
213
+ });
214
+ Object.defineProperty(button3, 'getBoundingClientRect', {
215
+ value: () => ({ top: 100, left: 10 }) // default tabindex, should be third
216
+ });
217
+
218
+ // Mock getFocusableElements to return elements in DOM order
219
+ mockedGetFocusableElements.mockReturnValue([button1, button2, button3]);
220
+
221
+ // Act
222
+ const result = testOrder(container);
223
+
224
+ // Assert
225
+ expect(result).toBe(true);
226
+ });
227
+
228
+ it('should handle elements with zero tabindex correctly', () => {
229
+ // Arrange: Create elements with explicit tabindex="0"
230
+ const container = document.createElement('div');
231
+ const button1 = document.createElement('button');
232
+ const button2 = document.createElement('button');
233
+
234
+ button1.setAttribute('tabindex', '0');
235
+ button2.setAttribute('tabindex', '0');
236
+
237
+ // Mock getBoundingClientRect for visual ordering
238
+ Object.defineProperty(button1, 'getBoundingClientRect', {
239
+ value: () => ({ top: 10, left: 10 })
240
+ });
241
+ Object.defineProperty(button2, 'getBoundingClientRect', {
242
+ value: () => ({ top: 10, left: 100 })
243
+ });
244
+
245
+ // Mock getFocusableElements to return elements in DOM order
246
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
247
+
248
+ // Act
249
+ const result = testOrder(container);
250
+
251
+ // Assert
252
+ expect(result).toBe(true);
253
+ });
254
+
255
+ it('should prioritize positive tabindex over default tabindex', () => {
256
+ // Arrange: Create mixed scenario where positive tabindex should come first
257
+ const container = document.createElement('div');
258
+ const button1 = document.createElement('button'); // default tabindex
259
+ const button2 = document.createElement('button');
260
+
261
+ button2.setAttribute('tabindex', '1'); // positive tabindex
262
+
263
+ // Mock getBoundingClientRect where element with positive tabindex is visually first
264
+ // AND focus order matches visual order (positive tabindex first, then default)
265
+ Object.defineProperty(button2, 'getBoundingClientRect', {
266
+ value: () => ({ top: 10, left: 10 }) // visually first AND focus first (tabindex=1)
267
+ });
268
+ Object.defineProperty(button1, 'getBoundingClientRect', {
269
+ value: () => ({ top: 10, left: 100 }) // visually second AND focus second (default)
270
+ });
271
+
272
+ // Mock getFocusableElements
273
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
274
+
275
+ // Act
276
+ const result = testOrder(container);
277
+
278
+ // Assert: Should return true because visual order matches focus order
279
+ // Focus order: [button2 (tabindex=1), button1 (default)]
280
+ // Visual order: [button2, button1] (same)
281
+ expect(result).toBe(true);
282
+ });
283
+
284
+ it('should handle CSS style removal and restoration', () => {
285
+ // Arrange: Setup document with CSS styles
286
+ const container = document.createElement('div');
287
+ const button1 = document.createElement('button');
288
+ const button2 = document.createElement('button');
289
+
290
+ // Add CSS styles to document
291
+ const styleElement = document.createElement('style');
292
+ styleElement.textContent = 'button { position: absolute; }';
293
+ document.head.appendChild(styleElement);
294
+
295
+ const linkElement = document.createElement('link');
296
+ linkElement.rel = 'stylesheet';
297
+ linkElement.href = 'data:text/css,button%20%7B%20position%3A%20absolute%3B%20%7D';
298
+ document.head.appendChild(linkElement);
299
+
300
+ // Add inline styles
301
+ button1.setAttribute('style', 'top: 20px; left: 20px;');
302
+ button2.setAttribute('style', 'top: 10px; left: 10px;');
303
+
304
+ // Mock getBoundingClientRect to be consistent regardless of styles
305
+ Object.defineProperty(button1, 'getBoundingClientRect', {
306
+ value: () => ({ top: 10, left: 10 })
307
+ });
308
+ Object.defineProperty(button2, 'getBoundingClientRect', {
309
+ value: () => ({ top: 10, left: 100 })
310
+ });
311
+
312
+ // Mock getFocusableElements
313
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
314
+
315
+ // Act
316
+ const result = testOrder(container);
317
+
318
+ // Assert: CSS should be restored after test
319
+ expect(document.head.querySelector('style')).toBeTruthy();
320
+ expect(document.head.querySelector('link[rel="stylesheet"]')).toBeTruthy();
321
+ expect(button1.getAttribute('style')).toBe('top: 20px; left: 20px;');
322
+ expect(button2.getAttribute('style')).toBe('top: 10px; left: 10px;');
323
+ expect(result).toBe(true);
324
+ });
325
+
326
+ it('should handle elements without inline styles during CSS restoration', () => {
327
+ // Arrange: Create elements without inline styles
328
+ const container = document.createElement('div');
329
+ const button1 = document.createElement('button');
330
+ const button2 = document.createElement('button');
331
+
332
+ // Ensure no inline styles initially
333
+ expect(button1.getAttribute('style')).toBeNull();
334
+ expect(button2.getAttribute('style')).toBeNull();
335
+
336
+ // Mock getBoundingClientRect
337
+ Object.defineProperty(button1, 'getBoundingClientRect', {
338
+ value: () => ({ top: 10, left: 10 })
339
+ });
340
+ Object.defineProperty(button2, 'getBoundingClientRect', {
341
+ value: () => ({ top: 10, left: 100 })
342
+ });
343
+
344
+ // Mock getFocusableElements
345
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
346
+
347
+ // Act
348
+ const result = testOrder(container);
349
+
350
+ // Assert: Elements should not have style attribute after restoration
351
+ expect(button1.hasAttribute('style')).toBe(false);
352
+ expect(button2.hasAttribute('style')).toBe(false);
353
+ expect(result).toBe(true);
354
+ });
355
+
356
+ it('should return true when no focusable elements exist', () => {
357
+ // Arrange: Empty container
358
+ const container = document.createElement('div');
359
+
360
+ // Mock getFocusableElements to return empty array
361
+ mockedGetFocusableElements.mockReturnValue([]);
362
+
363
+ // Act
364
+ const result = testOrder(container);
365
+
366
+ // Assert
367
+ expect(result).toBe(true);
368
+ });
369
+
370
+ it('should return true for single focusable element', () => {
371
+ // Arrange: Container with single element
372
+ const container = document.createElement('div');
373
+ const button = document.createElement('button');
374
+
375
+ Object.defineProperty(button, 'getBoundingClientRect', {
376
+ value: () => ({ top: 10, left: 10 })
377
+ });
378
+
379
+ // Mock getFocusableElements
380
+ mockedGetFocusableElements.mockReturnValue([button]);
381
+
382
+ // Act
383
+ const result = testOrder(container);
384
+
385
+ // Assert
386
+ expect(result).toBe(true);
387
+ });
388
+
389
+ it('should handle complex tabindex ordering with multiple positive values', () => {
390
+ // Arrange: Create elements with various tabindex values
391
+ const container = document.createElement('div');
392
+ const button1 = document.createElement('button');
393
+ const button2 = document.createElement('button');
394
+ const button3 = document.createElement('button');
395
+ const button4 = document.createElement('button');
396
+
397
+ button1.setAttribute('tabindex', '5');
398
+ button2.setAttribute('tabindex', '2');
399
+ button3.setAttribute('tabindex', '10');
400
+ // button4 has no tabindex (default 0)
401
+
402
+ // Mock getBoundingClientRect to match expected tabindex order
403
+ Object.defineProperty(button2, 'getBoundingClientRect', {
404
+ value: () => ({ top: 10, left: 10 }) // tabindex 2, should be first
405
+ });
406
+ Object.defineProperty(button1, 'getBoundingClientRect', {
407
+ value: () => ({ top: 10, left: 100 }) // tabindex 5, should be second
408
+ });
409
+ Object.defineProperty(button3, 'getBoundingClientRect', {
410
+ value: () => ({ top: 20, left: 10 }) // tabindex 10, should be third
411
+ });
412
+ Object.defineProperty(button4, 'getBoundingClientRect', {
413
+ value: () => ({ top: 20, left: 100 }) // default, should be last
414
+ });
415
+
416
+ // Mock getFocusableElements
417
+ mockedGetFocusableElements.mockReturnValue([button1, button2, button3, button4]);
418
+
419
+ // Act
420
+ const result = testOrder(container);
421
+
422
+ // Assert
423
+ expect(result).toBe(true);
424
+ });
425
+
426
+ it('should handle elements with empty tabindex attributes', () => {
427
+ // Arrange: Create elements with empty tabindex
428
+ const container = document.createElement('div');
429
+ const button1 = document.createElement('button');
430
+ const button2 = document.createElement('button');
431
+
432
+ button1.setAttribute('tabindex', ''); // empty tabindex
433
+ button2.setAttribute('tabindex', '1'); // valid positive
434
+
435
+ // Mock getBoundingClientRect - positive tabindex should come first visually
436
+ Object.defineProperty(button2, 'getBoundingClientRect', {
437
+ value: () => ({ top: 10, left: 10 }) // tabindex 1, should be first
438
+ });
439
+ Object.defineProperty(button1, 'getBoundingClientRect', {
440
+ value: () => ({ top: 10, left: 100 }) // empty tabindex, should be last
441
+ });
442
+
443
+ // Mock getFocusableElements
444
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
445
+
446
+ // Act
447
+ const result = testOrder(container);
448
+
449
+ // Assert
450
+ expect(result).toBe(true);
451
+ });
452
+
453
+ it('should handle elements with negative tabindex', () => {
454
+ // Arrange: Create elements with negative tabindex (should be treated as 0)
455
+ const container = document.createElement('div');
456
+ const button1 = document.createElement('button');
457
+ const button2 = document.createElement('button');
458
+
459
+ button1.setAttribute('tabindex', '-1'); // negative tabindex
460
+ button2.setAttribute('tabindex', '1'); // positive tabindex
461
+
462
+ // Mock getBoundingClientRect - positive tabindex should come first visually
463
+ Object.defineProperty(button2, 'getBoundingClientRect', {
464
+ value: () => ({ top: 10, left: 10 }) // tabindex 1, should be first
465
+ });
466
+ Object.defineProperty(button1, 'getBoundingClientRect', {
467
+ value: () => ({ top: 10, left: 100 }) // tabindex -1, should be last
468
+ });
469
+
470
+ // Mock getFocusableElements
471
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
472
+
473
+ // Act
474
+ const result = testOrder(container);
475
+
476
+ // Assert
477
+ expect(result).toBe(true);
478
+ });
479
+
480
+ it('should handle elements with non-numeric tabindex values', () => {
481
+ // Arrange: Create elements with invalid tabindex values
482
+ const container = document.createElement('div');
483
+ const button1 = document.createElement('button');
484
+ const button2 = document.createElement('button');
485
+
486
+ button1.setAttribute('tabindex', 'invalid'); // non-numeric
487
+ button2.setAttribute('tabindex', '1'); // valid positive
488
+
489
+ // Mock getBoundingClientRect
490
+ Object.defineProperty(button2, 'getBoundingClientRect', {
491
+ value: () => ({ top: 10, left: 10 }) // valid tabindex first
492
+ });
493
+ Object.defineProperty(button1, 'getBoundingClientRect', {
494
+ value: () => ({ top: 10, left: 100 }) // invalid tabindex treated as 0
495
+ });
496
+
497
+ // Mock getFocusableElements
498
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
499
+
500
+ // Act
501
+ const result = testOrder(container);
502
+
503
+ // Assert
504
+ expect(result).toBe(true);
505
+ });
506
+
507
+ it('should handle elements where some have null style attributes', () => {
508
+ // Arrange: Create elements with mixed style situations
509
+ const container = document.createElement('div');
510
+ const button1 = document.createElement('button');
511
+ const button2 = document.createElement('button');
512
+
513
+ // One element has inline style, other doesn't
514
+ button1.setAttribute('style', 'color: red;');
515
+ // button2 has no style attribute
516
+
517
+ // Mock getBoundingClientRect
518
+ Object.defineProperty(button1, 'getBoundingClientRect', {
519
+ value: () => ({ top: 10, left: 10 })
520
+ });
521
+ Object.defineProperty(button2, 'getBoundingClientRect', {
522
+ value: () => ({ top: 10, left: 100 })
523
+ });
524
+
525
+ // Mock getFocusableElements
526
+ mockedGetFocusableElements.mockReturnValue([button1, button2]);
527
+
528
+ // Act
529
+ const result = testOrder(container);
530
+
531
+ // Assert: Should handle mixed style attributes correctly
532
+ expect(result).toBe(true);
533
+ // Verify original styles are preserved
534
+ expect(button1.getAttribute('style')).toBe('color: red;');
535
+ expect(button2.getAttribute('style')).toBeNull();
536
+ });
537
+
538
+ });
539
+
540
+ describe('testOrder - Integration Tests', () => {
541
+ beforeEach(() => {
542
+ // Clear all mocks to use real getFocusableElements
543
+ vi.clearAllMocks();
544
+ vi.resetModules();
545
+ document.body.innerHTML = '';
546
+ document.head.innerHTML = '';
547
+ });
548
+
549
+ it('should test with real getFocusableElements - visual order matches focus order', async () => {
550
+ // Restore real implementation
551
+ vi.unmock('../src/getFocusableElements.js');
552
+ const { testOrder } = await import('../src/testOrder.js');
553
+
554
+ // Create focusable elements in proper order
555
+ const container = document.createElement('div');
556
+ document.body.appendChild(container);
557
+
558
+ const button1 = document.createElement('button');
559
+ button1.textContent = 'Button 1';
560
+ container.appendChild(button1);
561
+
562
+ const button2 = document.createElement('button');
563
+ button2.textContent = 'Button 2';
564
+ container.appendChild(button2);
565
+
566
+ const button3 = document.createElement('button');
567
+ button3.textContent = 'Button 3';
568
+ container.appendChild(button3);
569
+
570
+ // Mock getBoundingClientRect to simulate proper visual order
571
+ Object.defineProperty(button1, 'getBoundingClientRect', {
572
+ value: () => ({ top: 10, left: 10 })
573
+ });
574
+ Object.defineProperty(button2, 'getBoundingClientRect', {
575
+ value: () => ({ top: 10, left: 100 })
576
+ });
577
+ Object.defineProperty(button3, 'getBoundingClientRect', {
578
+ value: () => ({ top: 100, left: 10 })
579
+ });
580
+
581
+ // Act
582
+ const result = testOrder(container);
583
+
584
+ // Assert
585
+ expect(result).toBe(true);
38
586
  });
39
587
 
40
- // Skip tests that require mocking due to CommonJS/ES6 module incompatibility in test environment
41
- it.skip('should return true when focus order matches visual order', () => {
42
- // Test implementation requires mocking getFocusableElements which has issues with CommonJS modules
588
+ it('should return false when focus order does not match visual order', async () => {
589
+ // Restore real implementation
590
+ vi.unmock('../src/getFocusableElements.js');
591
+ const { testOrder } = await import('../src/testOrder.js');
592
+
593
+ const container = document.createElement('div');
594
+ document.body.appendChild(container);
595
+
596
+ const button1 = document.createElement('button');
597
+ button1.textContent = 'Button 1';
598
+ container.appendChild(button1);
599
+
600
+ const button2 = document.createElement('button');
601
+ button2.textContent = 'Button 2';
602
+ container.appendChild(button2);
603
+
604
+ const button3 = document.createElement('button');
605
+ button3.textContent = 'Button 3';
606
+ container.appendChild(button3);
607
+
608
+ // Mock getBoundingClientRect to simulate mismatched visual order
609
+ // Visual order will be button3, button1, button2 (not matching DOM order)
610
+ Object.defineProperty(button1, 'getBoundingClientRect', {
611
+ value: () => ({ top: 50, left: 10 })
612
+ });
613
+ Object.defineProperty(button2, 'getBoundingClientRect', {
614
+ value: () => ({ top: 100, left: 10 })
615
+ });
616
+ Object.defineProperty(button3, 'getBoundingClientRect', {
617
+ value: () => ({ top: 10, left: 10 })
618
+ });
619
+
620
+ // Act
621
+ const result = testOrder(container);
622
+
623
+ // Assert
624
+ expect(result).toBe(false);
43
625
  });
44
626
 
45
- it.skip('should return false when focus order does not match visual order', () => {
46
- // Test implementation requires mocking getFocusableElements which has issues with CommonJS modules
627
+ it('should handle positive tabindex with real getFocusableElements', async () => {
628
+ // Restore real implementation
629
+ vi.unmock('../src/getFocusableElements.js');
630
+ const { testOrder } = await import('../src/testOrder.js');
631
+
632
+ const container = document.createElement('div');
633
+ document.body.appendChild(container);
634
+
635
+ const button1 = document.createElement('button');
636
+ button1.textContent = 'Button 1';
637
+ button1.setAttribute('tabindex', '2');
638
+ container.appendChild(button1);
639
+
640
+ const button2 = document.createElement('button');
641
+ button2.textContent = 'Button 2';
642
+ button2.setAttribute('tabindex', '1');
643
+ container.appendChild(button2);
644
+
645
+ const button3 = document.createElement('button');
646
+ button3.textContent = 'Button 3';
647
+ // no tabindex (default)
648
+ container.appendChild(button3);
649
+
650
+ // Mock getBoundingClientRect to match expected focus order
651
+ // Focus order: button2 (tabindex 1), button1 (tabindex 2), button3 (default)
652
+ Object.defineProperty(button2, 'getBoundingClientRect', {
653
+ value: () => ({ top: 10, left: 10 })
654
+ });
655
+ Object.defineProperty(button1, 'getBoundingClientRect', {
656
+ value: () => ({ top: 10, left: 100 })
657
+ });
658
+ Object.defineProperty(button3, 'getBoundingClientRect', {
659
+ value: () => ({ top: 100, left: 10 })
660
+ });
661
+
662
+ // Act
663
+ const result = testOrder(container);
664
+
665
+ // Assert
666
+ expect(result).toBe(true);
47
667
  });
48
668
 
49
- it.skip('should handle elements with tabindex correctly', () => {
50
- // Test implementation requires mocking getFocusableElements which has issues with CommonJS modules
669
+ it('should handle CSS removal and restoration with real implementation', async () => {
670
+ // Restore real implementation
671
+ vi.unmock('../src/getFocusableElements.js');
672
+ const { testOrder } = await import('../src/testOrder.js');
673
+
674
+ const container = document.createElement('div');
675
+ document.body.appendChild(container);
676
+
677
+ // Add CSS styles
678
+ const styleElement = document.createElement('style');
679
+ styleElement.textContent = 'button { position: relative; }';
680
+ document.head.appendChild(styleElement);
681
+
682
+ const button1 = document.createElement('button');
683
+ button1.textContent = 'Button 1';
684
+ button1.setAttribute('style', 'top: 0px;');
685
+ container.appendChild(button1);
686
+
687
+ const button2 = document.createElement('button');
688
+ button2.textContent = 'Button 2';
689
+ container.appendChild(button2);
690
+
691
+ // Mock getBoundingClientRect
692
+ Object.defineProperty(button1, 'getBoundingClientRect', {
693
+ value: () => ({ top: 10, left: 10 })
694
+ });
695
+ Object.defineProperty(button2, 'getBoundingClientRect', {
696
+ value: () => ({ top: 10, left: 100 })
697
+ });
698
+
699
+ // Act
700
+ const result = testOrder(container);
701
+
702
+ // Assert: Styles should be preserved
703
+ expect(document.head.querySelector('style')).toBeTruthy();
704
+ expect(button1.getAttribute('style')).toBe('top: 0px;');
705
+ expect(result).toBe(true);
706
+ });
707
+
708
+ it('should return false when visual order after CSS removal differs', async () => {
709
+ // Restore real implementation
710
+ vi.unmock('../src/getFocusableElements.js');
711
+ const { testOrder } = await import('../src/testOrder.js');
712
+
713
+ const container = document.createElement('div');
714
+ document.body.appendChild(container);
715
+
716
+ const button1 = document.createElement('button');
717
+ button1.textContent = 'Button 1';
718
+ container.appendChild(button1);
719
+
720
+ const button2 = document.createElement('button');
721
+ button2.textContent = 'Button 2';
722
+ container.appendChild(button2);
723
+
724
+ // Create a mock where visual order changes after CSS removal
725
+ let callCount = 0;
726
+ Object.defineProperty(button1, 'getBoundingClientRect', {
727
+ value: () => {
728
+ callCount++;
729
+ // First call: normal order
730
+ // Second call: after CSS removal, different order
731
+ // Third call: after restoration, back to normal
732
+ if (callCount === 2 || callCount === 4) {
733
+ return { top: 100, left: 10 }; // different position without CSS
734
+ }
735
+ return { top: 10, left: 10 };
736
+ }
737
+ });
738
+
739
+ Object.defineProperty(button2, 'getBoundingClientRect', {
740
+ value: () => ({ top: 10, left: 100 })
741
+ });
742
+
743
+ // Act
744
+ const result = testOrder(container);
745
+
746
+ // Assert
747
+ expect(result).toBe(false);
748
+ });
749
+
750
+ it('should handle input elements with real getFocusableElements', async () => {
751
+ // Restore real implementation
752
+ vi.unmock('../src/getFocusableElements.js');
753
+ const { testOrder } = await import('../src/testOrder.js');
754
+
755
+ const container = document.createElement('div');
756
+ document.body.appendChild(container);
757
+
758
+ const input1 = document.createElement('input');
759
+ input1.type = 'text';
760
+ container.appendChild(input1);
761
+
762
+ const input2 = document.createElement('input');
763
+ input2.type = 'checkbox';
764
+ container.appendChild(input2);
765
+
766
+ const textarea = document.createElement('textarea');
767
+ container.appendChild(textarea);
768
+
769
+ // Mock getBoundingClientRect
770
+ Object.defineProperty(input1, 'getBoundingClientRect', {
771
+ value: () => ({ top: 10, left: 10 })
772
+ });
773
+ Object.defineProperty(input2, 'getBoundingClientRect', {
774
+ value: () => ({ top: 10, left: 100 })
775
+ });
776
+ Object.defineProperty(textarea, 'getBoundingClientRect', {
777
+ value: () => ({ top: 50, left: 10 })
778
+ });
779
+
780
+ // Act
781
+ const result = testOrder(container);
782
+
783
+ // Assert
784
+ expect(result).toBe(true);
51
785
  });
786
+
52
787
  });