@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.
@@ -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
- 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
- // Fix getComputedStyle usage and make it work in browser
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 = getGeneratedContent(el);
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
- test.describe('getGeneratedContent', () => {
58
- test('should return ::before content when present', async ({ page }) => {
59
- const result = await page.evaluate(() => {
60
- const element = document.getElementById('with-before');
61
- return window.getGeneratedContent(element);
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
- expect(result).toContain('Before Content');
65
- });
104
+ expect(result).toContain('Before Content');
105
+ });
66
106
 
67
- test('should return ::after content when present', async ({ page }) => {
68
- const result = await page.evaluate(() => {
69
- const element = document.getElementById('with-after');
70
- return window.getGeneratedContent(element);
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
- expect(result).toContain('After Content');
74
- });
113
+ expect(result).toContain('After Content');
114
+ });
75
115
 
76
- test('should combine ::before, text content, and ::after', async ({ page }) => {
77
- const result = await page.evaluate(() => {
78
- const element = document.getElementById('with-both');
79
- return window.getGeneratedContent(element);
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
- expect(result).toContain('Before');
83
- expect(result).toContain('Text Content');
84
- expect(result).toContain('After');
85
- });
122
+ expect(result).toContain('Before');
123
+ expect(result).toContain('Text Content');
124
+ expect(result).toContain('After');
125
+ });
86
126
 
87
- test('should handle quoted content values in CSS', async ({ page }) => {
88
- const result = await page.evaluate(() => {
89
- const element = document.getElementById('with-quotes');
90
- return window.getGeneratedContent(element);
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
- expect(result).toBeTruthy();
94
- expect(result).toContain('Quoted');
95
- });
133
+ expect(result).toBeTruthy();
134
+ expect(result).toContain('Quoted');
135
+ });
96
136
 
97
- test('should handle CSS content with special characters', async ({ page }) => {
98
- const result = await page.evaluate(() => {
99
- const element = document.getElementById('with-special-chars');
100
- return window.getGeneratedContent(element);
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
- expect(result).toBeTruthy();
104
- });
143
+ expect(result).toBeTruthy();
144
+ });
105
145
 
106
- test('should trim whitespace from the combined result', async ({ page }) => {
107
- const result = await page.evaluate(() => {
108
- const element = document.getElementById('with-whitespace');
109
- return window.getGeneratedContent(element);
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
- expect(result).toBeTruthy();
113
- expect(result.startsWith(' ')).toBe(false);
114
- expect(result.endsWith(' ')).toBe(false);
115
- });
152
+ expect(result).toBeTruthy();
153
+ expect(result.startsWith(' ')).toBe(false);
154
+ expect(result.endsWith(' ')).toBe(false);
155
+ });
116
156
 
117
- test('should handle nested elements with generated content', async ({ page }) => {
118
- const result = await page.evaluate(() => {
119
- const element = document.getElementById('nested-content');
120
- return window.getGeneratedContent(element);
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
- expect(result).toBeTruthy();
124
- });
163
+ expect(result).toBeTruthy();
164
+ });
125
165
 
126
- test('should handle content with HTML entities in CSS', async ({ page }) => {
127
- const result = await page.evaluate(() => {
128
- const element = document.getElementById('with-entities');
129
- return window.getGeneratedContent(element);
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
- expect(result).toBeTruthy();
172
+ expect(result).toBeTruthy();
173
+ });
133
174
  });
134
- });
135
175
 
136
- test.describe('hasCSSGeneratedContent', () => {
137
- test('should correctly identify elements with ::before content', async ({ page }) => {
138
- const result = await page.evaluate(() => {
139
- const element = document.getElementById('with-before');
140
- return window.hasCSSGeneratedContent(element);
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
- expect(result).toBe(true);
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('should correctly identify elements with ::after content', async ({ page }) => {
147
- const result = await page.evaluate(() => {
148
- const element = document.getElementById('with-after');
149
- return window.hasCSSGeneratedContent(element);
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
- expect(result).toBe(true);
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">
@@ -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
  });