@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,21 +1,162 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
- // Import the function or module you want to test
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 do something expected', () => {
12
- // Arrange: Set up your test
13
-
14
- // Act: Call the function or method being tested
15
-
16
- // Assert: Check the result matches what you expect
17
- expect(true).toBe(true);
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
- // Add more test cases here
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
+ });