@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.
@@ -0,0 +1,389 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import formUtils from '../src/formUtils.js';
3
+
4
+ describe('formUtils', () => {
5
+ beforeEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ describe('isLabellable', () => {
10
+ it('should return false for null/undefined', () => {
11
+ expect(formUtils.isLabellable(null)).toBe(false);
12
+ expect(formUtils.isLabellable(undefined)).toBe(false);
13
+ });
14
+
15
+ it('should return true for labellable form controls', () => {
16
+ const tags = ['input', 'select', 'textarea', 'button', 'meter', 'output', 'progress'];
17
+ tags.forEach(tag => {
18
+ const el = document.createElement(tag);
19
+ expect(formUtils.isLabellable(el)).toBe(true);
20
+ });
21
+ });
22
+
23
+ it('should return false for non-labellable elements', () => {
24
+ const tags = ['div', 'span', 'p', 'a', 'section', 'label', 'fieldset', 'form'];
25
+ tags.forEach(tag => {
26
+ const el = document.createElement(tag);
27
+ expect(formUtils.isLabellable(el)).toBe(false);
28
+ });
29
+ });
30
+ });
31
+
32
+ describe('isHiddenInput', () => {
33
+ it('should return false for null/undefined', () => {
34
+ expect(formUtils.isHiddenInput(null)).toBe(false);
35
+ expect(formUtils.isHiddenInput(undefined)).toBe(false);
36
+ });
37
+
38
+ it('should return true for input type="hidden"', () => {
39
+ const el = document.createElement('input');
40
+ el.setAttribute('type', 'hidden');
41
+ expect(formUtils.isHiddenInput(el)).toBe(true);
42
+ });
43
+
44
+ it('should be case-insensitive for type attribute', () => {
45
+ const el = document.createElement('input');
46
+ el.setAttribute('type', 'HIDDEN');
47
+ expect(formUtils.isHiddenInput(el)).toBe(true);
48
+ });
49
+
50
+ it('should return false for other input types', () => {
51
+ const el = document.createElement('input');
52
+ el.setAttribute('type', 'text');
53
+ expect(formUtils.isHiddenInput(el)).toBe(false);
54
+ });
55
+
56
+ it('should return false for non-input elements', () => {
57
+ const el = document.createElement('div');
58
+ expect(formUtils.isHiddenInput(el)).toBe(false);
59
+ });
60
+
61
+ it('should return false for input without type attribute', () => {
62
+ const el = document.createElement('input');
63
+ expect(formUtils.isHiddenInput(el)).toBe(false);
64
+ });
65
+ });
66
+
67
+ describe('hasExplicitAccessibleName', () => {
68
+ it('should return true when aria-labelledby points to element with text', () => {
69
+ document.body.innerHTML = `
70
+ <span id="lbl">Group Label</span>
71
+ <div id="target" aria-labelledby="lbl"></div>
72
+ `;
73
+ const target = document.getElementById('target');
74
+ expect(formUtils.hasExplicitAccessibleName(target)).toBe(true);
75
+ });
76
+
77
+ it('should return false when aria-labelledby points to empty element', () => {
78
+ document.body.innerHTML = `
79
+ <span id="lbl"> </span>
80
+ <div id="target" aria-labelledby="lbl"></div>
81
+ `;
82
+ const target = document.getElementById('target');
83
+ expect(formUtils.hasExplicitAccessibleName(target)).toBe(false);
84
+ });
85
+
86
+ it('should return false when aria-labelledby points to non-existent id', () => {
87
+ document.body.innerHTML = `
88
+ <div id="target" aria-labelledby="nonexistent"></div>
89
+ `;
90
+ const target = document.getElementById('target');
91
+ expect(formUtils.hasExplicitAccessibleName(target)).toBe(false);
92
+ });
93
+
94
+ it('should return true when aria-label has non-empty value', () => {
95
+ const el = document.createElement('div');
96
+ el.setAttribute('aria-label', 'My label');
97
+ expect(formUtils.hasExplicitAccessibleName(el)).toBe(true);
98
+ });
99
+
100
+ it('should return false when aria-label is empty', () => {
101
+ const el = document.createElement('div');
102
+ el.setAttribute('aria-label', ' ');
103
+ expect(formUtils.hasExplicitAccessibleName(el)).toBe(false);
104
+ });
105
+
106
+ it('should return true when title has non-empty value', () => {
107
+ const el = document.createElement('div');
108
+ el.setAttribute('title', 'Tooltip text');
109
+ expect(formUtils.hasExplicitAccessibleName(el)).toBe(true);
110
+ });
111
+
112
+ it('should return false when title is empty', () => {
113
+ const el = document.createElement('div');
114
+ el.setAttribute('title', ' ');
115
+ expect(formUtils.hasExplicitAccessibleName(el)).toBe(false);
116
+ });
117
+
118
+ it('should return false when element has no naming attributes', () => {
119
+ const el = document.createElement('div');
120
+ expect(formUtils.hasExplicitAccessibleName(el)).toBe(false);
121
+ });
122
+
123
+ it('should handle multiple ids in aria-labelledby', () => {
124
+ document.body.innerHTML = `
125
+ <span id="lbl1"></span>
126
+ <span id="lbl2">Second label</span>
127
+ <div id="target" aria-labelledby="lbl1 lbl2"></div>
128
+ `;
129
+ const target = document.getElementById('target');
130
+ expect(formUtils.hasExplicitAccessibleName(target)).toBe(true);
131
+ });
132
+ });
133
+
134
+ describe('isProperlyGrouped', () => {
135
+ it('should return true when in fieldset with legend', () => {
136
+ document.body.innerHTML = `
137
+ <fieldset>
138
+ <legend>Color options</legend>
139
+ <input type="radio" id="r1" name="color" value="red">
140
+ </fieldset>
141
+ `;
142
+ const control = document.getElementById('r1');
143
+ expect(formUtils.isProperlyGrouped(control)).toBe(true);
144
+ });
145
+
146
+ it('should return false when in fieldset without legend', () => {
147
+ document.body.innerHTML = `
148
+ <fieldset>
149
+ <input type="radio" id="r1" name="color" value="red">
150
+ </fieldset>
151
+ `;
152
+ const control = document.getElementById('r1');
153
+ expect(formUtils.isProperlyGrouped(control)).toBe(false);
154
+ });
155
+
156
+ it('should return false when in fieldset with empty legend', () => {
157
+ document.body.innerHTML = `
158
+ <fieldset>
159
+ <legend> </legend>
160
+ <input type="radio" id="r1" name="color" value="red">
161
+ </fieldset>
162
+ `;
163
+ const control = document.getElementById('r1');
164
+ expect(formUtils.isProperlyGrouped(control)).toBe(false);
165
+ });
166
+
167
+ it('should return true when in role="radiogroup" with aria-label', () => {
168
+ document.body.innerHTML = `
169
+ <div role="radiogroup" aria-label="Color selection">
170
+ <input type="radio" id="r1" name="color" value="red">
171
+ </div>
172
+ `;
173
+ const control = document.getElementById('r1');
174
+ expect(formUtils.isProperlyGrouped(control)).toBe(true);
175
+ });
176
+
177
+ it('should return false when in role="radiogroup" without label', () => {
178
+ document.body.innerHTML = `
179
+ <div role="radiogroup">
180
+ <input type="radio" id="r1" name="color" value="red">
181
+ </div>
182
+ `;
183
+ const control = document.getElementById('r1');
184
+ expect(formUtils.isProperlyGrouped(control)).toBe(false);
185
+ });
186
+
187
+ it('should support custom group role', () => {
188
+ document.body.innerHTML = `
189
+ <div role="group" aria-label="Options">
190
+ <input type="checkbox" id="c1">
191
+ </div>
192
+ `;
193
+ const control = document.getElementById('c1');
194
+ expect(formUtils.isProperlyGrouped(control, 'group')).toBe(true);
195
+ });
196
+
197
+ it('should return false when not inside any grouping', () => {
198
+ document.body.innerHTML = `
199
+ <div>
200
+ <input type="radio" id="r1" name="color" value="red">
201
+ </div>
202
+ `;
203
+ const control = document.getElementById('r1');
204
+ expect(formUtils.isProperlyGrouped(control)).toBe(false);
205
+ });
206
+ });
207
+
208
+ describe('findGroupingAncestor', () => {
209
+ it('should find fieldset ancestor', () => {
210
+ document.body.innerHTML = `
211
+ <fieldset id="fs">
212
+ <div><input type="text" id="input1"></div>
213
+ </fieldset>
214
+ `;
215
+ const input = document.getElementById('input1');
216
+ const result = formUtils.findGroupingAncestor(input);
217
+ expect(result.tagName).toBe('FIELDSET');
218
+ });
219
+
220
+ it('should find form ancestor', () => {
221
+ document.body.innerHTML = `
222
+ <form id="myform">
223
+ <div><input type="text" id="input1"></div>
224
+ </form>
225
+ `;
226
+ const input = document.getElementById('input1');
227
+ const result = formUtils.findGroupingAncestor(input);
228
+ expect(result.tagName).toBe('FORM');
229
+ });
230
+
231
+ it('should find role="radiogroup" ancestor', () => {
232
+ document.body.innerHTML = `
233
+ <div role="radiogroup" id="rg">
234
+ <input type="radio" id="r1">
235
+ </div>
236
+ `;
237
+ const input = document.getElementById('r1');
238
+ const result = formUtils.findGroupingAncestor(input);
239
+ expect(result.getAttribute('role')).toBe('radiogroup');
240
+ });
241
+
242
+ it('should find role="group" ancestor', () => {
243
+ document.body.innerHTML = `
244
+ <div role="group" id="grp">
245
+ <input type="checkbox" id="c1">
246
+ </div>
247
+ `;
248
+ const input = document.getElementById('c1');
249
+ const result = formUtils.findGroupingAncestor(input);
250
+ expect(result.getAttribute('role')).toBe('group');
251
+ });
252
+
253
+ it('should return document.body when no grouping ancestor found', () => {
254
+ document.body.innerHTML = `
255
+ <div><input type="text" id="input1"></div>
256
+ `;
257
+ const input = document.getElementById('input1');
258
+ const result = formUtils.findGroupingAncestor(input);
259
+ expect(result).toBe(document.body);
260
+ });
261
+
262
+ it('should find nearest grouping ancestor', () => {
263
+ document.body.innerHTML = `
264
+ <form>
265
+ <fieldset id="fs">
266
+ <input type="text" id="input1">
267
+ </fieldset>
268
+ </form>
269
+ `;
270
+ const input = document.getElementById('input1');
271
+ const result = formUtils.findGroupingAncestor(input);
272
+ expect(result.tagName).toBe('FIELDSET');
273
+ });
274
+ });
275
+
276
+ describe('hasAssociatedLabel', () => {
277
+ it('should return true when label[for] matches element id', () => {
278
+ document.body.innerHTML = `
279
+ <label for="name">Name</label>
280
+ <input type="text" id="name">
281
+ `;
282
+ const input = document.getElementById('name');
283
+ expect(formUtils.hasAssociatedLabel(input)).toBe(true);
284
+ });
285
+
286
+ it('should return false when label[for] matches but label is empty', () => {
287
+ document.body.innerHTML = `
288
+ <label for="name"> </label>
289
+ <input type="text" id="name">
290
+ `;
291
+ const input = document.getElementById('name');
292
+ expect(formUtils.hasAssociatedLabel(input)).toBe(false);
293
+ });
294
+
295
+ it('should return true when element is wrapped in label with text', () => {
296
+ document.body.innerHTML = `
297
+ <label>
298
+ Username
299
+ <input type="text" id="user">
300
+ </label>
301
+ `;
302
+ const input = document.getElementById('user');
303
+ expect(formUtils.hasAssociatedLabel(input)).toBe(true);
304
+ });
305
+
306
+ it('should return false when element has no label association', () => {
307
+ document.body.innerHTML = `
308
+ <input type="text" id="orphan">
309
+ `;
310
+ const input = document.getElementById('orphan');
311
+ expect(formUtils.hasAssociatedLabel(input)).toBe(false);
312
+ });
313
+
314
+ it('should return false when element has no id and no wrapping label', () => {
315
+ document.body.innerHTML = `
316
+ <div><input type="text" id="nolabel"></div>
317
+ `;
318
+ const input = document.getElementById('nolabel');
319
+ expect(formUtils.hasAssociatedLabel(input)).toBe(false);
320
+ });
321
+ });
322
+
323
+ describe('getTextContentExcludingControls', () => {
324
+ it('should return text excluding input elements', () => {
325
+ document.body.innerHTML = `
326
+ <label id="lbl"><input type="checkbox"> Remember me</label>
327
+ `;
328
+ const label = document.getElementById('lbl');
329
+ const text = formUtils.getTextContentExcludingControls(label);
330
+ expect(text.trim()).toBe('Remember me');
331
+ });
332
+
333
+ it('should exclude select elements', () => {
334
+ document.body.innerHTML = `
335
+ <div id="container">
336
+ Choose option: <select><option>A</option></select> then continue
337
+ </div>
338
+ `;
339
+ const container = document.getElementById('container');
340
+ const text = formUtils.getTextContentExcludingControls(container);
341
+ expect(text).toContain('Choose option:');
342
+ expect(text).toContain('then continue');
343
+ expect(text).not.toContain('A');
344
+ });
345
+
346
+ it('should exclude textarea elements', () => {
347
+ document.body.innerHTML = `
348
+ <div id="container">
349
+ Enter text: <textarea>existing text</textarea>
350
+ </div>
351
+ `;
352
+ const container = document.getElementById('container');
353
+ const text = formUtils.getTextContentExcludingControls(container);
354
+ expect(text).toContain('Enter text:');
355
+ });
356
+
357
+ it('should exclude button elements', () => {
358
+ document.body.innerHTML = `
359
+ <div id="container">
360
+ Click <button>Submit</button> to continue
361
+ </div>
362
+ `;
363
+ const container = document.getElementById('container');
364
+ const text = formUtils.getTextContentExcludingControls(container);
365
+ expect(text).toContain('Click');
366
+ expect(text).toContain('to continue');
367
+ expect(text).not.toContain('Submit');
368
+ });
369
+
370
+ it('should include text from non-control child elements', () => {
371
+ document.body.innerHTML = `
372
+ <label id="lbl"><input type="checkbox"> <span>Accept</span> terms</label>
373
+ `;
374
+ const label = document.getElementById('lbl');
375
+ const text = formUtils.getTextContentExcludingControls(label);
376
+ expect(text).toContain('Accept');
377
+ expect(text).toContain('terms');
378
+ });
379
+
380
+ it('should return empty string when element has only controls', () => {
381
+ document.body.innerHTML = `
382
+ <div id="container"><input type="text"><button>Go</button></div>
383
+ `;
384
+ const container = document.getElementById('container');
385
+ const text = formUtils.getTextContentExcludingControls(container);
386
+ expect(text.trim()).toBe('');
387
+ });
388
+ });
389
+ });