@afixt/test-utils 1.1.7 → 1.2.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/.claude/settings.local.json +6 -1
- package/.github/workflows/ci.yml +71 -0
- package/.github/workflows/pr-check.yml +88 -0
- package/.github/workflows/security.yml +139 -0
- package/.husky/pre-commit +1 -0
- package/.jscpd.json +27 -0
- package/.markdownlint.json +9 -0
- package/.prettierignore +13 -0
- package/.prettierrc +10 -0
- package/docs/arrayUtils.js.html +2 -2
- package/docs/domUtils.js.html +2 -2
- package/docs/getAccessibleName.js.html +2 -2
- package/docs/getAccessibleText.js.html +2 -2
- package/docs/getAriaAttributesByElement.js.html +2 -2
- package/docs/getCSSGeneratedContent.js.html +2 -2
- package/docs/getComputedRole.js.html +2 -2
- package/docs/getFocusableElements.js.html +2 -2
- package/docs/getGeneratedContent.js.html +2 -2
- package/docs/getImageText.js.html +2 -2
- package/docs/getStyleObject.js.html +2 -2
- package/docs/global.html +2 -2
- package/docs/hasAccessibleName.js.html +2 -2
- package/docs/hasAttribute.js.html +2 -2
- package/docs/hasCSSGeneratedContent.js.html +2 -2
- package/docs/hasHiddenParent.js.html +2 -2
- package/docs/hasParent.js.html +2 -2
- package/docs/hasValidAriaAttributes.js.html +2 -2
- package/docs/hasValidAriaRole.js.html +2 -2
- package/docs/index.html +2 -2
- package/docs/index.js.html +2 -2
- package/docs/isAriaAttributesValid.js.html +2 -2
- package/docs/isComplexTable.js.html +2 -2
- package/docs/isDataTable.js.html +2 -2
- package/docs/isFocusable.js.html +2 -2
- package/docs/isHidden.js.html +2 -2
- package/docs/isOffScreen.js.html +2 -2
- package/docs/isValidUrl.js.html +2 -2
- package/docs/isVisible.js.html +2 -2
- package/docs/module-afixt-test-utils.html +2 -2
- package/docs/scripts/core.js +726 -726
- package/docs/scripts/core.min.js +22 -22
- package/docs/scripts/resize.js +90 -90
- package/docs/scripts/search.js +265 -265
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -202
- package/docs/scripts/third-party/fuse.js +8 -8
- package/docs/scripts/third-party/hljs-line-num-original.js +369 -369
- package/docs/scripts/third-party/hljs-original.js +5171 -5171
- package/docs/scripts/third-party/popper.js +5 -5
- package/docs/scripts/third-party/tippy.js +1 -1
- package/docs/scripts/third-party/tocbot.js +671 -671
- package/docs/styles/clean-jsdoc-theme-base.css +1159 -1159
- package/docs/styles/clean-jsdoc-theme-dark.css +412 -412
- package/docs/styles/clean-jsdoc-theme-light.css +482 -482
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +29 -29
- package/docs/testContrast.js.html +2 -2
- package/docs/testLang.js.html +2 -2
- package/docs/testOrder.js.html +2 -2
- package/eslint.config.mjs +84 -0
- package/package.json +68 -41
- package/scratchpads/issue-6-standardize-repo.md +109 -0
- package/src/getAccessibleName.js +156 -112
- package/src/getAccessibleText.js +71 -42
- package/src/stringUtils.js +19 -21
- package/src/testContrast.js +103 -22
- package/test/getAccessibleName.test.js +379 -315
- package/test/getAccessibleText.test.js +375 -308
- package/test/stringUtils.test.js +376 -332
- package/test/testContrast.test.js +801 -651
- package/.eslintrc +0 -78
- package/.github/workflows/test.yml +0 -26
|
@@ -1,682 +1,832 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
testContrast,
|
|
4
|
+
getComputedBackgroundColor,
|
|
5
|
+
luminance,
|
|
6
|
+
parseRGB,
|
|
7
|
+
getColorContrast,
|
|
8
|
+
parseColor,
|
|
9
|
+
getRelativeLuminance,
|
|
10
|
+
getContrastRatio,
|
|
11
|
+
} from '../src/testContrast';
|
|
3
12
|
|
|
4
13
|
describe('testContrast', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
});
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
document.body.innerHTML = '';
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return false for null element', () => {
|
|
19
|
+
expect(testContrast(null)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return true for elements with no direct text', () => {
|
|
23
|
+
const div = document.createElement('div');
|
|
24
|
+
div.innerHTML = '<span>Text in child</span>';
|
|
25
|
+
document.body.appendChild(div);
|
|
26
|
+
|
|
27
|
+
expect(testContrast(div)).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return false for elements with identical foreground and background colors', () => {
|
|
31
|
+
const div = document.createElement('div');
|
|
32
|
+
div.textContent = 'Invisible text due to same colors';
|
|
33
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
34
|
+
div.style.backgroundColor = 'rgb(0, 0, 0)';
|
|
35
|
+
document.body.appendChild(div);
|
|
36
|
+
|
|
37
|
+
// Identical colors yield 0 contrast ratio, which fails any threshold
|
|
38
|
+
const result = testContrast(div);
|
|
39
|
+
expect(typeof result).toBe('boolean');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return true for elements with sufficient contrast (AA)', () => {
|
|
43
|
+
const div = document.createElement('div');
|
|
44
|
+
div.textContent = 'High contrast text';
|
|
45
|
+
div.style.color = 'rgb(0, 0, 0)'; // Black text
|
|
46
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)'; // White background
|
|
47
|
+
document.body.appendChild(div);
|
|
48
|
+
|
|
49
|
+
expect(testContrast(div, { level: 'AA' })).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle AAA level requirements', () => {
|
|
53
|
+
const div = document.createElement('div');
|
|
54
|
+
div.textContent = 'AAA level text';
|
|
55
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
56
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
57
|
+
document.body.appendChild(div);
|
|
58
|
+
|
|
59
|
+
expect(testContrast(div, { level: 'AAA' })).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle large text differently', () => {
|
|
63
|
+
const div = document.createElement('div');
|
|
64
|
+
div.textContent = 'Large text has different requirements';
|
|
65
|
+
div.style.color = 'rgb(90, 90, 90)';
|
|
66
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
67
|
+
div.style.fontSize = '18px';
|
|
68
|
+
document.body.appendChild(div);
|
|
69
|
+
|
|
70
|
+
// Large text only needs 3:1 for AA
|
|
71
|
+
expect(testContrast(div, { level: 'AA' })).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should handle bold text differently', () => {
|
|
75
|
+
const div = document.createElement('div');
|
|
76
|
+
div.textContent = 'Bold text at 14px';
|
|
77
|
+
div.style.color = 'rgb(100, 100, 100)';
|
|
78
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
79
|
+
div.style.fontSize = '14px';
|
|
80
|
+
div.style.fontWeight = 'bold';
|
|
81
|
+
document.body.appendChild(div);
|
|
82
|
+
|
|
83
|
+
// Bold text at 14px is treated as large text (3:1 for AA)
|
|
84
|
+
expect(testContrast(div, { level: 'AA' })).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle default options when none provided', () => {
|
|
88
|
+
const div = document.createElement('div');
|
|
89
|
+
div.textContent = 'Default options text';
|
|
90
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
91
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
92
|
+
document.body.appendChild(div);
|
|
93
|
+
|
|
94
|
+
expect(testContrast(div)).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should handle elements with background images', () => {
|
|
98
|
+
const div = document.createElement('div');
|
|
99
|
+
div.textContent = 'Text with background image';
|
|
100
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
101
|
+
div.style.backgroundImage = 'url(image.jpg)';
|
|
102
|
+
document.body.appendChild(div);
|
|
103
|
+
|
|
104
|
+
// Should return true because background color can't be determined
|
|
105
|
+
expect(testContrast(div)).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle invisible elements', () => {
|
|
109
|
+
const div = document.createElement('div');
|
|
110
|
+
div.textContent = 'Hidden text';
|
|
111
|
+
div.style.display = 'none';
|
|
112
|
+
document.body.appendChild(div);
|
|
113
|
+
|
|
114
|
+
expect(testContrast(div)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle offscreen elements', () => {
|
|
118
|
+
const div = document.createElement('div');
|
|
119
|
+
div.textContent = 'Offscreen text';
|
|
120
|
+
div.style.position = 'absolute';
|
|
121
|
+
div.style.left = '-9999px';
|
|
122
|
+
document.body.appendChild(div);
|
|
123
|
+
|
|
124
|
+
expect(testContrast(div)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle AA level for medium text (14-17px)', () => {
|
|
128
|
+
const div = document.createElement('div');
|
|
129
|
+
div.textContent = 'Medium text';
|
|
130
|
+
div.style.color = 'rgb(100, 100, 100)';
|
|
131
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
132
|
+
div.style.fontSize = '16px';
|
|
133
|
+
document.body.appendChild(div);
|
|
134
|
+
|
|
135
|
+
expect(testContrast(div, { level: 'AA' })).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle AAA level for large text', () => {
|
|
139
|
+
const div = document.createElement('div');
|
|
140
|
+
div.textContent = 'Large text AAA';
|
|
141
|
+
div.style.color = 'rgb(80, 80, 80)';
|
|
142
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
143
|
+
div.style.fontSize = '20px';
|
|
144
|
+
document.body.appendChild(div);
|
|
145
|
+
|
|
146
|
+
expect(testContrast(div, { level: 'AAA' })).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should handle bold text at 14px for AAA', () => {
|
|
150
|
+
const div = document.createElement('div');
|
|
151
|
+
div.textContent = 'Bold 14px AAA';
|
|
152
|
+
div.style.color = 'rgb(60, 60, 60)';
|
|
153
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
154
|
+
div.style.fontSize = '14px';
|
|
155
|
+
div.style.fontWeight = 'bold';
|
|
156
|
+
document.body.appendChild(div);
|
|
157
|
+
|
|
158
|
+
expect(testContrast(div, { level: 'AAA' })).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should handle numeric font weight', () => {
|
|
162
|
+
const div = document.createElement('div');
|
|
163
|
+
div.textContent = 'Numeric weight';
|
|
164
|
+
div.style.color = 'rgb(80, 80, 80)';
|
|
165
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
166
|
+
div.style.fontSize = '14px';
|
|
167
|
+
div.style.fontWeight = '700';
|
|
168
|
+
document.body.appendChild(div);
|
|
169
|
+
|
|
170
|
+
expect(testContrast(div, { level: 'AA' })).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should handle transparent background with parent', () => {
|
|
174
|
+
const parent = document.createElement('div');
|
|
175
|
+
parent.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
176
|
+
|
|
177
|
+
const child = document.createElement('div');
|
|
178
|
+
child.textContent = 'Child with transparent bg';
|
|
179
|
+
child.style.color = 'rgb(0, 0, 0)';
|
|
180
|
+
child.style.backgroundColor = 'rgba(0, 0, 0, 0)';
|
|
181
|
+
|
|
182
|
+
parent.appendChild(child);
|
|
183
|
+
document.body.appendChild(parent);
|
|
184
|
+
|
|
185
|
+
expect(testContrast(child)).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should handle elements without computed styles properly', () => {
|
|
189
|
+
const div = document.createElement('div');
|
|
190
|
+
div.textContent = 'Test without style';
|
|
191
|
+
|
|
192
|
+
// Just test that the function handles elements without explicit styles
|
|
193
|
+
expect(() => testContrast(div)).not.toThrow();
|
|
194
|
+
expect(typeof testContrast(div)).toBe('boolean');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle small text requiring 4.5:1 ratio', () => {
|
|
198
|
+
const div = document.createElement('div');
|
|
199
|
+
div.textContent = 'Small text';
|
|
200
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
201
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
202
|
+
div.style.fontSize = '12px';
|
|
203
|
+
document.body.appendChild(div);
|
|
204
|
+
|
|
205
|
+
expect(testContrast(div, { level: 'AA' })).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should handle element with parent having different colors', () => {
|
|
209
|
+
const parent = document.createElement('div');
|
|
210
|
+
parent.style.color = 'rgb(255, 0, 0)'; // Different color
|
|
211
|
+
parent.style.backgroundColor = 'rgb(0, 0, 255)'; // Different background
|
|
212
|
+
|
|
213
|
+
const child = document.createElement('div');
|
|
214
|
+
child.textContent = 'Child with different colors';
|
|
215
|
+
child.style.color = 'rgb(0, 0, 0)';
|
|
216
|
+
child.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
217
|
+
|
|
218
|
+
parent.appendChild(child);
|
|
219
|
+
document.body.appendChild(parent);
|
|
220
|
+
|
|
221
|
+
expect(testContrast(child)).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should handle element with parent having same colors', () => {
|
|
225
|
+
const parent = document.createElement('div');
|
|
226
|
+
parent.style.color = 'rgb(0, 0, 0)'; // Same color
|
|
227
|
+
parent.style.backgroundColor = 'rgb(255, 255, 255)'; // Same background
|
|
228
|
+
parent.style.fontSize = '16px'; // Same size
|
|
229
|
+
parent.style.fontWeight = 'normal'; // Same weight
|
|
230
|
+
|
|
231
|
+
const child = document.createElement('div');
|
|
232
|
+
child.textContent = 'Child with same colors';
|
|
233
|
+
child.style.color = 'rgb(0, 0, 0)';
|
|
234
|
+
child.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
235
|
+
child.style.fontSize = '16px';
|
|
236
|
+
child.style.fontWeight = 'normal';
|
|
237
|
+
|
|
238
|
+
parent.appendChild(child);
|
|
239
|
+
document.body.appendChild(parent);
|
|
240
|
+
|
|
241
|
+
expect(testContrast(child)).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should handle element without parent', () => {
|
|
245
|
+
const div = document.createElement('div');
|
|
246
|
+
div.textContent = 'No parent element';
|
|
247
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
248
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
249
|
+
|
|
250
|
+
// Don't append to body to test no parent scenario
|
|
251
|
+
expect(testContrast(div)).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should handle element with parent having different font size', () => {
|
|
255
|
+
const parent = document.createElement('div');
|
|
256
|
+
parent.style.color = 'rgb(0, 0, 0)';
|
|
257
|
+
parent.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
258
|
+
parent.style.fontSize = '16px';
|
|
259
|
+
|
|
260
|
+
const child = document.createElement('div');
|
|
261
|
+
child.textContent = 'Different font size';
|
|
262
|
+
child.style.color = 'rgb(0, 0, 0)';
|
|
263
|
+
child.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
264
|
+
child.style.fontSize = '20px'; // Different size
|
|
265
|
+
|
|
266
|
+
parent.appendChild(child);
|
|
267
|
+
document.body.appendChild(parent);
|
|
268
|
+
|
|
269
|
+
expect(testContrast(child)).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should handle element with parent having different font weight', () => {
|
|
273
|
+
const parent = document.createElement('div');
|
|
274
|
+
parent.style.color = 'rgb(0, 0, 0)';
|
|
275
|
+
parent.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
276
|
+
parent.style.fontWeight = 'normal';
|
|
277
|
+
|
|
278
|
+
const child = document.createElement('div');
|
|
279
|
+
child.textContent = 'Different font weight';
|
|
280
|
+
child.style.color = 'rgb(0, 0, 0)';
|
|
281
|
+
child.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
282
|
+
child.style.fontWeight = 'bold'; // Different weight
|
|
283
|
+
|
|
284
|
+
parent.appendChild(child);
|
|
285
|
+
document.body.appendChild(parent);
|
|
286
|
+
|
|
287
|
+
expect(testContrast(child)).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should return false when contrast is insufficient for AA level', () => {
|
|
291
|
+
const div = document.createElement('div');
|
|
292
|
+
div.textContent = 'Low contrast text';
|
|
293
|
+
div.style.color = 'rgb(200, 200, 200)'; // Light gray
|
|
294
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)'; // White
|
|
295
|
+
div.style.fontSize = '12px';
|
|
296
|
+
document.body.appendChild(div);
|
|
297
|
+
|
|
298
|
+
// This should fail AA contrast requirements (4.5:1 for small text)
|
|
299
|
+
const result = testContrast(div, { level: 'AA' });
|
|
300
|
+
expect(typeof result).toBe('boolean');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should return false when contrast is insufficient for AAA level', () => {
|
|
304
|
+
const div = document.createElement('div');
|
|
305
|
+
div.textContent = 'Low contrast AAA';
|
|
306
|
+
div.style.color = 'rgb(150, 150, 150)'; // Medium gray
|
|
307
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)'; // White
|
|
308
|
+
div.style.fontSize = '12px';
|
|
309
|
+
document.body.appendChild(div);
|
|
310
|
+
|
|
311
|
+
// This should fail AAA contrast requirements (7:1 for small text)
|
|
312
|
+
const result = testContrast(div, { level: 'AAA' });
|
|
313
|
+
expect(typeof result).toBe('boolean');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should handle AAA level for normal text under 18px', () => {
|
|
317
|
+
const div = document.createElement('div');
|
|
318
|
+
div.textContent = 'AAA small text';
|
|
319
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
320
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
321
|
+
div.style.fontSize = '12px';
|
|
322
|
+
document.body.appendChild(div);
|
|
323
|
+
|
|
324
|
+
expect(testContrast(div, { level: 'AAA' })).toBe(true);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should handle AAA level for bold text between 14-18px', () => {
|
|
328
|
+
const div = document.createElement('div');
|
|
329
|
+
div.textContent = 'AAA bold medium text';
|
|
330
|
+
div.style.color = 'rgb(80, 80, 80)';
|
|
331
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
332
|
+
div.style.fontSize = '16px';
|
|
333
|
+
div.style.fontWeight = 'bold';
|
|
334
|
+
document.body.appendChild(div);
|
|
335
|
+
|
|
336
|
+
expect(testContrast(div, { level: 'AAA' })).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should handle AAA level for normal text between 14-18px', () => {
|
|
340
|
+
const div = document.createElement('div');
|
|
341
|
+
div.textContent = 'AAA normal medium text';
|
|
342
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
343
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
344
|
+
div.style.fontSize = '16px';
|
|
345
|
+
div.style.fontWeight = 'normal';
|
|
346
|
+
document.body.appendChild(div);
|
|
347
|
+
|
|
348
|
+
expect(testContrast(div, { level: 'AAA' })).toBe(true);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should handle foreground darker than background', () => {
|
|
352
|
+
const div = document.createElement('div');
|
|
353
|
+
div.textContent = 'Dark on light';
|
|
354
|
+
div.style.color = 'rgb(50, 50, 50)'; // Dark foreground
|
|
355
|
+
div.style.backgroundColor = 'rgb(200, 200, 200)'; // Light background
|
|
356
|
+
document.body.appendChild(div);
|
|
357
|
+
|
|
358
|
+
expect(testContrast(div)).toBe(true);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should handle background darker than foreground', () => {
|
|
362
|
+
const div = document.createElement('div');
|
|
363
|
+
div.textContent = 'Light on dark';
|
|
364
|
+
div.style.color = 'rgb(200, 200, 200)'; // Light foreground
|
|
365
|
+
div.style.backgroundColor = 'rgb(50, 50, 50)'; // Dark background
|
|
366
|
+
document.body.appendChild(div);
|
|
367
|
+
|
|
368
|
+
expect(testContrast(div)).toBe(true);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should handle element with nested transparent backgrounds', () => {
|
|
372
|
+
const grandparent = document.createElement('div');
|
|
373
|
+
grandparent.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
374
|
+
|
|
375
|
+
const parent = document.createElement('div');
|
|
376
|
+
parent.style.backgroundColor = 'rgba(0, 0, 0, 0)';
|
|
377
|
+
|
|
378
|
+
const child = document.createElement('div');
|
|
379
|
+
child.textContent = 'Nested transparent';
|
|
380
|
+
child.style.color = 'rgb(0, 0, 0)';
|
|
381
|
+
child.style.backgroundColor = 'rgba(0, 0, 0, 0)';
|
|
382
|
+
|
|
383
|
+
grandparent.appendChild(parent);
|
|
384
|
+
parent.appendChild(child);
|
|
385
|
+
document.body.appendChild(grandparent);
|
|
386
|
+
|
|
387
|
+
expect(testContrast(child)).toBe(true);
|
|
388
|
+
});
|
|
381
389
|
});
|
|
382
390
|
|
|
383
391
|
describe('getComputedBackgroundColor', () => {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
392
|
+
beforeEach(() => {
|
|
393
|
+
document.body.innerHTML = '';
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('should return false for null element', () => {
|
|
397
|
+
expect(getComputedBackgroundColor(null)).toBe(false);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should return false for document node', () => {
|
|
401
|
+
expect(getComputedBackgroundColor(document)).toBe(false);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should return background color for element with solid color', () => {
|
|
405
|
+
const div = document.createElement('div');
|
|
406
|
+
div.style.backgroundColor = 'rgb(255, 0, 0)';
|
|
407
|
+
document.body.appendChild(div);
|
|
387
408
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
409
|
+
const result = getComputedBackgroundColor(div);
|
|
410
|
+
// In JSDOM this may return false due to limited computed style support
|
|
411
|
+
expect(typeof result === 'string' || result === false).toBe(true);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should return false when background-image is set', () => {
|
|
415
|
+
const div = document.createElement('div');
|
|
416
|
+
div.style.backgroundImage = 'url(test.jpg)';
|
|
417
|
+
document.body.appendChild(div);
|
|
418
|
+
|
|
419
|
+
const result = getComputedBackgroundColor(div);
|
|
420
|
+
expect(result).toBe(false);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should traverse to parent when background is transparent', () => {
|
|
424
|
+
const parent = document.createElement('div');
|
|
425
|
+
parent.style.backgroundColor = 'rgb(0, 255, 0)';
|
|
426
|
+
|
|
427
|
+
const child = document.createElement('div');
|
|
428
|
+
child.style.backgroundColor = 'rgba(0, 0, 0, 0)';
|
|
429
|
+
|
|
430
|
+
parent.appendChild(child);
|
|
431
|
+
document.body.appendChild(parent);
|
|
391
432
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
433
|
+
const result = getComputedBackgroundColor(child);
|
|
434
|
+
// In JSDOM this may return false due to limited computed style support
|
|
435
|
+
expect(typeof result === 'string' || result === false).toBe(true);
|
|
436
|
+
});
|
|
395
437
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
438
|
+
it('should return false if getComputedStyle is not available', () => {
|
|
439
|
+
const div = document.createElement('div');
|
|
440
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
|
441
|
+
window.getComputedStyle = null;
|
|
400
442
|
|
|
401
|
-
|
|
402
|
-
// In JSDOM this may return false due to limited computed style support
|
|
403
|
-
expect(typeof result === 'string' || result === false).toBe(true);
|
|
404
|
-
});
|
|
443
|
+
const result = getComputedBackgroundColor(div);
|
|
405
444
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
document.body.appendChild(div);
|
|
445
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
446
|
+
expect(result).toBe(false);
|
|
447
|
+
});
|
|
410
448
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
449
|
+
it('should return false if computed style returns null', () => {
|
|
450
|
+
const div = document.createElement('div');
|
|
451
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
|
452
|
+
window.getComputedStyle = () => null;
|
|
414
453
|
|
|
415
|
-
|
|
416
|
-
const parent = document.createElement('div');
|
|
417
|
-
parent.style.backgroundColor = 'rgb(0, 255, 0)';
|
|
454
|
+
const result = getComputedBackgroundColor(div);
|
|
418
455
|
|
|
419
|
-
|
|
420
|
-
|
|
456
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
457
|
+
expect(result).toBe(false);
|
|
458
|
+
});
|
|
421
459
|
|
|
422
|
-
|
|
423
|
-
|
|
460
|
+
it('should return fallbackColor when background-image is set and skipBackgroundImages is false', () => {
|
|
461
|
+
const div = document.createElement('div');
|
|
462
|
+
div.style.backgroundImage = 'url(test.jpg)';
|
|
463
|
+
document.body.appendChild(div);
|
|
424
464
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
});
|
|
465
|
+
const result = getComputedBackgroundColor(div, { skipBackgroundImages: false });
|
|
466
|
+
expect(result).toBe('rgb(255, 255, 255)');
|
|
467
|
+
});
|
|
429
468
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
469
|
+
it('should return custom fallbackColor when background-image is set and skipBackgroundImages is false', () => {
|
|
470
|
+
const div = document.createElement('div');
|
|
471
|
+
div.style.backgroundImage = 'url(test.jpg)';
|
|
472
|
+
document.body.appendChild(div);
|
|
434
473
|
|
|
435
|
-
|
|
474
|
+
const result = getComputedBackgroundColor(div, {
|
|
475
|
+
skipBackgroundImages: false,
|
|
476
|
+
fallbackColor: 'rgb(0, 0, 0)',
|
|
477
|
+
});
|
|
478
|
+
expect(result).toBe('rgb(0, 0, 0)');
|
|
479
|
+
});
|
|
436
480
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
481
|
+
it('should still return false when background-image is set and skipBackgroundImages is true (default)', () => {
|
|
482
|
+
const div = document.createElement('div');
|
|
483
|
+
div.style.backgroundImage = 'url(test.jpg)';
|
|
484
|
+
document.body.appendChild(div);
|
|
440
485
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
window.getComputedStyle = () => null;
|
|
486
|
+
const result = getComputedBackgroundColor(div, { skipBackgroundImages: true });
|
|
487
|
+
expect(result).toBe(false);
|
|
488
|
+
});
|
|
445
489
|
|
|
446
|
-
|
|
490
|
+
it('should return fallbackColor when no parent has a background color', () => {
|
|
491
|
+
const div = document.createElement('div');
|
|
492
|
+
div.style.backgroundColor = 'rgba(0, 0, 0, 0)';
|
|
493
|
+
document.body.appendChild(div);
|
|
447
494
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
495
|
+
const result = getComputedBackgroundColor(div, { fallbackColor: 'rgb(255, 255, 255)' });
|
|
496
|
+
// If traversal reaches a point with no more parents and still transparent,
|
|
497
|
+
// should use fallbackColor
|
|
498
|
+
expect(typeof result === 'string' || result === false).toBe(true);
|
|
499
|
+
});
|
|
451
500
|
});
|
|
452
501
|
|
|
453
502
|
describe('luminance', () => {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
503
|
+
it('should calculate luminance for pure black (0, 0, 0)', () => {
|
|
504
|
+
const result = luminance(0, 0, 0);
|
|
505
|
+
expect(result).toBe(0);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should calculate luminance for pure white (255, 255, 255)', () => {
|
|
509
|
+
const result = luminance(255, 255, 255);
|
|
510
|
+
expect(result).toBeCloseTo(1, 2);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('should calculate luminance for red (255, 0, 0)', () => {
|
|
514
|
+
const result = luminance(255, 0, 0);
|
|
515
|
+
expect(result).toBeGreaterThan(0);
|
|
516
|
+
expect(result).toBeLessThan(1);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should calculate luminance for green (0, 255, 0)', () => {
|
|
520
|
+
const result = luminance(0, 255, 0);
|
|
521
|
+
expect(result).toBeGreaterThan(0);
|
|
522
|
+
expect(result).toBeLessThan(1);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should calculate luminance for blue (0, 0, 255)', () => {
|
|
526
|
+
const result = luminance(0, 0, 255);
|
|
527
|
+
expect(result).toBeGreaterThan(0);
|
|
528
|
+
expect(result).toBeLessThan(1);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('should calculate luminance for gray (128, 128, 128)', () => {
|
|
532
|
+
const result = luminance(128, 128, 128);
|
|
533
|
+
expect(result).toBeGreaterThan(0);
|
|
534
|
+
expect(result).toBeLessThan(1);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should handle low values correctly (<=0.03928)', () => {
|
|
538
|
+
// RGB value 10 gives sRGB of 0.039 which is > 0.03928
|
|
539
|
+
// RGB value 5 gives sRGB of 0.0196 which is <= 0.03928
|
|
540
|
+
const result = luminance(5, 5, 5);
|
|
541
|
+
expect(result).toBeGreaterThan(0);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should handle high values correctly (>0.03928)', () => {
|
|
545
|
+
const result = luminance(200, 200, 200);
|
|
546
|
+
expect(result).toBeGreaterThan(0);
|
|
547
|
+
expect(result).toBeLessThan(1);
|
|
548
|
+
});
|
|
500
549
|
});
|
|
501
550
|
|
|
502
551
|
describe('parseRGB', () => {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
552
|
+
it('should parse rgb() format', () => {
|
|
553
|
+
const result = parseRGB('rgb(255, 0, 0)');
|
|
554
|
+
expect(result).toEqual(expect.arrayContaining(['rgb(255, 0, 0)', '255', '0', '0']));
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('should parse rgba() format', () => {
|
|
558
|
+
const result = parseRGB('rgba(255, 0, 0, 0.5)');
|
|
559
|
+
expect(result).toBeTruthy();
|
|
560
|
+
expect(result[1]).toBe('255');
|
|
561
|
+
expect(result[2]).toBe('0');
|
|
562
|
+
expect(result[3]).toBe('0');
|
|
563
|
+
expect(result[4]).toBe('0.5');
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it('should parse rgb with different values', () => {
|
|
567
|
+
const result = parseRGB('rgb(128, 64, 32)');
|
|
568
|
+
expect(result).toBeTruthy();
|
|
569
|
+
expect(result[1]).toBe('128');
|
|
570
|
+
expect(result[2]).toBe('64');
|
|
571
|
+
expect(result[3]).toBe('32');
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should return null for invalid format', () => {
|
|
575
|
+
const result = parseRGB('not a color');
|
|
576
|
+
expect(result).toBeNull();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should return null for hex color', () => {
|
|
580
|
+
const result = parseRGB('#FF0000');
|
|
581
|
+
expect(result).toBeNull();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should parse rgba with decimal alpha', () => {
|
|
585
|
+
const result = parseRGB('rgba(0, 0, 0, 0.75)');
|
|
586
|
+
expect(result).toBeTruthy();
|
|
587
|
+
expect(result[4]).toBe('0.75');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should parse rgb with single digit values', () => {
|
|
591
|
+
const result = parseRGB('rgb(1, 2, 3)');
|
|
592
|
+
expect(result).toBeTruthy();
|
|
593
|
+
expect(result[1]).toBe('1');
|
|
594
|
+
expect(result[2]).toBe('2');
|
|
595
|
+
expect(result[3]).toBe('3');
|
|
596
|
+
});
|
|
548
597
|
});
|
|
549
598
|
|
|
550
599
|
describe('getColorContrast', () => {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
}
|
|
600
|
+
beforeEach(() => {
|
|
601
|
+
document.body.innerHTML = '';
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('should return false for null element', () => {
|
|
605
|
+
expect(getColorContrast(null)).toBe(false);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should return false if getComputedStyle is not available', () => {
|
|
609
|
+
const div = document.createElement('div');
|
|
610
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
|
611
|
+
window.getComputedStyle = null;
|
|
612
|
+
|
|
613
|
+
const result = getColorContrast(div);
|
|
614
|
+
|
|
615
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
616
|
+
expect(result).toBe(false);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('should calculate contrast for black text on white background', () => {
|
|
620
|
+
const div = document.createElement('div');
|
|
621
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
622
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
623
|
+
document.body.appendChild(div);
|
|
624
|
+
|
|
625
|
+
const result = getColorContrast(div);
|
|
626
|
+
// In JSDOM, computed styles may not work correctly, so result may be false
|
|
627
|
+
expect(typeof result === 'number' || result === false).toBe(true);
|
|
628
|
+
if (typeof result === 'number') {
|
|
629
|
+
expect(result).toBeCloseTo(21, 0);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should calculate contrast for white text on black background', () => {
|
|
634
|
+
const div = document.createElement('div');
|
|
635
|
+
div.style.color = 'rgb(255, 255, 255)';
|
|
636
|
+
div.style.backgroundColor = 'rgb(0, 0, 0)';
|
|
637
|
+
document.body.appendChild(div);
|
|
638
|
+
|
|
639
|
+
const result = getColorContrast(div);
|
|
640
|
+
// In JSDOM, computed styles may not work correctly, so result may be false
|
|
641
|
+
expect(typeof result === 'number' || result === false).toBe(true);
|
|
642
|
+
if (typeof result === 'number') {
|
|
643
|
+
expect(result).toBeCloseTo(21, 0);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('should return 0 for identical foreground and background colors', () => {
|
|
648
|
+
const div = document.createElement('div');
|
|
649
|
+
div.style.color = 'rgb(128, 128, 128)';
|
|
650
|
+
div.style.backgroundColor = 'rgb(128, 128, 128)';
|
|
651
|
+
document.body.appendChild(div);
|
|
652
|
+
|
|
653
|
+
const result = getColorContrast(div);
|
|
654
|
+
// In JSDOM, computed styles may not work correctly, so result may be false
|
|
655
|
+
expect(typeof result === 'number' || result === false).toBe(true);
|
|
656
|
+
if (typeof result === 'number') {
|
|
657
|
+
expect(result).toBe(0);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('should return false if background color cannot be determined', () => {
|
|
662
|
+
const div = document.createElement('div');
|
|
663
|
+
div.style.color = 'rgb(0, 0, 0)';
|
|
664
|
+
div.style.backgroundImage = 'url(test.jpg)';
|
|
665
|
+
document.body.appendChild(div);
|
|
666
|
+
|
|
667
|
+
const result = getColorContrast(div);
|
|
668
|
+
expect(result).toBe(false);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it('should return false if foreground color cannot be parsed', () => {
|
|
672
|
+
const div = document.createElement('div');
|
|
673
|
+
document.body.appendChild(div);
|
|
674
|
+
|
|
675
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
|
676
|
+
window.getComputedStyle = () => ({
|
|
677
|
+
getPropertyValue: prop => {
|
|
678
|
+
if (prop === 'color') {
|
|
679
|
+
return 'invalid';
|
|
680
|
+
}
|
|
681
|
+
return 'rgb(255, 255, 255)';
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
const result = getColorContrast(div);
|
|
686
|
+
|
|
687
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
688
|
+
expect(result).toBe(false);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it('should calculate contrast for gray on white', () => {
|
|
692
|
+
const div = document.createElement('div');
|
|
693
|
+
div.style.color = 'rgb(128, 128, 128)';
|
|
694
|
+
div.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
695
|
+
document.body.appendChild(div);
|
|
696
|
+
|
|
697
|
+
const result = getColorContrast(div);
|
|
698
|
+
// In JSDOM, computed styles may not work correctly
|
|
699
|
+
expect(typeof result === 'number' || result === false).toBe(true);
|
|
700
|
+
if (typeof result === 'number') {
|
|
701
|
+
expect(result).toBeGreaterThan(1);
|
|
702
|
+
expect(result).toBeLessThan(21);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('should handle darker foreground and lighter background', () => {
|
|
707
|
+
const div = document.createElement('div');
|
|
708
|
+
div.style.color = 'rgb(50, 50, 50)';
|
|
709
|
+
div.style.backgroundColor = 'rgb(200, 200, 200)';
|
|
710
|
+
document.body.appendChild(div);
|
|
711
|
+
|
|
712
|
+
const result = getColorContrast(div);
|
|
713
|
+
// In JSDOM, computed styles may not work correctly
|
|
714
|
+
expect(typeof result === 'number' || result === false).toBe(true);
|
|
715
|
+
if (typeof result === 'number') {
|
|
716
|
+
expect(result).toBeGreaterThan(1);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it('should handle lighter foreground and darker background', () => {
|
|
721
|
+
const div = document.createElement('div');
|
|
722
|
+
div.style.color = 'rgb(200, 200, 200)';
|
|
723
|
+
div.style.backgroundColor = 'rgb(50, 50, 50)';
|
|
724
|
+
document.body.appendChild(div);
|
|
725
|
+
|
|
726
|
+
const result = getColorContrast(div);
|
|
727
|
+
// In JSDOM, computed styles may not work correctly
|
|
728
|
+
expect(typeof result === 'number' || result === false).toBe(true);
|
|
729
|
+
if (typeof result === 'number') {
|
|
730
|
+
expect(result).toBeGreaterThan(1);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
describe('parseColor', () => {
|
|
736
|
+
it('should parse rgb() string into an object with r, g, b properties', () => {
|
|
737
|
+
const result = parseColor('rgb(255, 128, 0)');
|
|
738
|
+
expect(result).toEqual({ r: 255, g: 128, b: 0 });
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it('should parse rgba() string into an object with r, g, b, a properties', () => {
|
|
742
|
+
const result = parseColor('rgba(255, 128, 0, 0.5)');
|
|
743
|
+
expect(result).toEqual({ r: 255, g: 128, b: 0, a: 0.5 });
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it('should return null for invalid color strings', () => {
|
|
747
|
+
expect(parseColor('not a color')).toBeNull();
|
|
748
|
+
expect(parseColor('#FF0000')).toBeNull();
|
|
749
|
+
expect(parseColor('')).toBeNull();
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('should handle single-digit RGB values', () => {
|
|
753
|
+
const result = parseColor('rgb(1, 2, 3)');
|
|
754
|
+
expect(result).toEqual({ r: 1, g: 2, b: 3 });
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('should handle rgba with alpha of 0', () => {
|
|
758
|
+
const result = parseColor('rgba(0, 0, 0, 0)');
|
|
759
|
+
expect(result).toEqual({ r: 0, g: 0, b: 0, a: 0 });
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('should handle rgba with alpha of 1', () => {
|
|
763
|
+
const result = parseColor('rgba(255, 255, 255, 1)');
|
|
764
|
+
expect(result).toEqual({ r: 255, g: 255, b: 255, a: 1 });
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it('should return null for null/undefined input', () => {
|
|
768
|
+
expect(parseColor(null)).toBeNull();
|
|
769
|
+
expect(parseColor(undefined)).toBeNull();
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
describe('getRelativeLuminance', () => {
|
|
774
|
+
it('should calculate luminance for black { r: 0, g: 0, b: 0 }', () => {
|
|
775
|
+
expect(getRelativeLuminance({ r: 0, g: 0, b: 0 })).toBe(0);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('should calculate luminance for white { r: 255, g: 255, b: 255 }', () => {
|
|
779
|
+
expect(getRelativeLuminance({ r: 255, g: 255, b: 255 })).toBeCloseTo(1, 2);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it('should return same value as luminance() for matching inputs', () => {
|
|
783
|
+
const fromObject = getRelativeLuminance({ r: 128, g: 64, b: 32 });
|
|
784
|
+
const fromArgs = luminance(128, 64, 32);
|
|
785
|
+
expect(fromObject).toBe(fromArgs);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
it('should return null for null/undefined input', () => {
|
|
789
|
+
expect(getRelativeLuminance(null)).toBeNull();
|
|
790
|
+
expect(getRelativeLuminance(undefined)).toBeNull();
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it('should return null for objects missing required properties', () => {
|
|
794
|
+
expect(getRelativeLuminance({ r: 255 })).toBeNull();
|
|
795
|
+
expect(getRelativeLuminance({ r: 255, g: 128 })).toBeNull();
|
|
796
|
+
expect(getRelativeLuminance({})).toBeNull();
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
describe('getContrastRatio', () => {
|
|
801
|
+
it('should return 21:1 for black on white', () => {
|
|
802
|
+
const result = getContrastRatio('rgb(0, 0, 0)', 'rgb(255, 255, 255)');
|
|
803
|
+
expect(result).toBeCloseTo(21, 0);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it('should return 21:1 for white on black', () => {
|
|
807
|
+
const result = getContrastRatio('rgb(255, 255, 255)', 'rgb(0, 0, 0)');
|
|
808
|
+
expect(result).toBeCloseTo(21, 0);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('should return 1 for identical colors', () => {
|
|
812
|
+
const result = getContrastRatio('rgb(128, 128, 128)', 'rgb(128, 128, 128)');
|
|
813
|
+
expect(result).toBe(1);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it('should return null for invalid color strings', () => {
|
|
817
|
+
expect(getContrastRatio('invalid', 'rgb(0, 0, 0)')).toBeNull();
|
|
818
|
+
expect(getContrastRatio('rgb(0, 0, 0)', 'invalid')).toBeNull();
|
|
819
|
+
expect(getContrastRatio('invalid', 'invalid')).toBeNull();
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('should return null for null/undefined input', () => {
|
|
823
|
+
expect(getContrastRatio(null, 'rgb(0, 0, 0)')).toBeNull();
|
|
824
|
+
expect(getContrastRatio('rgb(0, 0, 0)', null)).toBeNull();
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('should calculate correct ratio for gray on white', () => {
|
|
828
|
+
const result = getContrastRatio('rgb(128, 128, 128)', 'rgb(255, 255, 255)');
|
|
829
|
+
expect(result).toBeGreaterThan(1);
|
|
830
|
+
expect(result).toBeLessThan(21);
|
|
831
|
+
});
|
|
832
|
+
});
|