@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
|
@@ -9,19 +9,18 @@ const { test, expect } = require('@playwright/test');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
|
|
11
11
|
test.describe('CSS Pseudo-element Support', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//
|
|
12
|
+
test.beforeEach(async ({ page }) => {
|
|
13
|
+
// Load the test fixture HTML
|
|
14
|
+
const fixturePath = path.join(__dirname, 'fixtures', 'css-pseudo-elements.html');
|
|
15
|
+
await page.goto(`file://${fixturePath}`);
|
|
16
|
+
|
|
17
|
+
// Inject the functions into the page
|
|
18
|
+
await page.addScriptTag({
|
|
19
|
+
content: `
|
|
20
|
+
// getGeneratedContent: includes textContent alongside pseudo-element content
|
|
21
21
|
function getGeneratedContent(el) {
|
|
22
22
|
if (!el) return false;
|
|
23
23
|
|
|
24
|
-
// Get pseudo-element content properly
|
|
25
24
|
const beforeStyle = window.getComputedStyle(el, '::before');
|
|
26
25
|
const afterStyle = window.getComputedStyle(el, '::after');
|
|
27
26
|
|
|
@@ -29,11 +28,9 @@ test.describe('CSS Pseudo-element Support', () => {
|
|
|
29
28
|
let after = afterStyle.getPropertyValue('content') || '';
|
|
30
29
|
const inner = el.textContent || '';
|
|
31
30
|
|
|
32
|
-
// Remove quotes from CSS content values
|
|
33
31
|
before = before.replace(/^["']|["']$/g, '');
|
|
34
32
|
after = after.replace(/^["']|["']$/g, '');
|
|
35
33
|
|
|
36
|
-
// Filter out 'none' values
|
|
37
34
|
if (before === 'none') before = '';
|
|
38
35
|
if (after === 'none') after = '';
|
|
39
36
|
|
|
@@ -41,115 +38,251 @@ test.describe('CSS Pseudo-element Support', () => {
|
|
|
41
38
|
return result || false;
|
|
42
39
|
}
|
|
43
40
|
|
|
41
|
+
// extractMeaningfulContent: helper for getCSSGeneratedContent
|
|
42
|
+
function extractMeaningfulContent(rawValue) {
|
|
43
|
+
if (!rawValue || rawValue === 'none' || rawValue === 'normal') {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
let cleaned = rawValue.replace(/^["'](.*)["']$/, '$1');
|
|
47
|
+
cleaned = cleaned.trim();
|
|
48
|
+
return cleaned.length > 0 ? cleaned : false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// getCSSGeneratedContent: only checks ::before and ::after pseudo-elements
|
|
52
|
+
function getCSSGeneratedContent(el, pseudoElement) {
|
|
53
|
+
if (pseudoElement === undefined) pseudoElement = 'both';
|
|
54
|
+
if (!el) return false;
|
|
55
|
+
|
|
56
|
+
let content = '';
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
if (pseudoElement === 'before' || pseudoElement === 'both') {
|
|
60
|
+
const style = window.getComputedStyle(el, '::before');
|
|
61
|
+
const before = style.getPropertyValue('content');
|
|
62
|
+
const cleanBefore = extractMeaningfulContent(before);
|
|
63
|
+
if (cleanBefore) {
|
|
64
|
+
content += cleanBefore;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (pseudoElement === 'after' || pseudoElement === 'both') {
|
|
69
|
+
const style = window.getComputedStyle(el, '::after');
|
|
70
|
+
const after = style.getPropertyValue('content');
|
|
71
|
+
const cleanAfter = extractMeaningfulContent(after);
|
|
72
|
+
if (cleanAfter) {
|
|
73
|
+
content += (content ? ' ' : '') + cleanAfter;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return content ? content.trim() : false;
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
function hasCSSGeneratedContent(el) {
|
|
45
84
|
if (!el) return false;
|
|
46
|
-
const content =
|
|
85
|
+
const content = getCSSGeneratedContent(el);
|
|
47
86
|
return content !== false;
|
|
48
87
|
}
|
|
49
88
|
|
|
50
89
|
// Make functions globally available
|
|
51
90
|
window.getGeneratedContent = getGeneratedContent;
|
|
91
|
+
window.getCSSGeneratedContent = getCSSGeneratedContent;
|
|
52
92
|
window.hasCSSGeneratedContent = hasCSSGeneratedContent;
|
|
53
|
-
|
|
93
|
+
`,
|
|
94
|
+
});
|
|
54
95
|
});
|
|
55
|
-
});
|
|
56
96
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
test.describe('getGeneratedContent', () => {
|
|
98
|
+
test('should return ::before content when present', async ({ page }) => {
|
|
99
|
+
const result = await page.evaluate(() => {
|
|
100
|
+
const element = document.getElementById('with-before');
|
|
101
|
+
return window.getGeneratedContent(element);
|
|
102
|
+
});
|
|
63
103
|
|
|
64
|
-
|
|
65
|
-
|
|
104
|
+
expect(result).toContain('Before Content');
|
|
105
|
+
});
|
|
66
106
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
107
|
+
test('should return ::after content when present', async ({ page }) => {
|
|
108
|
+
const result = await page.evaluate(() => {
|
|
109
|
+
const element = document.getElementById('with-after');
|
|
110
|
+
return window.getGeneratedContent(element);
|
|
111
|
+
});
|
|
72
112
|
|
|
73
|
-
|
|
74
|
-
|
|
113
|
+
expect(result).toContain('After Content');
|
|
114
|
+
});
|
|
75
115
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
116
|
+
test('should combine ::before, text content, and ::after', async ({ page }) => {
|
|
117
|
+
const result = await page.evaluate(() => {
|
|
118
|
+
const element = document.getElementById('with-both');
|
|
119
|
+
return window.getGeneratedContent(element);
|
|
120
|
+
});
|
|
81
121
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
122
|
+
expect(result).toContain('Before');
|
|
123
|
+
expect(result).toContain('Text Content');
|
|
124
|
+
expect(result).toContain('After');
|
|
125
|
+
});
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
127
|
+
test('should handle quoted content values in CSS', async ({ page }) => {
|
|
128
|
+
const result = await page.evaluate(() => {
|
|
129
|
+
const element = document.getElementById('with-quotes');
|
|
130
|
+
return window.getGeneratedContent(element);
|
|
131
|
+
});
|
|
92
132
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
133
|
+
expect(result).toBeTruthy();
|
|
134
|
+
expect(result).toContain('Quoted');
|
|
135
|
+
});
|
|
96
136
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
137
|
+
test('should handle CSS content with special characters', async ({ page }) => {
|
|
138
|
+
const result = await page.evaluate(() => {
|
|
139
|
+
const element = document.getElementById('with-special-chars');
|
|
140
|
+
return window.getGeneratedContent(element);
|
|
141
|
+
});
|
|
102
142
|
|
|
103
|
-
|
|
104
|
-
|
|
143
|
+
expect(result).toBeTruthy();
|
|
144
|
+
});
|
|
105
145
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
test('should trim whitespace from the combined result', async ({ page }) => {
|
|
147
|
+
const result = await page.evaluate(() => {
|
|
148
|
+
const element = document.getElementById('with-whitespace');
|
|
149
|
+
return window.getGeneratedContent(element);
|
|
150
|
+
});
|
|
111
151
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
152
|
+
expect(result).toBeTruthy();
|
|
153
|
+
expect(result.startsWith(' ')).toBe(false);
|
|
154
|
+
expect(result.endsWith(' ')).toBe(false);
|
|
155
|
+
});
|
|
116
156
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
157
|
+
test('should handle nested elements with generated content', async ({ page }) => {
|
|
158
|
+
const result = await page.evaluate(() => {
|
|
159
|
+
const element = document.getElementById('nested-content');
|
|
160
|
+
return window.getGeneratedContent(element);
|
|
161
|
+
});
|
|
122
162
|
|
|
123
|
-
|
|
124
|
-
|
|
163
|
+
expect(result).toBeTruthy();
|
|
164
|
+
});
|
|
125
165
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
166
|
+
test('should handle content with HTML entities in CSS', async ({ page }) => {
|
|
167
|
+
const result = await page.evaluate(() => {
|
|
168
|
+
const element = document.getElementById('with-entities');
|
|
169
|
+
return window.getGeneratedContent(element);
|
|
170
|
+
});
|
|
131
171
|
|
|
132
|
-
|
|
172
|
+
expect(result).toBeTruthy();
|
|
173
|
+
});
|
|
133
174
|
});
|
|
134
|
-
});
|
|
135
175
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
176
|
+
test.describe('getCSSGeneratedContent', () => {
|
|
177
|
+
test('should get ::before content', async ({ page }) => {
|
|
178
|
+
const result = await page.evaluate(() => {
|
|
179
|
+
const element = document.getElementById('with-before');
|
|
180
|
+
return window.getCSSGeneratedContent(element, 'before');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(result).toBe('Before Content');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('should get ::after content', async ({ page }) => {
|
|
187
|
+
const result = await page.evaluate(() => {
|
|
188
|
+
const element = document.getElementById('with-after');
|
|
189
|
+
return window.getCSSGeneratedContent(element, 'after');
|
|
190
|
+
});
|
|
142
191
|
|
|
143
|
-
|
|
192
|
+
expect(result).toBe('After Content');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('should get both ::before and ::after content', async ({ page }) => {
|
|
196
|
+
const result = await page.evaluate(() => {
|
|
197
|
+
const element = document.getElementById('with-both');
|
|
198
|
+
return window.getCSSGeneratedContent(element);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result).toContain('Before');
|
|
202
|
+
expect(result).toContain('After');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('should handle quoted content', async ({ page }) => {
|
|
206
|
+
const result = await page.evaluate(() => {
|
|
207
|
+
const element = document.getElementById('with-quotes');
|
|
208
|
+
return window.getCSSGeneratedContent(element, 'before');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(result).toBeTruthy();
|
|
212
|
+
expect(result).toContain('Quoted');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('should handle URL content', async ({ page }) => {
|
|
216
|
+
const result = await page.evaluate(() => {
|
|
217
|
+
const element = document.getElementById('url-content');
|
|
218
|
+
return window.getCSSGeneratedContent(element, 'before');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(result).toBeTruthy();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('should default pseudoElement to both', async ({ page }) => {
|
|
225
|
+
const result = await page.evaluate(() => {
|
|
226
|
+
const element = document.getElementById('with-before');
|
|
227
|
+
return window.getCSSGeneratedContent(element);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(result).toBe('Before Content');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('should return false for empty content', async ({ page }) => {
|
|
234
|
+
const result = await page.evaluate(() => {
|
|
235
|
+
const element = document.getElementById('empty-content');
|
|
236
|
+
return window.getCSSGeneratedContent(element);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
expect(result).toBe(false);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('should return false for content: none', async ({ page }) => {
|
|
243
|
+
const result = await page.evaluate(() => {
|
|
244
|
+
const element = document.getElementById('no-content');
|
|
245
|
+
return window.getCSSGeneratedContent(element);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(result).toBe(false);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('should return false for null element', async ({ page }) => {
|
|
252
|
+
const result = await page.evaluate(() => {
|
|
253
|
+
return window.getCSSGeneratedContent(null);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
expect(result).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('should return false for elements with no pseudo-element styles', async ({ page }) => {
|
|
260
|
+
const result = await page.evaluate(() => {
|
|
261
|
+
const element = document.getElementById('plain-div');
|
|
262
|
+
return window.getCSSGeneratedContent(element);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(result).toBe(false);
|
|
266
|
+
});
|
|
144
267
|
});
|
|
145
268
|
|
|
146
|
-
test('
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
269
|
+
test.describe('hasCSSGeneratedContent', () => {
|
|
270
|
+
test('should correctly identify elements with ::before content', async ({ page }) => {
|
|
271
|
+
const result = await page.evaluate(() => {
|
|
272
|
+
const element = document.getElementById('with-before');
|
|
273
|
+
return window.hasCSSGeneratedContent(element);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(result).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('should correctly identify elements with ::after content', async ({ page }) => {
|
|
280
|
+
const result = await page.evaluate(() => {
|
|
281
|
+
const element = document.getElementById('with-after');
|
|
282
|
+
return window.hasCSSGeneratedContent(element);
|
|
283
|
+
});
|
|
151
284
|
|
|
152
|
-
|
|
285
|
+
expect(result).toBe(true);
|
|
286
|
+
});
|
|
153
287
|
});
|
|
154
|
-
});
|
|
155
288
|
});
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
#no-content::before {
|
|
53
53
|
content: none;
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
#url-content::before {
|
|
57
|
+
content: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
|
|
58
|
+
}
|
|
55
59
|
</style>
|
|
56
60
|
</head>
|
|
57
61
|
<body>
|
|
@@ -67,6 +71,8 @@
|
|
|
67
71
|
<div id="with-entities">Entities</div>
|
|
68
72
|
<div id="empty-content">Empty</div>
|
|
69
73
|
<div id="no-content">None</div>
|
|
74
|
+
<div id="url-content">URL Content</div>
|
|
75
|
+
<div id="plain-div">Plain div no pseudo-elements</div>
|
|
70
76
|
|
|
71
77
|
<!-- Load the functions we need directly in the page -->
|
|
72
78
|
<script type="module">
|