@afixt/test-utils 1.2.0 → 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/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 +149 -0
- package/src/tableUtils.js +180 -0
- package/src/testContrast.js +35 -1
- 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 +222 -0
- package/test/tableUtils.test.js +340 -0
- package/vitest.config.js +28 -28
- 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">
|
package/test/stringUtils.test.js
CHANGED
|
@@ -423,4 +423,226 @@ describe('stringUtils', () => {
|
|
|
423
423
|
expect(typeof result).toBe('string');
|
|
424
424
|
});
|
|
425
425
|
});
|
|
426
|
+
|
|
427
|
+
describe('isEmptyOrWhitespace', () => {
|
|
428
|
+
it('should return true for null/undefined/empty', () => {
|
|
429
|
+
expect(stringUtils.isEmptyOrWhitespace(null)).toBe(true);
|
|
430
|
+
expect(stringUtils.isEmptyOrWhitespace(undefined)).toBe(true);
|
|
431
|
+
expect(stringUtils.isEmptyOrWhitespace('')).toBe(true);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should return true for whitespace-only strings', () => {
|
|
435
|
+
expect(stringUtils.isEmptyOrWhitespace(' ')).toBe(true);
|
|
436
|
+
expect(stringUtils.isEmptyOrWhitespace('\t\n')).toBe(true);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should return true for strings with zero-width spaces', () => {
|
|
440
|
+
expect(stringUtils.isEmptyOrWhitespace('\u200B')).toBe(true);
|
|
441
|
+
expect(stringUtils.isEmptyOrWhitespace('\u200C')).toBe(true);
|
|
442
|
+
expect(stringUtils.isEmptyOrWhitespace('\u200D')).toBe(true);
|
|
443
|
+
expect(stringUtils.isEmptyOrWhitespace('\uFEFF')).toBe(true);
|
|
444
|
+
expect(stringUtils.isEmptyOrWhitespace('\u2060')).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should return true for non-breaking spaces only', () => {
|
|
448
|
+
expect(stringUtils.isEmptyOrWhitespace('\u00A0')).toBe(true);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should return false for strings with visible characters', () => {
|
|
452
|
+
expect(stringUtils.isEmptyOrWhitespace('hello')).toBe(false);
|
|
453
|
+
expect(stringUtils.isEmptyOrWhitespace(' a ')).toBe(false);
|
|
454
|
+
expect(stringUtils.isEmptyOrWhitespace('\u200Bx')).toBe(false);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
describe('isGenericTitle', () => {
|
|
459
|
+
it('should return true for known generic titles', () => {
|
|
460
|
+
expect(stringUtils.isGenericTitle('iframe')).toBe(true);
|
|
461
|
+
expect(stringUtils.isGenericTitle('frame')).toBe(true);
|
|
462
|
+
expect(stringUtils.isGenericTitle('untitled')).toBe(true);
|
|
463
|
+
expect(stringUtils.isGenericTitle('title')).toBe(true);
|
|
464
|
+
expect(stringUtils.isGenericTitle('content')).toBe(true);
|
|
465
|
+
expect(stringUtils.isGenericTitle('main')).toBe(true);
|
|
466
|
+
expect(stringUtils.isGenericTitle('page')).toBe(true);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should be case-insensitive', () => {
|
|
470
|
+
expect(stringUtils.isGenericTitle('IFRAME')).toBe(true);
|
|
471
|
+
expect(stringUtils.isGenericTitle('Untitled')).toBe(true);
|
|
472
|
+
expect(stringUtils.isGenericTitle('TITLE')).toBe(true);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should match numbered variants', () => {
|
|
476
|
+
expect(stringUtils.isGenericTitle('frame1')).toBe(true);
|
|
477
|
+
expect(stringUtils.isGenericTitle('iframe2')).toBe(true);
|
|
478
|
+
expect(stringUtils.isGenericTitle('untitled3')).toBe(true);
|
|
479
|
+
expect(stringUtils.isGenericTitle('title42')).toBe(true);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('should return false for descriptive titles', () => {
|
|
483
|
+
expect(stringUtils.isGenericTitle('Contact Form')).toBe(false);
|
|
484
|
+
expect(stringUtils.isGenericTitle('Product Details')).toBe(false);
|
|
485
|
+
expect(stringUtils.isGenericTitle('Navigation Menu')).toBe(false);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should return false for null/undefined', () => {
|
|
489
|
+
expect(stringUtils.isGenericTitle(null)).toBe(false);
|
|
490
|
+
expect(stringUtils.isGenericTitle(undefined)).toBe(false);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('should trim whitespace', () => {
|
|
494
|
+
expect(stringUtils.isGenericTitle(' iframe ')).toBe(true);
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe('isGenericLinkText', () => {
|
|
499
|
+
it('should return true for common generic link text', () => {
|
|
500
|
+
expect(stringUtils.isGenericLinkText('click here')).toBe(true);
|
|
501
|
+
expect(stringUtils.isGenericLinkText('here')).toBe(true);
|
|
502
|
+
expect(stringUtils.isGenericLinkText('more')).toBe(true);
|
|
503
|
+
expect(stringUtils.isGenericLinkText('read more')).toBe(true);
|
|
504
|
+
expect(stringUtils.isGenericLinkText('learn more')).toBe(true);
|
|
505
|
+
expect(stringUtils.isGenericLinkText('link')).toBe(true);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should be case-insensitive', () => {
|
|
509
|
+
expect(stringUtils.isGenericLinkText('Click Here')).toBe(true);
|
|
510
|
+
expect(stringUtils.isGenericLinkText('READ MORE')).toBe(true);
|
|
511
|
+
expect(stringUtils.isGenericLinkText('LEARN MORE')).toBe(true);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('should return false for descriptive text', () => {
|
|
515
|
+
expect(stringUtils.isGenericLinkText('View product details')).toBe(false);
|
|
516
|
+
expect(stringUtils.isGenericLinkText('Download annual report')).toBe(false);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should return false for null/undefined', () => {
|
|
520
|
+
expect(stringUtils.isGenericLinkText(null)).toBe(false);
|
|
521
|
+
expect(stringUtils.isGenericLinkText(undefined)).toBe(false);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should support custom generic list', () => {
|
|
525
|
+
const custom = ['foo', 'bar'];
|
|
526
|
+
expect(stringUtils.isGenericLinkText('foo', custom)).toBe(true);
|
|
527
|
+
expect(stringUtils.isGenericLinkText('bar', custom)).toBe(true);
|
|
528
|
+
expect(stringUtils.isGenericLinkText('click here', custom)).toBe(false);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('should trim whitespace', () => {
|
|
532
|
+
expect(stringUtils.isGenericLinkText(' here ')).toBe(true);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe('getActualVisibleText', () => {
|
|
537
|
+
it('should return empty string for null/undefined', () => {
|
|
538
|
+
expect(stringUtils.getActualVisibleText(null)).toBe('');
|
|
539
|
+
expect(stringUtils.getActualVisibleText(undefined)).toBe('');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('should return text content of element', () => {
|
|
543
|
+
const el = document.createElement('span');
|
|
544
|
+
el.textContent = 'Hello World';
|
|
545
|
+
expect(stringUtils.getActualVisibleText(el)).toBe('Hello World');
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('should trim whitespace', () => {
|
|
549
|
+
const el = document.createElement('span');
|
|
550
|
+
el.textContent = ' Hello ';
|
|
551
|
+
expect(stringUtils.getActualVisibleText(el)).toBe('Hello');
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should return empty string for empty element', () => {
|
|
555
|
+
const el = document.createElement('div');
|
|
556
|
+
expect(stringUtils.getActualVisibleText(el)).toBe('');
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should include nested text content', () => {
|
|
560
|
+
const el = document.createElement('div');
|
|
561
|
+
el.innerHTML = '<span>Nested</span> text';
|
|
562
|
+
expect(stringUtils.getActualVisibleText(el)).toBe('Nested text');
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
describe('hasNewWindowWarning', () => {
|
|
567
|
+
it('should return true for text containing "new window"', () => {
|
|
568
|
+
expect(stringUtils.hasNewWindowWarning('Opens in a new window')).toBe(true);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should return true for text containing "new tab"', () => {
|
|
572
|
+
expect(stringUtils.hasNewWindowWarning('Opens in new tab')).toBe(true);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should return true for "opens in new" variant', () => {
|
|
576
|
+
expect(stringUtils.hasNewWindowWarning('Link opens in new browser window')).toBe(true);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should return true for "external link"', () => {
|
|
580
|
+
expect(stringUtils.hasNewWindowWarning('External link to resource')).toBe(true);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('should return true for "external site"', () => {
|
|
584
|
+
expect(stringUtils.hasNewWindowWarning('Goes to external site')).toBe(true);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('should be case-insensitive', () => {
|
|
588
|
+
expect(stringUtils.hasNewWindowWarning('Opens In A New Window')).toBe(true);
|
|
589
|
+
expect(stringUtils.hasNewWindowWarning('EXTERNAL LINK')).toBe(true);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should return false for text without warning', () => {
|
|
593
|
+
expect(stringUtils.hasNewWindowWarning('Contact us')).toBe(false);
|
|
594
|
+
expect(stringUtils.hasNewWindowWarning('Read the article')).toBe(false);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should return false for null/undefined', () => {
|
|
598
|
+
expect(stringUtils.hasNewWindowWarning(null)).toBe(false);
|
|
599
|
+
expect(stringUtils.hasNewWindowWarning(undefined)).toBe(false);
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
describe('textIncludingImgAlt', () => {
|
|
604
|
+
it('should return text content from text nodes', () => {
|
|
605
|
+
const el = document.createElement('div');
|
|
606
|
+
el.textContent = 'Hello World';
|
|
607
|
+
expect(stringUtils.textIncludingImgAlt(el).trim()).toBe('Hello World');
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('should include img alt text', () => {
|
|
611
|
+
const el = document.createElement('div');
|
|
612
|
+
el.innerHTML = 'Text <img alt="photo"> more text';
|
|
613
|
+
const result = stringUtils.textIncludingImgAlt(el);
|
|
614
|
+
expect(result).toContain('Text');
|
|
615
|
+
expect(result).toContain('photo');
|
|
616
|
+
expect(result).toContain('more text');
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('should handle img without alt', () => {
|
|
620
|
+
const el = document.createElement('div');
|
|
621
|
+
el.innerHTML = 'Text <img src="img.png"> more';
|
|
622
|
+
const result = stringUtils.textIncludingImgAlt(el);
|
|
623
|
+
expect(result).toContain('Text');
|
|
624
|
+
expect(result).toContain('more');
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('should not include aria-label text', () => {
|
|
628
|
+
const el = document.createElement('div');
|
|
629
|
+
el.innerHTML = '<button aria-label="Close">X</button>';
|
|
630
|
+
const result = stringUtils.textIncludingImgAlt(el);
|
|
631
|
+
expect(result).toContain('X');
|
|
632
|
+
expect(result).not.toContain('Close');
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('should handle empty element', () => {
|
|
636
|
+
const el = document.createElement('div');
|
|
637
|
+
expect(stringUtils.textIncludingImgAlt(el)).toBe('');
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it('should concatenate text from nested elements', () => {
|
|
641
|
+
const el = document.createElement('div');
|
|
642
|
+
el.innerHTML = '<span>First</span> <span>Second</span>';
|
|
643
|
+
const result = stringUtils.textIncludingImgAlt(el);
|
|
644
|
+
expect(result).toContain('First');
|
|
645
|
+
expect(result).toContain('Second');
|
|
646
|
+
});
|
|
647
|
+
});
|
|
426
648
|
});
|