@afixt/test-utils 1.1.8 → 1.2.1
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 +5 -1
- package/.github/workflows/pr-check.yml +88 -0
- package/.github/workflows/security.yml +0 -3
- package/eslint.config.mjs +1 -1
- package/package.json +2 -1
- package/src/constants.js +231 -0
- package/src/cssUtils.js +77 -0
- package/src/domUtils.js +268 -12
- package/src/formUtils.js +175 -0
- package/src/getCSSGeneratedContent.js +39 -17
- package/src/index.js +18 -2
- package/src/stringUtils.js +168 -21
- package/src/tableUtils.js +180 -0
- package/src/testContrast.js +137 -22
- package/src/testLang.js +514 -444
- package/test/cssUtils.test.js +248 -0
- package/test/domUtils.test.js +815 -297
- package/test/formUtils.test.js +389 -0
- package/test/getCSSGeneratedContent.test.js +187 -232
- package/test/hasCSSGeneratedContent.test.js +37 -147
- package/test/playwright/css-pseudo-elements.spec.js +224 -91
- package/test/playwright/fixtures/css-pseudo-elements.html +6 -0
- package/test/stringUtils.test.js +609 -343
- package/test/tableUtils.test.js +340 -0
- package/test/testContrast.test.js +801 -651
- package/vitest.config.js +28 -28
- package/.github/dependabot.yml +0 -36
- package/test/getCSSGeneratedContent.browser.test.js +0 -125
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import tableUtils from '../src/tableUtils.js';
|
|
3
|
+
|
|
4
|
+
describe('tableUtils', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('hasMultipleHeaderRows', () => {
|
|
10
|
+
it('should return true for table with 3+ header rows', () => {
|
|
11
|
+
document.body.innerHTML = `
|
|
12
|
+
<table id="t">
|
|
13
|
+
<tr><th>H1</th><th>H2</th></tr>
|
|
14
|
+
<tr><th>H3</th><th>H4</th></tr>
|
|
15
|
+
<tr><th>H5</th><th>H6</th></tr>
|
|
16
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
17
|
+
</table>
|
|
18
|
+
`;
|
|
19
|
+
expect(tableUtils.hasMultipleHeaderRows(document.getElementById('t'))).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return false for table with fewer than 3 header rows', () => {
|
|
23
|
+
document.body.innerHTML = `
|
|
24
|
+
<table id="t">
|
|
25
|
+
<tr><th>H1</th><th>H2</th></tr>
|
|
26
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
27
|
+
<tr><td>D3</td><td>D4</td></tr>
|
|
28
|
+
</table>
|
|
29
|
+
`;
|
|
30
|
+
expect(tableUtils.hasMultipleHeaderRows(document.getElementById('t'))).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should return false for table with no header rows', () => {
|
|
34
|
+
document.body.innerHTML = `
|
|
35
|
+
<table id="t">
|
|
36
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
37
|
+
<tr><td>D3</td><td>D4</td></tr>
|
|
38
|
+
</table>
|
|
39
|
+
`;
|
|
40
|
+
expect(tableUtils.hasMultipleHeaderRows(document.getElementById('t'))).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should not count rows where th count is less than td count', () => {
|
|
44
|
+
document.body.innerHTML = `
|
|
45
|
+
<table id="t">
|
|
46
|
+
<tr><th>H1</th><th>H2</th><th>H3</th></tr>
|
|
47
|
+
<tr><th>H4</th><td>D1</td><td>D2</td></tr>
|
|
48
|
+
<tr><td>D3</td><td>D4</td><td>D5</td></tr>
|
|
49
|
+
</table>
|
|
50
|
+
`;
|
|
51
|
+
expect(tableUtils.hasMultipleHeaderRows(document.getElementById('t'))).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('hasComplexHeaderSpans', () => {
|
|
56
|
+
it('should return true when colspan exceeds max', () => {
|
|
57
|
+
document.body.innerHTML = `
|
|
58
|
+
<table id="t">
|
|
59
|
+
<tr><th colspan="5">Wide header</th></tr>
|
|
60
|
+
<tr><td>D1</td><td>D2</td><td>D3</td><td>D4</td><td>D5</td></tr>
|
|
61
|
+
</table>
|
|
62
|
+
`;
|
|
63
|
+
expect(tableUtils.hasComplexHeaderSpans(document.getElementById('t'))).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should return true when rowspan exceeds max', () => {
|
|
67
|
+
document.body.innerHTML = `
|
|
68
|
+
<table id="t">
|
|
69
|
+
<tr><th rowspan="4">Tall header</th><td>D1</td></tr>
|
|
70
|
+
<tr><td>D2</td></tr>
|
|
71
|
+
<tr><td>D3</td></tr>
|
|
72
|
+
<tr><td>D4</td></tr>
|
|
73
|
+
</table>
|
|
74
|
+
`;
|
|
75
|
+
expect(tableUtils.hasComplexHeaderSpans(document.getElementById('t'))).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should return false when spans are within defaults', () => {
|
|
79
|
+
document.body.innerHTML = `
|
|
80
|
+
<table id="t">
|
|
81
|
+
<tr><th colspan="2">Header</th><th>H2</th></tr>
|
|
82
|
+
<tr><td>D1</td><td>D2</td><td>D3</td></tr>
|
|
83
|
+
</table>
|
|
84
|
+
`;
|
|
85
|
+
expect(tableUtils.hasComplexHeaderSpans(document.getElementById('t'))).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should support custom max values', () => {
|
|
89
|
+
document.body.innerHTML = `
|
|
90
|
+
<table id="t">
|
|
91
|
+
<tr><th colspan="3">Header</th></tr>
|
|
92
|
+
<tr><td>D1</td><td>D2</td><td>D3</td></tr>
|
|
93
|
+
</table>
|
|
94
|
+
`;
|
|
95
|
+
expect(tableUtils.hasComplexHeaderSpans(document.getElementById('t'), 2, 2)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should return false for table with no spanning headers', () => {
|
|
99
|
+
document.body.innerHTML = `
|
|
100
|
+
<table id="t">
|
|
101
|
+
<tr><th>H1</th><th>H2</th></tr>
|
|
102
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
103
|
+
</table>
|
|
104
|
+
`;
|
|
105
|
+
expect(tableUtils.hasComplexHeaderSpans(document.getElementById('t'))).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('getMaxColumnCount', () => {
|
|
110
|
+
it('should count columns correctly', () => {
|
|
111
|
+
document.body.innerHTML = `
|
|
112
|
+
<table id="t">
|
|
113
|
+
<tr><th>H1</th><th>H2</th><th>H3</th></tr>
|
|
114
|
+
<tr><td>D1</td><td>D2</td><td>D3</td></tr>
|
|
115
|
+
</table>
|
|
116
|
+
`;
|
|
117
|
+
expect(tableUtils.getMaxColumnCount(document.getElementById('t'))).toBe(3);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should account for colspan', () => {
|
|
121
|
+
document.body.innerHTML = `
|
|
122
|
+
<table id="t">
|
|
123
|
+
<tr><th colspan="3">Wide header</th><th>H2</th></tr>
|
|
124
|
+
<tr><td>D1</td><td>D2</td><td>D3</td><td>D4</td></tr>
|
|
125
|
+
</table>
|
|
126
|
+
`;
|
|
127
|
+
expect(tableUtils.getMaxColumnCount(document.getElementById('t'))).toBe(4);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should return the maximum across all rows', () => {
|
|
131
|
+
document.body.innerHTML = `
|
|
132
|
+
<table id="t">
|
|
133
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
134
|
+
<tr><td>D1</td><td>D2</td><td>D3</td><td>D4</td><td>D5</td></tr>
|
|
135
|
+
<tr><td>D1</td><td>D2</td><td>D3</td></tr>
|
|
136
|
+
</table>
|
|
137
|
+
`;
|
|
138
|
+
expect(tableUtils.getMaxColumnCount(document.getElementById('t'))).toBe(5);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should return 0 for empty table', () => {
|
|
142
|
+
document.body.innerHTML = '<table id="t"></table>';
|
|
143
|
+
expect(tableUtils.getMaxColumnCount(document.getElementById('t'))).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('hasMultiLevelHeaderGroupings', () => {
|
|
148
|
+
it('should return true when 2+ rows have spanned headers', () => {
|
|
149
|
+
document.body.innerHTML = `
|
|
150
|
+
<table id="t">
|
|
151
|
+
<tr><th colspan="2">Group A</th><th colspan="2">Group B</th></tr>
|
|
152
|
+
<tr><th rowspan="2">Sub</th><th>S1</th><th>S2</th><th>S3</th></tr>
|
|
153
|
+
<tr><td>D1</td><td>D2</td><td>D3</td></tr>
|
|
154
|
+
</table>
|
|
155
|
+
`;
|
|
156
|
+
expect(tableUtils.hasMultiLevelHeaderGroupings(document.getElementById('t'))).toBe(
|
|
157
|
+
true
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should return false when only 1 row has spanned headers', () => {
|
|
162
|
+
document.body.innerHTML = `
|
|
163
|
+
<table id="t">
|
|
164
|
+
<tr><th colspan="2">Header</th></tr>
|
|
165
|
+
<tr><th>H1</th><th>H2</th></tr>
|
|
166
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
167
|
+
</table>
|
|
168
|
+
`;
|
|
169
|
+
expect(tableUtils.hasMultiLevelHeaderGroupings(document.getElementById('t'))).toBe(
|
|
170
|
+
false
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return false when no rows have spanned headers', () => {
|
|
175
|
+
document.body.innerHTML = `
|
|
176
|
+
<table id="t">
|
|
177
|
+
<tr><th>H1</th><th>H2</th></tr>
|
|
178
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
179
|
+
</table>
|
|
180
|
+
`;
|
|
181
|
+
expect(tableUtils.hasMultiLevelHeaderGroupings(document.getElementById('t'))).toBe(
|
|
182
|
+
false
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('isHeaderOnlyTable', () => {
|
|
188
|
+
it('should return true for table with only th cells', () => {
|
|
189
|
+
document.body.innerHTML = `
|
|
190
|
+
<table id="t">
|
|
191
|
+
<tr><th>H1</th><th>H2</th></tr>
|
|
192
|
+
<tr><th>H3</th><th>H4</th></tr>
|
|
193
|
+
</table>
|
|
194
|
+
`;
|
|
195
|
+
expect(tableUtils.isHeaderOnlyTable(document.getElementById('t'))).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should return false for table with both th and td', () => {
|
|
199
|
+
document.body.innerHTML = `
|
|
200
|
+
<table id="t">
|
|
201
|
+
<tr><th>H1</th><th>H2</th></tr>
|
|
202
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
203
|
+
</table>
|
|
204
|
+
`;
|
|
205
|
+
expect(tableUtils.isHeaderOnlyTable(document.getElementById('t'))).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should return false for table with only td cells', () => {
|
|
209
|
+
document.body.innerHTML = `
|
|
210
|
+
<table id="t">
|
|
211
|
+
<tr><td>D1</td><td>D2</td></tr>
|
|
212
|
+
</table>
|
|
213
|
+
`;
|
|
214
|
+
expect(tableUtils.isHeaderOnlyTable(document.getElementById('t'))).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('isHeaderOnlyAriaTable', () => {
|
|
219
|
+
it('should return true for ARIA table with only header roles', () => {
|
|
220
|
+
document.body.innerHTML = `
|
|
221
|
+
<div role="table" id="t">
|
|
222
|
+
<div role="row">
|
|
223
|
+
<div role="columnheader">H1</div>
|
|
224
|
+
<div role="columnheader">H2</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
`;
|
|
228
|
+
expect(tableUtils.isHeaderOnlyAriaTable(document.getElementById('t'))).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should return true for ARIA table with rowheader only', () => {
|
|
232
|
+
document.body.innerHTML = `
|
|
233
|
+
<div role="table" id="t">
|
|
234
|
+
<div role="row">
|
|
235
|
+
<div role="rowheader">RH</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
`;
|
|
239
|
+
expect(tableUtils.isHeaderOnlyAriaTable(document.getElementById('t'))).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should return false for ARIA table with cell roles', () => {
|
|
243
|
+
document.body.innerHTML = `
|
|
244
|
+
<div role="table" id="t">
|
|
245
|
+
<div role="row">
|
|
246
|
+
<div role="columnheader">H1</div>
|
|
247
|
+
<div role="cell">D1</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
`;
|
|
251
|
+
expect(tableUtils.isHeaderOnlyAriaTable(document.getElementById('t'))).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should return false for ARIA table with gridcell roles', () => {
|
|
255
|
+
document.body.innerHTML = `
|
|
256
|
+
<div role="table" id="t">
|
|
257
|
+
<div role="row">
|
|
258
|
+
<div role="columnheader">H1</div>
|
|
259
|
+
<div role="gridcell">D1</div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
`;
|
|
263
|
+
expect(tableUtils.isHeaderOnlyAriaTable(document.getElementById('t'))).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should return false when no headers exist', () => {
|
|
267
|
+
document.body.innerHTML = `
|
|
268
|
+
<div role="table" id="t">
|
|
269
|
+
<div role="row">
|
|
270
|
+
<div role="cell">D1</div>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
`;
|
|
274
|
+
expect(tableUtils.isHeaderOnlyAriaTable(document.getElementById('t'))).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('isPunctuationOnly', () => {
|
|
279
|
+
it('should return true for strings with only punctuation', () => {
|
|
280
|
+
expect(tableUtils.isPunctuationOnly('...')).toBe(true);
|
|
281
|
+
expect(tableUtils.isPunctuationOnly('---')).toBe(true);
|
|
282
|
+
expect(tableUtils.isPunctuationOnly('!!!')).toBe(true);
|
|
283
|
+
expect(tableUtils.isPunctuationOnly('***')).toBe(true);
|
|
284
|
+
expect(tableUtils.isPunctuationOnly('.,;:!?')).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should return false for strings with letters', () => {
|
|
288
|
+
expect(tableUtils.isPunctuationOnly('hello')).toBe(false);
|
|
289
|
+
expect(tableUtils.isPunctuationOnly('...hello')).toBe(false);
|
|
290
|
+
expect(tableUtils.isPunctuationOnly('a')).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should return false for strings with digits', () => {
|
|
294
|
+
expect(tableUtils.isPunctuationOnly('123')).toBe(false);
|
|
295
|
+
expect(tableUtils.isPunctuationOnly('...1')).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should return false for empty string', () => {
|
|
299
|
+
expect(tableUtils.isPunctuationOnly('')).toBe(false);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should return true for symbol characters', () => {
|
|
303
|
+
expect(tableUtils.isPunctuationOnly('$€£')).toBe(true);
|
|
304
|
+
expect(tableUtils.isPunctuationOnly('+')).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('isGenericSummary', () => {
|
|
309
|
+
it('should return true for common generic summaries', () => {
|
|
310
|
+
expect(tableUtils.isGenericSummary('table')).toBe(true);
|
|
311
|
+
expect(tableUtils.isGenericSummary('data table')).toBe(true);
|
|
312
|
+
expect(tableUtils.isGenericSummary('layout')).toBe(true);
|
|
313
|
+
expect(tableUtils.isGenericSummary('n/a')).toBe(true);
|
|
314
|
+
expect(tableUtils.isGenericSummary('none')).toBe(true);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should be case-insensitive', () => {
|
|
318
|
+
expect(tableUtils.isGenericSummary('TABLE')).toBe(true);
|
|
319
|
+
expect(tableUtils.isGenericSummary('Data Table')).toBe(true);
|
|
320
|
+
expect(tableUtils.isGenericSummary('LAYOUT')).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should trim whitespace', () => {
|
|
324
|
+
expect(tableUtils.isGenericSummary(' table ')).toBe(true);
|
|
325
|
+
expect(tableUtils.isGenericSummary(' layout ')).toBe(true);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should return false for descriptive summaries', () => {
|
|
329
|
+
expect(tableUtils.isGenericSummary('Quarterly sales data for 2024')).toBe(false);
|
|
330
|
+
expect(tableUtils.isGenericSummary('List of employees by department')).toBe(false);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should support custom generic list', () => {
|
|
334
|
+
const customList = ['custom', 'test'];
|
|
335
|
+
expect(tableUtils.isGenericSummary('custom', customList)).toBe(true);
|
|
336
|
+
expect(tableUtils.isGenericSummary('test', customList)).toBe(true);
|
|
337
|
+
expect(tableUtils.isGenericSummary('table', customList)).toBe(false);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|