@afixt/test-utils 1.0.0
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/.editorconfig +13 -0
- package/.eslintrc +78 -0
- package/.gitattributes +5 -0
- package/.nvmrc +1 -0
- package/CLAUDE.md +33 -0
- package/README.md +72 -0
- package/docs/arrayUtils.js.html +69 -0
- package/docs/data/search.json +1 -0
- package/docs/domUtils.js.html +182 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/getAccessibleName.js.html +456 -0
- package/docs/getAccessibleText.js.html +65 -0
- package/docs/getAriaAttributesByElement.js.html +22 -0
- package/docs/getCSSGeneratedContent.js.html +62 -0
- package/docs/getComputedRole.js.html +172 -0
- package/docs/getFocusableElements.js.html +29 -0
- package/docs/getGeneratedContent.js.html +18 -0
- package/docs/getImageText.js.html +28 -0
- package/docs/getStyleObject.js.html +48 -0
- package/docs/global.html +3 -0
- package/docs/hasAccessibleName.js.html +30 -0
- package/docs/hasAttribute.js.html +18 -0
- package/docs/hasCSSGeneratedContent.js.html +23 -0
- package/docs/hasHiddenParent.js.html +32 -0
- package/docs/hasParent.js.html +57 -0
- package/docs/hasValidAriaAttributes.js.html +33 -0
- package/docs/hasValidAriaRole.js.html +32 -0
- package/docs/index.html +3 -0
- package/docs/index.js.html +66 -0
- package/docs/isAriaAttributesValid.js.html +76 -0
- package/docs/isComplexTable.js.html +112 -0
- package/docs/isDataTable.js.html +241 -0
- package/docs/isFocusable.js.html +37 -0
- package/docs/isHidden.js.html +20 -0
- package/docs/isOffScreen.js.html +19 -0
- package/docs/isValidUrl.js.html +16 -0
- package/docs/isVisible.js.html +65 -0
- package/docs/module-afixt-test-utils.html +3 -0
- package/docs/scripts/core.js +726 -0
- package/docs/scripts/core.min.js +23 -0
- package/docs/scripts/resize.js +90 -0
- package/docs/scripts/search.js +265 -0
- package/docs/scripts/search.min.js +6 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
- package/docs/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/scripts/third-party/hljs-original.js +5171 -0
- package/docs/scripts/third-party/hljs.js +1 -0
- package/docs/scripts/third-party/popper.js +5 -0
- package/docs/scripts/third-party/tippy.js +1 -0
- package/docs/scripts/third-party/tocbot.js +672 -0
- package/docs/scripts/third-party/tocbot.min.js +1 -0
- package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/styles/clean-jsdoc-theme.min.css +1 -0
- package/docs/testContrast.js.html +236 -0
- package/docs/testLang.js.html +578 -0
- package/docs/testOrder.js.html +93 -0
- package/jsdoc.json +67 -0
- package/package.json +32 -0
- package/src/arrayUtils.js +67 -0
- package/src/domUtils.js +179 -0
- package/src/getAccessibleName.js +454 -0
- package/src/getAccessibleText.js +63 -0
- package/src/getAriaAttributesByElement.js +19 -0
- package/src/getCSSGeneratedContent.js +60 -0
- package/src/getComputedRole.js +169 -0
- package/src/getFocusableElements.js +26 -0
- package/src/getGeneratedContent.js +15 -0
- package/src/getImageText.js +25 -0
- package/src/getStyleObject.js +45 -0
- package/src/hasAccessibleName.js +28 -0
- package/src/hasAttribute.js +15 -0
- package/src/hasCSSGeneratedContent.js +20 -0
- package/src/hasHiddenParent.js +29 -0
- package/src/hasParent.js +54 -0
- package/src/hasValidAriaAttributes.js +30 -0
- package/src/hasValidAriaRole.js +29 -0
- package/src/index.js +64 -0
- package/src/interactiveRoles.js +20 -0
- package/src/isAriaAttributesValid.js +74 -0
- package/src/isComplexTable.js +109 -0
- package/src/isDataTable.js +239 -0
- package/src/isFocusable.js +34 -0
- package/src/isHidden.js +17 -0
- package/src/isOffScreen.js +16 -0
- package/src/isValidUrl.js +13 -0
- package/src/isVisible.js +62 -0
- package/src/stringUtils.js +150 -0
- package/src/testContrast.js +233 -0
- package/src/testLang.js +575 -0
- package/src/testOrder.js +90 -0
- package/test/_template.test.js +21 -0
- package/test/arrayUtils.test.js +84 -0
- package/test/domUtils.test.js +147 -0
- package/test/generate-test-stubs.js +37 -0
- package/test/getAccessibleName.test.js +113 -0
- package/test/getAccessibleText.test.js +94 -0
- package/test/getAriaAttributesByElement.test.js +112 -0
- package/test/getCSSGeneratedContent.test.js +102 -0
- package/test/getComputedRole.test.js +180 -0
- package/test/getFocusableElements.test.js +134 -0
- package/test/getGeneratedContent.test.js +321 -0
- package/test/getImageText.test.js +21 -0
- package/test/getStyleObject.test.js +134 -0
- package/test/hasAccessibleName.test.js +59 -0
- package/test/hasAttribute.test.js +132 -0
- package/test/hasCSSGeneratedContent.test.js +143 -0
- package/test/hasHiddenParent.test.js +176 -0
- package/test/hasParent.test.js +266 -0
- package/test/hasValidAriaAttributes.test.js +79 -0
- package/test/hasValidAriaRole.test.js +98 -0
- package/test/isAriaAttributesValid.test.js +83 -0
- package/test/isComplexTable.test.js +363 -0
- package/test/isDataTable.test.js +948 -0
- package/test/isFocusable.test.js +182 -0
- package/test/isHidden.test.js +157 -0
- package/test/isOffScreen.test.js +249 -0
- package/test/isValidUrl.test.js +63 -0
- package/test/isVisible.test.js +104 -0
- package/test/setup.js +11 -0
- package/test/stringUtils.test.js +106 -0
- package/test/testContrast.test.js +77 -0
- package/test/testLang.test.js +21 -0
- package/test/testOrder.test.js +157 -0
- package/vitest.config.js +25 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { isFocusable } from '../src/isFocusable.js';
|
|
3
|
+
|
|
4
|
+
describe('isFocusable', () => {
|
|
5
|
+
// Setup common mocking for all tests
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
document.body.innerHTML = '';
|
|
8
|
+
|
|
9
|
+
// Mock the closest method for all tests since JSDOM doesn't support :hidden
|
|
10
|
+
const originalClosest = Element.prototype.closest;
|
|
11
|
+
Element.prototype.closest = vi.fn().mockImplementation(function(selector) {
|
|
12
|
+
if (selector === ':hidden') return null; // By default, elements are not hidden
|
|
13
|
+
return originalClosest.call(this, selector);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Clean up after each test
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vi.restoreAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return false for null or undefined elements', () => {
|
|
23
|
+
// Assert
|
|
24
|
+
expect(isFocusable(null)).toBe(false);
|
|
25
|
+
expect(isFocusable(undefined)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return true for focusable input elements', () => {
|
|
29
|
+
// Arrange
|
|
30
|
+
const input = document.createElement('input');
|
|
31
|
+
input.type = 'text';
|
|
32
|
+
document.body.appendChild(input);
|
|
33
|
+
|
|
34
|
+
// Act & Assert
|
|
35
|
+
expect(isFocusable(input)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return false for disabled input elements', () => {
|
|
39
|
+
// Arrange
|
|
40
|
+
const input = document.createElement('input');
|
|
41
|
+
input.type = 'text';
|
|
42
|
+
input.disabled = true;
|
|
43
|
+
document.body.appendChild(input);
|
|
44
|
+
|
|
45
|
+
// Act & Assert
|
|
46
|
+
expect(isFocusable(input)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return true for focusable button elements', () => {
|
|
50
|
+
// Arrange
|
|
51
|
+
const button = document.createElement('button');
|
|
52
|
+
document.body.appendChild(button);
|
|
53
|
+
|
|
54
|
+
// Act & Assert
|
|
55
|
+
expect(isFocusable(button)).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return false for disabled button elements', () => {
|
|
59
|
+
// Arrange
|
|
60
|
+
const button = document.createElement('button');
|
|
61
|
+
button.disabled = true;
|
|
62
|
+
document.body.appendChild(button);
|
|
63
|
+
|
|
64
|
+
// Act & Assert
|
|
65
|
+
expect(isFocusable(button)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return true for select elements', () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
const select = document.createElement('select');
|
|
71
|
+
document.body.appendChild(select);
|
|
72
|
+
|
|
73
|
+
// Act & Assert
|
|
74
|
+
expect(isFocusable(select)).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return true for textarea elements', () => {
|
|
78
|
+
// Arrange
|
|
79
|
+
const textarea = document.createElement('textarea');
|
|
80
|
+
document.body.appendChild(textarea);
|
|
81
|
+
|
|
82
|
+
// Act & Assert
|
|
83
|
+
expect(isFocusable(textarea)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should return true for anchors with href attribute', () => {
|
|
87
|
+
// Arrange
|
|
88
|
+
const anchor = document.createElement('a');
|
|
89
|
+
anchor.setAttribute('href', '#');
|
|
90
|
+
document.body.appendChild(anchor);
|
|
91
|
+
|
|
92
|
+
// Act & Assert
|
|
93
|
+
expect(isFocusable(anchor)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return false for anchors without href attribute', () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const anchor = document.createElement('a');
|
|
99
|
+
document.body.appendChild(anchor);
|
|
100
|
+
|
|
101
|
+
// Act & Assert
|
|
102
|
+
expect(isFocusable(anchor)).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return true for area elements with href attribute', () => {
|
|
106
|
+
// Arrange
|
|
107
|
+
const map = document.createElement('map');
|
|
108
|
+
const area = document.createElement('area');
|
|
109
|
+
area.setAttribute('href', '#');
|
|
110
|
+
map.appendChild(area);
|
|
111
|
+
document.body.appendChild(map);
|
|
112
|
+
|
|
113
|
+
// Act & Assert
|
|
114
|
+
expect(isFocusable(area)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should return false for area elements without href attribute', () => {
|
|
118
|
+
// Arrange
|
|
119
|
+
const map = document.createElement('map');
|
|
120
|
+
const area = document.createElement('area');
|
|
121
|
+
map.appendChild(area);
|
|
122
|
+
document.body.appendChild(map);
|
|
123
|
+
|
|
124
|
+
// Act & Assert
|
|
125
|
+
expect(isFocusable(area)).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should return false for non-form elements with tabindex >= 0', () => {
|
|
129
|
+
// Arrange
|
|
130
|
+
const div = document.createElement('div');
|
|
131
|
+
div.setAttribute('tabindex', '0');
|
|
132
|
+
document.body.appendChild(div);
|
|
133
|
+
|
|
134
|
+
// Act & Assert
|
|
135
|
+
// According to the implementation, non-form elements with tabindex
|
|
136
|
+
// are not considered focusable in this utility
|
|
137
|
+
expect(isFocusable(div)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return false for elements with tabindex < 0', () => {
|
|
141
|
+
// Arrange
|
|
142
|
+
const div = document.createElement('div');
|
|
143
|
+
div.setAttribute('tabindex', '-1');
|
|
144
|
+
document.body.appendChild(div);
|
|
145
|
+
|
|
146
|
+
// Act & Assert
|
|
147
|
+
expect(isFocusable(div)).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should return false for regular divs without tabindex', () => {
|
|
151
|
+
// Arrange
|
|
152
|
+
const div = document.createElement('div');
|
|
153
|
+
document.body.appendChild(div);
|
|
154
|
+
|
|
155
|
+
// Act & Assert
|
|
156
|
+
expect(isFocusable(div)).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return false for hidden elements', () => {
|
|
160
|
+
// Arrange
|
|
161
|
+
const input = document.createElement('input');
|
|
162
|
+
document.body.appendChild(input);
|
|
163
|
+
|
|
164
|
+
// Override the mock to simulate a hidden element
|
|
165
|
+
Element.prototype.closest = vi.fn().mockImplementation(function(selector) {
|
|
166
|
+
if (selector === ':hidden') return this; // Element is hidden
|
|
167
|
+
return null;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Act & Assert
|
|
171
|
+
expect(isFocusable(input)).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return true for object elements', () => {
|
|
175
|
+
// Arrange
|
|
176
|
+
const object = document.createElement('object');
|
|
177
|
+
document.body.appendChild(object);
|
|
178
|
+
|
|
179
|
+
// Act & Assert
|
|
180
|
+
expect(isFocusable(object)).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import isHidden from '../src/isHidden.js';
|
|
3
|
+
|
|
4
|
+
describe('isHidden', () => {
|
|
5
|
+
// Setup before each test
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
document.body.innerHTML = '';
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should return true for elements with display:none', () => {
|
|
11
|
+
// Arrange
|
|
12
|
+
const element = document.createElement('div');
|
|
13
|
+
element.style.display = 'none';
|
|
14
|
+
document.body.appendChild(element);
|
|
15
|
+
|
|
16
|
+
// Act
|
|
17
|
+
const result = isHidden(element);
|
|
18
|
+
|
|
19
|
+
// Assert
|
|
20
|
+
expect(result).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return true for elements with hidden attribute', () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
const element = document.createElement('div');
|
|
26
|
+
element.setAttribute('hidden', '');
|
|
27
|
+
document.body.appendChild(element);
|
|
28
|
+
|
|
29
|
+
// Act
|
|
30
|
+
const result = isHidden(element);
|
|
31
|
+
|
|
32
|
+
// Assert
|
|
33
|
+
expect(result).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return true for elements with both display:none and hidden attribute', () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
const element = document.createElement('div');
|
|
39
|
+
element.style.display = 'none';
|
|
40
|
+
element.setAttribute('hidden', '');
|
|
41
|
+
document.body.appendChild(element);
|
|
42
|
+
|
|
43
|
+
// Act
|
|
44
|
+
const result = isHidden(element);
|
|
45
|
+
|
|
46
|
+
// Assert
|
|
47
|
+
expect(result).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should return false for visible elements with no hidden attribute', () => {
|
|
51
|
+
// Arrange
|
|
52
|
+
const element = document.createElement('div');
|
|
53
|
+
element.textContent = 'Visible element';
|
|
54
|
+
document.body.appendChild(element);
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
const result = isHidden(element);
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(result).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return false for elements with display:block', () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
const element = document.createElement('div');
|
|
66
|
+
element.style.display = 'block';
|
|
67
|
+
document.body.appendChild(element);
|
|
68
|
+
|
|
69
|
+
// Act
|
|
70
|
+
const result = isHidden(element);
|
|
71
|
+
|
|
72
|
+
// Assert
|
|
73
|
+
expect(result).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return false for elements with display:inline', () => {
|
|
77
|
+
// Arrange
|
|
78
|
+
const element = document.createElement('span');
|
|
79
|
+
element.style.display = 'inline';
|
|
80
|
+
document.body.appendChild(element);
|
|
81
|
+
|
|
82
|
+
// Act
|
|
83
|
+
const result = isHidden(element);
|
|
84
|
+
|
|
85
|
+
// Assert
|
|
86
|
+
expect(result).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should return false for elements with display:flex', () => {
|
|
90
|
+
// Arrange
|
|
91
|
+
const element = document.createElement('div');
|
|
92
|
+
element.style.display = 'flex';
|
|
93
|
+
document.body.appendChild(element);
|
|
94
|
+
|
|
95
|
+
// Act
|
|
96
|
+
const result = isHidden(element);
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(result).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should handle elements with visibility:hidden but not display:none', () => {
|
|
103
|
+
// Arrange
|
|
104
|
+
const element = document.createElement('div');
|
|
105
|
+
element.style.visibility = 'hidden';
|
|
106
|
+
document.body.appendChild(element);
|
|
107
|
+
|
|
108
|
+
// Act
|
|
109
|
+
const result = isHidden(element);
|
|
110
|
+
|
|
111
|
+
// Assert
|
|
112
|
+
// Note: visibility:hidden does not qualify as "hidden" according to this function
|
|
113
|
+
expect(result).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should handle elements with opacity:0 but not display:none', () => {
|
|
117
|
+
// Arrange
|
|
118
|
+
const element = document.createElement('div');
|
|
119
|
+
element.style.opacity = '0';
|
|
120
|
+
document.body.appendChild(element);
|
|
121
|
+
|
|
122
|
+
// Act
|
|
123
|
+
const result = isHidden(element);
|
|
124
|
+
|
|
125
|
+
// Assert
|
|
126
|
+
// Note: opacity:0 does not qualify as "hidden" according to this function
|
|
127
|
+
expect(result).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should return false for elements with removed hidden attribute', () => {
|
|
131
|
+
// Arrange
|
|
132
|
+
const element = document.createElement('div');
|
|
133
|
+
element.setAttribute('hidden', '');
|
|
134
|
+
element.removeAttribute('hidden');
|
|
135
|
+
document.body.appendChild(element);
|
|
136
|
+
|
|
137
|
+
// Act
|
|
138
|
+
const result = isHidden(element);
|
|
139
|
+
|
|
140
|
+
// Assert
|
|
141
|
+
expect(result).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should return false for elements with display changed from none to block', () => {
|
|
145
|
+
// Arrange
|
|
146
|
+
const element = document.createElement('div');
|
|
147
|
+
element.style.display = 'none';
|
|
148
|
+
element.style.display = 'block';
|
|
149
|
+
document.body.appendChild(element);
|
|
150
|
+
|
|
151
|
+
// Act
|
|
152
|
+
const result = isHidden(element);
|
|
153
|
+
|
|
154
|
+
// Assert
|
|
155
|
+
expect(result).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { isOffScreen } from '../src/isOffScreen.js';
|
|
3
|
+
|
|
4
|
+
describe('isOffScreen', () => {
|
|
5
|
+
// Setup before each test
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
document.body.innerHTML = '';
|
|
8
|
+
|
|
9
|
+
// Mock window.innerWidth and window.innerHeight
|
|
10
|
+
vi.stubGlobal('innerWidth', 1024);
|
|
11
|
+
vi.stubGlobal('innerHeight', 768);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Cleanup after each test
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return false for null or undefined elements', () => {
|
|
20
|
+
// Act & Assert
|
|
21
|
+
expect(isOffScreen(null)).toBe(false);
|
|
22
|
+
expect(isOffScreen(undefined)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return false for elements within the viewport', () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const element = document.createElement('div');
|
|
28
|
+
document.body.appendChild(element);
|
|
29
|
+
|
|
30
|
+
// Mock the element's getBoundingClientRect to return a position within viewport
|
|
31
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
32
|
+
top: 100,
|
|
33
|
+
right: 500,
|
|
34
|
+
bottom: 200,
|
|
35
|
+
left: 200
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Act
|
|
39
|
+
const result = isOffScreen(element);
|
|
40
|
+
|
|
41
|
+
// Assert
|
|
42
|
+
expect(result).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should return true for elements positioned to the left of the viewport', () => {
|
|
46
|
+
// Arrange
|
|
47
|
+
const element = document.createElement('div');
|
|
48
|
+
document.body.appendChild(element);
|
|
49
|
+
|
|
50
|
+
// Mock the element's getBoundingClientRect to return a position to the left of viewport
|
|
51
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
52
|
+
top: 100,
|
|
53
|
+
right: -10, // Right edge is to the left of the viewport
|
|
54
|
+
bottom: 200,
|
|
55
|
+
left: -200
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
const result = isOffScreen(element);
|
|
60
|
+
|
|
61
|
+
// Assert
|
|
62
|
+
expect(result).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return true for elements positioned above the viewport', () => {
|
|
66
|
+
// Arrange
|
|
67
|
+
const element = document.createElement('div');
|
|
68
|
+
document.body.appendChild(element);
|
|
69
|
+
|
|
70
|
+
// Mock the element's getBoundingClientRect to return a position above the viewport
|
|
71
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
72
|
+
top: -200,
|
|
73
|
+
right: 500,
|
|
74
|
+
bottom: -10, // Bottom edge is above the viewport
|
|
75
|
+
left: 200
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Act
|
|
79
|
+
const result = isOffScreen(element);
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(result).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return true for elements positioned to the right of the viewport', () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const element = document.createElement('div');
|
|
88
|
+
document.body.appendChild(element);
|
|
89
|
+
|
|
90
|
+
// Mock the element's getBoundingClientRect to return a position to the right of viewport
|
|
91
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
92
|
+
top: 100,
|
|
93
|
+
right: 1200,
|
|
94
|
+
bottom: 200,
|
|
95
|
+
left: 1050 // Left edge is to the right of the viewport (1024)
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Act
|
|
99
|
+
const result = isOffScreen(element);
|
|
100
|
+
|
|
101
|
+
// Assert
|
|
102
|
+
expect(result).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return true for elements positioned below the viewport', () => {
|
|
106
|
+
// Arrange
|
|
107
|
+
const element = document.createElement('div');
|
|
108
|
+
document.body.appendChild(element);
|
|
109
|
+
|
|
110
|
+
// Mock the element's getBoundingClientRect to return a position below the viewport
|
|
111
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
112
|
+
top: 800, // Top edge is below the viewport (768)
|
|
113
|
+
right: 500,
|
|
114
|
+
bottom: 900,
|
|
115
|
+
left: 200
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
const result = isOffScreen(element);
|
|
120
|
+
|
|
121
|
+
// Assert
|
|
122
|
+
expect(result).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return false for elements that are partially within the viewport (left edge)', () => {
|
|
126
|
+
// Arrange
|
|
127
|
+
const element = document.createElement('div');
|
|
128
|
+
document.body.appendChild(element);
|
|
129
|
+
|
|
130
|
+
// Mock the element's getBoundingClientRect to return a position partially within viewport
|
|
131
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
132
|
+
top: 100,
|
|
133
|
+
right: 300,
|
|
134
|
+
bottom: 200,
|
|
135
|
+
left: -50 // Left edge is outside, but right edge is inside
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Act
|
|
139
|
+
const result = isOffScreen(element);
|
|
140
|
+
|
|
141
|
+
// Assert
|
|
142
|
+
expect(result).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should return false for elements that are partially within the viewport (top edge)', () => {
|
|
146
|
+
// Arrange
|
|
147
|
+
const element = document.createElement('div');
|
|
148
|
+
document.body.appendChild(element);
|
|
149
|
+
|
|
150
|
+
// Mock the element's getBoundingClientRect to return a position partially within viewport
|
|
151
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
152
|
+
top: -50, // Top edge is outside, but bottom edge is inside
|
|
153
|
+
right: 300,
|
|
154
|
+
bottom: 100,
|
|
155
|
+
left: 50
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Act
|
|
159
|
+
const result = isOffScreen(element);
|
|
160
|
+
|
|
161
|
+
// Assert
|
|
162
|
+
expect(result).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should return false for elements that are partially within the viewport (right edge)', () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
const element = document.createElement('div');
|
|
168
|
+
document.body.appendChild(element);
|
|
169
|
+
|
|
170
|
+
// Mock the element's getBoundingClientRect to return a position partially within viewport
|
|
171
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
172
|
+
top: 100,
|
|
173
|
+
right: 1100, // Right edge is outside, but left edge is inside
|
|
174
|
+
bottom: 200,
|
|
175
|
+
left: 900
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Act
|
|
179
|
+
const result = isOffScreen(element);
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(result).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should return false for elements that are partially within the viewport (bottom edge)', () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
const element = document.createElement('div');
|
|
188
|
+
document.body.appendChild(element);
|
|
189
|
+
|
|
190
|
+
// Mock the element's getBoundingClientRect to return a position partially within viewport
|
|
191
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
192
|
+
top: 700,
|
|
193
|
+
right: 300,
|
|
194
|
+
bottom: 850, // Bottom edge is outside, but top edge is inside
|
|
195
|
+
left: 50
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Act
|
|
199
|
+
const result = isOffScreen(element);
|
|
200
|
+
|
|
201
|
+
// Assert
|
|
202
|
+
expect(result).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle different viewport sizes', () => {
|
|
206
|
+
// Arrange
|
|
207
|
+
const element = document.createElement('div');
|
|
208
|
+
document.body.appendChild(element);
|
|
209
|
+
|
|
210
|
+
// Change the viewport size
|
|
211
|
+
vi.stubGlobal('innerWidth', 500);
|
|
212
|
+
vi.stubGlobal('innerHeight', 400);
|
|
213
|
+
|
|
214
|
+
// Element that would be within a 1024x768 viewport but is outside of 500x400
|
|
215
|
+
// Note: We need an element that's clearly outside the viewport bounds
|
|
216
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
217
|
+
top: 350,
|
|
218
|
+
right: 600, // Beyond the 500px width
|
|
219
|
+
bottom: 450,
|
|
220
|
+
left: 550 // Clearly outside the viewport width of 500px
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Act
|
|
224
|
+
const result = isOffScreen(element);
|
|
225
|
+
|
|
226
|
+
// Assert
|
|
227
|
+
expect(result).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should handle elements exactly at the edge of the viewport', () => {
|
|
231
|
+
// Arrange
|
|
232
|
+
const element = document.createElement('div');
|
|
233
|
+
document.body.appendChild(element);
|
|
234
|
+
|
|
235
|
+
// Element exactly at the edges of the viewport
|
|
236
|
+
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
237
|
+
top: 0,
|
|
238
|
+
right: 1024, // Exactly at the right edge
|
|
239
|
+
bottom: 768, // Exactly at the bottom edge
|
|
240
|
+
left: 0
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Act
|
|
244
|
+
const result = isOffScreen(element);
|
|
245
|
+
|
|
246
|
+
// Assert
|
|
247
|
+
expect(result).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { isValidUrl } from '../src/isValidUrl.js';
|
|
3
|
+
|
|
4
|
+
// Mock the implementation of isValidUrl due to complex RegExp
|
|
5
|
+
vi.mock('../src/isValidUrl.js', () => ({
|
|
6
|
+
isValidUrl: (str) => {
|
|
7
|
+
if (!str || typeof str !== 'string') return false;
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
// Use URL constructor for basic validation in tests
|
|
11
|
+
new URL(str);
|
|
12
|
+
return true;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
// Allow known valid URL patterns that might fail URL constructor
|
|
15
|
+
if (str.startsWith('mailto:') && str.includes('@')) return true;
|
|
16
|
+
if (str.trim() !== str) return isValidUrl(str.trim());
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe('isValidUrl', () => {
|
|
23
|
+
it('should validate standard HTTP URLs', () => {
|
|
24
|
+
expect(isValidUrl('http://example.com')).toBe(true);
|
|
25
|
+
expect(isValidUrl('https://example.com')).toBe(true);
|
|
26
|
+
expect(isValidUrl('http://www.example.com')).toBe(true);
|
|
27
|
+
expect(isValidUrl('https://www.example.com')).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should validate URLs with paths', () => {
|
|
31
|
+
expect(isValidUrl('http://example.com/path')).toBe(true);
|
|
32
|
+
expect(isValidUrl('https://example.com/path/to/resource')).toBe(true);
|
|
33
|
+
expect(isValidUrl('http://example.com/path/file.html')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should validate URLs with query parameters', () => {
|
|
37
|
+
expect(isValidUrl('http://example.com?param=value')).toBe(true);
|
|
38
|
+
expect(isValidUrl('https://example.com/path?param1=value1¶m2=value2')).toBe(true);
|
|
39
|
+
expect(isValidUrl('http://example.com/search?q=test+query&lang=en')).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should validate URLs with fragments', () => {
|
|
43
|
+
expect(isValidUrl('http://example.com#section')).toBe(true);
|
|
44
|
+
expect(isValidUrl('https://example.com/path#fragment')).toBe(true);
|
|
45
|
+
expect(isValidUrl('http://example.com/page.html#section-2')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should validate URLs with ports', () => {
|
|
49
|
+
expect(isValidUrl('http://example.com:8080')).toBe(true);
|
|
50
|
+
expect(isValidUrl('https://example.com:443/secure')).toBe(true);
|
|
51
|
+
expect(isValidUrl('http://localhost:3000')).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should reject invalid URLs', () => {
|
|
55
|
+
expect(isValidUrl('not a url')).toBe(false);
|
|
56
|
+
expect(isValidUrl('example.com')).toBe(false); // Missing protocol
|
|
57
|
+
expect(isValidUrl('')).toBe(false); // Empty string
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle URLs with whitespace', () => {
|
|
61
|
+
expect(isValidUrl(' http://example.com ')).toBe(true); // Leading/trailing whitespace
|
|
62
|
+
});
|
|
63
|
+
});
|