mtrl 0.3.0 → 0.3.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.
Files changed (60) hide show
  1. package/CLAUDE.md +33 -0
  2. package/index.ts +0 -2
  3. package/package.json +3 -1
  4. package/src/components/navigation/index.ts +4 -1
  5. package/src/components/navigation/types.ts +33 -0
  6. package/src/components/snackbar/index.ts +7 -1
  7. package/src/components/snackbar/types.ts +25 -0
  8. package/src/components/switch/index.ts +5 -1
  9. package/src/components/switch/types.ts +13 -0
  10. package/src/components/textfield/index.ts +7 -1
  11. package/src/components/textfield/types.ts +36 -0
  12. package/test/components/badge.test.ts +545 -0
  13. package/test/components/bottom-app-bar.test.ts +303 -0
  14. package/test/components/button.test.ts +233 -0
  15. package/test/components/card.test.ts +560 -0
  16. package/test/components/carousel.test.ts +951 -0
  17. package/test/components/checkbox.test.ts +462 -0
  18. package/test/components/chip.test.ts +692 -0
  19. package/test/components/datepicker.test.ts +1124 -0
  20. package/test/components/dialog.test.ts +990 -0
  21. package/test/components/divider.test.ts +412 -0
  22. package/test/components/extended-fab.test.ts +672 -0
  23. package/test/components/fab.test.ts +561 -0
  24. package/test/components/list.test.ts +365 -0
  25. package/test/components/menu.test.ts +718 -0
  26. package/test/components/navigation.test.ts +186 -0
  27. package/test/components/progress.test.ts +567 -0
  28. package/test/components/radios.test.ts +699 -0
  29. package/test/components/search.test.ts +1135 -0
  30. package/test/components/segmented-button.test.ts +732 -0
  31. package/test/components/sheet.test.ts +641 -0
  32. package/test/components/slider.test.ts +1220 -0
  33. package/test/components/snackbar.test.ts +461 -0
  34. package/test/components/switch.test.ts +452 -0
  35. package/test/components/tabs.test.ts +1369 -0
  36. package/test/components/textfield.test.ts +400 -0
  37. package/test/components/timepicker.test.ts +592 -0
  38. package/test/components/tooltip.test.ts +630 -0
  39. package/test/components/top-app-bar.test.ts +566 -0
  40. package/test/core/dom.attributes.test.ts +148 -0
  41. package/test/core/dom.classes.test.ts +152 -0
  42. package/test/core/dom.events.test.ts +243 -0
  43. package/test/core/emitter.test.ts +141 -0
  44. package/test/core/ripple.test.ts +99 -0
  45. package/test/core/state.store.test.ts +189 -0
  46. package/test/core/utils.normalize.test.ts +61 -0
  47. package/test/core/utils.object.test.ts +120 -0
  48. package/test/setup.ts +451 -0
  49. package/tsconfig.json +2 -2
  50. package/src/components/snackbar/constants.ts +0 -26
  51. package/test/components/button.test.js +0 -170
  52. package/test/components/checkbox.test.js +0 -238
  53. package/test/components/list.test.js +0 -105
  54. package/test/components/menu.test.js +0 -385
  55. package/test/components/navigation.test.js +0 -227
  56. package/test/components/snackbar.test.js +0 -234
  57. package/test/components/switch.test.js +0 -186
  58. package/test/components/textfield.test.js +0 -314
  59. package/test/core/emitter.test.js +0 -141
  60. package/test/core/ripple.test.js +0 -66
@@ -0,0 +1,699 @@
1
+ // test/components/radios.test.ts
2
+ import { describe, test, expect } from 'bun:test';
3
+ import {
4
+ type RadiosComponent,
5
+ type RadiosConfig,
6
+ type RadioOptionConfig,
7
+ type RadioItem
8
+ } from '../../src/components/radios/types';
9
+
10
+ // Mock radios implementation
11
+ const createMockRadios = (config: RadiosConfig): RadiosComponent => {
12
+ // Create main container element
13
+ const element = document.createElement('div');
14
+ element.className = 'mtrl-radios';
15
+
16
+ if (config.class) {
17
+ const classes = config.class.split(' ');
18
+ classes.forEach(className => element.classList.add(className));
19
+ }
20
+
21
+ if (config.disabled) {
22
+ element.classList.add('mtrl-radios--disabled');
23
+ }
24
+
25
+ // Store settings
26
+ const settings = {
27
+ name: config.name,
28
+ componentName: config.componentName || 'radios',
29
+ prefix: config.prefix || 'mtrl',
30
+ ripple: config.ripple !== undefined ? config.ripple : true,
31
+ disabled: config.disabled || false,
32
+ value: config.value || ''
33
+ };
34
+
35
+ // Create radio items
36
+ const radios: RadioItem[] = [];
37
+
38
+ // Track event handlers
39
+ const eventHandlers: Record<string, Function[]> = {};
40
+
41
+ // Emit an event
42
+ const emit = (event: string, data?: any) => {
43
+ if (eventHandlers[event]) {
44
+ eventHandlers[event].forEach(handler => handler(data));
45
+ }
46
+ };
47
+
48
+ // Helper function to create a radio item
49
+ const createRadioItem = (option: RadioOptionConfig): RadioItem => {
50
+ const radioElement = document.createElement('div');
51
+ radioElement.className = 'mtrl-radio';
52
+
53
+ const input = document.createElement('input');
54
+ input.type = 'radio';
55
+ input.name = settings.name;
56
+ input.value = option.value;
57
+ input.className = 'mtrl-radio__input';
58
+ input.checked = option.value === settings.value;
59
+
60
+ if (option.disabled || settings.disabled) {
61
+ input.disabled = true;
62
+ radioElement.classList.add('mtrl-radio--disabled');
63
+ }
64
+
65
+ const label = document.createElement('label');
66
+ label.className = 'mtrl-radio__label';
67
+ label.textContent = option.label;
68
+ label.setAttribute('for', `${settings.name}-${option.value}`);
69
+
70
+ if (option.labelBefore) {
71
+ radioElement.classList.add('mtrl-radio--label-before');
72
+ radioElement.appendChild(label);
73
+ radioElement.appendChild(input);
74
+ } else {
75
+ radioElement.appendChild(input);
76
+ radioElement.appendChild(label);
77
+ }
78
+
79
+ // Create ripple element if enabled
80
+ if (settings.ripple) {
81
+ const ripple = document.createElement('span');
82
+ ripple.className = 'mtrl-radio__ripple';
83
+ radioElement.appendChild(ripple);
84
+ }
85
+
86
+ // Handle change event
87
+ const handleChange = () => {
88
+ if (input.checked) {
89
+ settings.value = option.value;
90
+
91
+ // Update checked state of all radios
92
+ radios.forEach(radio => {
93
+ radio.input.checked = radio.input.value === settings.value;
94
+ });
95
+
96
+ emit('change', { value: settings.value });
97
+ }
98
+ };
99
+
100
+ input.addEventListener('change', handleChange);
101
+
102
+ const radioItem: RadioItem = {
103
+ element: radioElement,
104
+ input,
105
+ label,
106
+ config: { ...option },
107
+ destroy: () => {
108
+ input.removeEventListener('change', handleChange);
109
+ if (radioElement.parentNode) {
110
+ radioElement.parentNode.removeChild(radioElement);
111
+ }
112
+ }
113
+ };
114
+
115
+ return radioItem;
116
+ };
117
+
118
+ // Add initial options
119
+ if (config.options) {
120
+ config.options.forEach(option => {
121
+ const radioItem = createRadioItem(option);
122
+ radios.push(radioItem);
123
+ element.appendChild(radioItem.element);
124
+ });
125
+ }
126
+
127
+ // Create the radios component
128
+ const radiosComponent: RadiosComponent = {
129
+ element,
130
+ radios,
131
+
132
+ lifecycle: {
133
+ destroy: () => {
134
+ radiosComponent.destroy();
135
+ }
136
+ },
137
+
138
+ getClass: (name: string) => {
139
+ const prefix = settings.prefix;
140
+ return name ? `${prefix}-${name}` : `${prefix}-radios`;
141
+ },
142
+
143
+ getValue: () => settings.value,
144
+
145
+ setValue: (value: string) => {
146
+ settings.value = value;
147
+
148
+ // Update radio inputs
149
+ radios.forEach(radio => {
150
+ radio.input.checked = radio.config.value === value;
151
+ });
152
+
153
+ emit('change', { value });
154
+
155
+ return radiosComponent;
156
+ },
157
+
158
+ getSelected: () => {
159
+ const selectedRadio = radios.find(radio => radio.config.value === settings.value);
160
+ return selectedRadio ? { ...selectedRadio.config } : null;
161
+ },
162
+
163
+ addOption: (option: RadioOptionConfig) => {
164
+ // Check if option with this value already exists
165
+ const existingIndex = radios.findIndex(radio => radio.config.value === option.value);
166
+
167
+ if (existingIndex === -1) {
168
+ const radioItem = createRadioItem(option);
169
+ radios.push(radioItem);
170
+ element.appendChild(radioItem.element);
171
+ }
172
+
173
+ return radiosComponent;
174
+ },
175
+
176
+ removeOption: (value: string) => {
177
+ const index = radios.findIndex(radio => radio.config.value === value);
178
+
179
+ if (index !== -1) {
180
+ const radioItem = radios[index];
181
+ radioItem.destroy();
182
+ radios.splice(index, 1);
183
+
184
+ // If we removed the selected option, clear the value
185
+ if (settings.value === value) {
186
+ settings.value = '';
187
+ }
188
+ }
189
+
190
+ return radiosComponent;
191
+ },
192
+
193
+ enable: () => {
194
+ settings.disabled = false;
195
+ element.classList.remove('mtrl-radios--disabled');
196
+
197
+ radios.forEach(radio => {
198
+ if (!radio.config.disabled) {
199
+ radio.input.disabled = false;
200
+ radio.element.classList.remove('mtrl-radio--disabled');
201
+ }
202
+ });
203
+
204
+ return radiosComponent;
205
+ },
206
+
207
+ disable: () => {
208
+ settings.disabled = true;
209
+ element.classList.add('mtrl-radios--disabled');
210
+
211
+ radios.forEach(radio => {
212
+ radio.input.disabled = true;
213
+ radio.element.classList.add('mtrl-radio--disabled');
214
+ });
215
+
216
+ return radiosComponent;
217
+ },
218
+
219
+ enableOption: (value: string) => {
220
+ const radio = radios.find(r => r.config.value === value);
221
+
222
+ if (radio) {
223
+ radio.config.disabled = false;
224
+
225
+ if (!settings.disabled) {
226
+ radio.input.disabled = false;
227
+ radio.element.classList.remove('mtrl-radio--disabled');
228
+ }
229
+ }
230
+
231
+ return radiosComponent;
232
+ },
233
+
234
+ disableOption: (value: string) => {
235
+ const radio = radios.find(r => r.config.value === value);
236
+
237
+ if (radio) {
238
+ radio.config.disabled = true;
239
+ radio.input.disabled = true;
240
+ radio.element.classList.add('mtrl-radio--disabled');
241
+ }
242
+
243
+ return radiosComponent;
244
+ },
245
+
246
+ destroy: () => {
247
+ // Clean up radio items
248
+ radios.forEach(radio => radio.destroy());
249
+ radios.length = 0;
250
+
251
+ // Remove element from DOM if it has a parent
252
+ if (element.parentNode) {
253
+ element.parentNode.removeChild(element);
254
+ }
255
+
256
+ // Clear event handlers
257
+ for (const event in eventHandlers) {
258
+ eventHandlers[event] = [];
259
+ }
260
+ },
261
+
262
+ on: (event: string, handler: Function) => {
263
+ if (!eventHandlers[event]) {
264
+ eventHandlers[event] = [];
265
+ }
266
+
267
+ eventHandlers[event].push(handler);
268
+ return radiosComponent;
269
+ },
270
+
271
+ off: (event: string, handler: Function) => {
272
+ if (eventHandlers[event]) {
273
+ eventHandlers[event] = eventHandlers[event].filter(h => h !== handler);
274
+ }
275
+
276
+ return radiosComponent;
277
+ }
278
+ };
279
+
280
+ return radiosComponent;
281
+ };
282
+
283
+ describe('Radios Component', () => {
284
+ test('should create a radios component with options', () => {
285
+ const options: RadioOptionConfig[] = [
286
+ { value: 'option1', label: 'Option 1' },
287
+ { value: 'option2', label: 'Option 2' },
288
+ { value: 'option3', label: 'Option 3' }
289
+ ];
290
+
291
+ const radios = createMockRadios({
292
+ name: 'test-radios',
293
+ options
294
+ });
295
+
296
+ expect(radios.element).toBeDefined();
297
+ expect(radios.element.tagName).toBe('DIV');
298
+ expect(radios.element.className).toContain('mtrl-radios');
299
+
300
+ expect(radios.radios.length).toBe(3);
301
+
302
+ const radioElements = radios.element.querySelectorAll('.mtrl-radio');
303
+ expect(radioElements.length).toBe(3);
304
+
305
+ const inputs = radios.element.querySelectorAll('input[type="radio"]');
306
+ expect(inputs.length).toBe(3);
307
+
308
+ inputs.forEach((input: Element, index: number) => {
309
+ expect((input as HTMLInputElement).name).toBe('test-radios');
310
+ expect((input as HTMLInputElement).value).toBe(options[index].value);
311
+
312
+ const label = input.parentElement?.querySelector('label');
313
+ expect(label?.textContent).toBe(options[index].label);
314
+ });
315
+ });
316
+
317
+ test('should set initial selected value', () => {
318
+ const options: RadioOptionConfig[] = [
319
+ { value: 'option1', label: 'Option 1' },
320
+ { value: 'option2', label: 'Option 2' },
321
+ { value: 'option3', label: 'Option 3' }
322
+ ];
323
+
324
+ const radios = createMockRadios({
325
+ name: 'test-radios',
326
+ options,
327
+ value: 'option2'
328
+ });
329
+
330
+ expect(radios.getValue()).toBe('option2');
331
+
332
+ const inputs = radios.element.querySelectorAll('input[type="radio"]');
333
+ expect((inputs[0] as HTMLInputElement).checked).toBe(false);
334
+ expect((inputs[1] as HTMLInputElement).checked).toBe(true);
335
+ expect((inputs[2] as HTMLInputElement).checked).toBe(false);
336
+ });
337
+
338
+ test('should create with label before radio when configured', () => {
339
+ const options: RadioOptionConfig[] = [
340
+ { value: 'option1', label: 'Option 1', labelBefore: true }
341
+ ];
342
+
343
+ const radios = createMockRadios({
344
+ name: 'test-radios',
345
+ options
346
+ });
347
+
348
+ const radioElement = radios.element.querySelector('.mtrl-radio');
349
+ expect(radioElement?.className).toContain('mtrl-radio--label-before');
350
+
351
+ // First child should be label when labelBefore is true
352
+ const firstChild = radioElement?.firstChild;
353
+ expect(firstChild?.nodeName).toBe('LABEL');
354
+ });
355
+
356
+ test('should disable specific options', () => {
357
+ const options: RadioOptionConfig[] = [
358
+ { value: 'option1', label: 'Option 1' },
359
+ { value: 'option2', label: 'Option 2', disabled: true },
360
+ { value: 'option3', label: 'Option 3' }
361
+ ];
362
+
363
+ const radios = createMockRadios({
364
+ name: 'test-radios',
365
+ options
366
+ });
367
+
368
+ const inputs = radios.element.querySelectorAll('input[type="radio"]');
369
+ expect((inputs[0] as HTMLInputElement).disabled).toBe(false);
370
+ expect((inputs[1] as HTMLInputElement).disabled).toBe(true);
371
+ expect((inputs[2] as HTMLInputElement).disabled).toBe(false);
372
+
373
+ const radioElements = radios.element.querySelectorAll('.mtrl-radio');
374
+ expect(radioElements[1].className).toContain('mtrl-radio--disabled');
375
+ });
376
+
377
+ test('should disable all options when component is disabled', () => {
378
+ const options: RadioOptionConfig[] = [
379
+ { value: 'option1', label: 'Option 1' },
380
+ { value: 'option2', label: 'Option 2' },
381
+ { value: 'option3', label: 'Option 3' }
382
+ ];
383
+
384
+ const radios = createMockRadios({
385
+ name: 'test-radios',
386
+ options,
387
+ disabled: true
388
+ });
389
+
390
+ expect(radios.element.className).toContain('mtrl-radios--disabled');
391
+
392
+ const inputs = radios.element.querySelectorAll('input[type="radio"]');
393
+ inputs.forEach(input => {
394
+ expect((input as HTMLInputElement).disabled).toBe(true);
395
+ });
396
+
397
+ const radioElements = radios.element.querySelectorAll('.mtrl-radio');
398
+ radioElements.forEach(radio => {
399
+ expect(radio.className).toContain('mtrl-radio--disabled');
400
+ });
401
+ });
402
+
403
+ test('should create ripple effect by default', () => {
404
+ const radios = createMockRadios({
405
+ name: 'test-radios',
406
+ options: [{ value: 'option1', label: 'Option 1' }]
407
+ });
408
+
409
+ const ripples = radios.element.querySelectorAll('.mtrl-radio__ripple');
410
+ expect(ripples.length).toBe(1);
411
+ });
412
+
413
+ test('should not create ripple when disabled', () => {
414
+ const radios = createMockRadios({
415
+ name: 'test-radios',
416
+ options: [{ value: 'option1', label: 'Option 1' }],
417
+ ripple: false
418
+ });
419
+
420
+ const ripples = radios.element.querySelectorAll('.mtrl-radio__ripple');
421
+ expect(ripples.length).toBe(0);
422
+ });
423
+
424
+ test('should change selected value', () => {
425
+ const options: RadioOptionConfig[] = [
426
+ { value: 'option1', label: 'Option 1' },
427
+ { value: 'option2', label: 'Option 2' },
428
+ { value: 'option3', label: 'Option 3' }
429
+ ];
430
+
431
+ const radios = createMockRadios({
432
+ name: 'test-radios',
433
+ options
434
+ });
435
+
436
+ expect(radios.getValue()).toBe('');
437
+
438
+ radios.setValue('option2');
439
+
440
+ expect(radios.getValue()).toBe('option2');
441
+
442
+ const inputs = radios.element.querySelectorAll('input[type="radio"]');
443
+ expect((inputs[0] as HTMLInputElement).checked).toBe(false);
444
+ expect((inputs[1] as HTMLInputElement).checked).toBe(true);
445
+ expect((inputs[2] as HTMLInputElement).checked).toBe(false);
446
+ });
447
+
448
+ test('should get selected option config', () => {
449
+ const options: RadioOptionConfig[] = [
450
+ { value: 'option1', label: 'Option 1' },
451
+ { value: 'option2', label: 'Option 2' },
452
+ { value: 'option3', label: 'Option 3' }
453
+ ];
454
+
455
+ const radios = createMockRadios({
456
+ name: 'test-radios',
457
+ options,
458
+ value: 'option2'
459
+ });
460
+
461
+ const selected = radios.getSelected();
462
+ expect(selected).toEqual(options[1]);
463
+ });
464
+
465
+ test('should add a new option', () => {
466
+ const options: RadioOptionConfig[] = [
467
+ { value: 'option1', label: 'Option 1' },
468
+ { value: 'option2', label: 'Option 2' }
469
+ ];
470
+
471
+ const radios = createMockRadios({
472
+ name: 'test-radios',
473
+ options
474
+ });
475
+
476
+ expect(radios.radios.length).toBe(2);
477
+
478
+ const newOption: RadioOptionConfig = {
479
+ value: 'option3',
480
+ label: 'Option 3'
481
+ };
482
+
483
+ radios.addOption(newOption);
484
+
485
+ expect(radios.radios.length).toBe(3);
486
+
487
+ const radioElements = radios.element.querySelectorAll('.mtrl-radio');
488
+ expect(radioElements.length).toBe(3);
489
+
490
+ const lastInput = radios.radios[2].input;
491
+ expect(lastInput.value).toBe('option3');
492
+
493
+ const lastLabel = radios.radios[2].label;
494
+ expect(lastLabel.textContent).toBe('Option 3');
495
+ });
496
+
497
+ test('should remove an option', () => {
498
+ const options: RadioOptionConfig[] = [
499
+ { value: 'option1', label: 'Option 1' },
500
+ { value: 'option2', label: 'Option 2' },
501
+ { value: 'option3', label: 'Option 3' }
502
+ ];
503
+
504
+ const radios = createMockRadios({
505
+ name: 'test-radios',
506
+ options
507
+ });
508
+
509
+ expect(radios.radios.length).toBe(3);
510
+
511
+ radios.removeOption('option2');
512
+
513
+ expect(radios.radios.length).toBe(2);
514
+
515
+ const radioElements = radios.element.querySelectorAll('.mtrl-radio');
516
+ expect(radioElements.length).toBe(2);
517
+
518
+ const remainingValues = radios.radios.map(radio => radio.config.value);
519
+ expect(remainingValues).toEqual(['option1', 'option3']);
520
+ });
521
+
522
+ test('should clear value when selected option is removed', () => {
523
+ const options: RadioOptionConfig[] = [
524
+ { value: 'option1', label: 'Option 1' },
525
+ { value: 'option2', label: 'Option 2' }
526
+ ];
527
+
528
+ const radios = createMockRadios({
529
+ name: 'test-radios',
530
+ options,
531
+ value: 'option2'
532
+ });
533
+
534
+ expect(radios.getValue()).toBe('option2');
535
+
536
+ radios.removeOption('option2');
537
+
538
+ expect(radios.getValue()).toBe('');
539
+ });
540
+
541
+ test('should enable and disable the component', () => {
542
+ const options: RadioOptionConfig[] = [
543
+ { value: 'option1', label: 'Option 1' },
544
+ { value: 'option2', label: 'Option 2' }
545
+ ];
546
+
547
+ const radios = createMockRadios({
548
+ name: 'test-radios',
549
+ options
550
+ });
551
+
552
+ expect(radios.element.className).not.toContain('mtrl-radios--disabled');
553
+
554
+ radios.disable();
555
+
556
+ expect(radios.element.className).toContain('mtrl-radios--disabled');
557
+
558
+ const inputs = radios.element.querySelectorAll('input[type="radio"]');
559
+ inputs.forEach(input => {
560
+ expect((input as HTMLInputElement).disabled).toBe(true);
561
+ });
562
+
563
+ radios.enable();
564
+
565
+ expect(radios.element.className).not.toContain('mtrl-radios--disabled');
566
+
567
+ inputs.forEach(input => {
568
+ expect((input as HTMLInputElement).disabled).toBe(false);
569
+ });
570
+ });
571
+
572
+ test('should enable and disable specific options', () => {
573
+ const options: RadioOptionConfig[] = [
574
+ { value: 'option1', label: 'Option 1' },
575
+ { value: 'option2', label: 'Option 2' }
576
+ ];
577
+
578
+ const radios = createMockRadios({
579
+ name: 'test-radios',
580
+ options
581
+ });
582
+
583
+ radios.disableOption('option1');
584
+
585
+ const input1 = radios.radios[0].input;
586
+ const input2 = radios.radios[1].input;
587
+
588
+ expect(input1.disabled).toBe(true);
589
+ expect(input2.disabled).toBe(false);
590
+
591
+ expect(radios.radios[0].element.className).toContain('mtrl-radio--disabled');
592
+ expect(radios.radios[1].element.className).not.toContain('mtrl-radio--disabled');
593
+
594
+ radios.enableOption('option1');
595
+
596
+ expect(input1.disabled).toBe(false);
597
+ expect(radios.radios[0].element.className).not.toContain('mtrl-radio--disabled');
598
+ });
599
+
600
+ test('should emit change events', () => {
601
+ const options: RadioOptionConfig[] = [
602
+ { value: 'option1', label: 'Option 1' },
603
+ { value: 'option2', label: 'Option 2' }
604
+ ];
605
+
606
+ const radios = createMockRadios({
607
+ name: 'test-radios',
608
+ options
609
+ });
610
+
611
+ let eventFired = false;
612
+ let eventValue = '';
613
+
614
+ radios.on('change', (data) => {
615
+ eventFired = true;
616
+ eventValue = data.value;
617
+ });
618
+
619
+ radios.setValue('option2');
620
+
621
+ expect(eventFired).toBe(true);
622
+ expect(eventValue).toBe('option2');
623
+ });
624
+
625
+ test('should handle input change events', () => {
626
+ const options: RadioOptionConfig[] = [
627
+ { value: 'option1', label: 'Option 1' },
628
+ { value: 'option2', label: 'Option 2' }
629
+ ];
630
+
631
+ const radios = createMockRadios({
632
+ name: 'test-radios',
633
+ options
634
+ });
635
+
636
+ let eventFired = false;
637
+ let eventValue = '';
638
+
639
+ radios.on('change', (data) => {
640
+ eventFired = true;
641
+ eventValue = data.value;
642
+ });
643
+
644
+ // Simulate radio button change
645
+ const input = radios.radios[1].input;
646
+ input.checked = true;
647
+ input.dispatchEvent(new Event('change'));
648
+
649
+ expect(eventFired).toBe(true);
650
+ expect(eventValue).toBe('option2');
651
+ expect(radios.getValue()).toBe('option2');
652
+ });
653
+
654
+ test('should remove event listeners', () => {
655
+ const radios = createMockRadios({
656
+ name: 'test-radios',
657
+ options: [
658
+ { value: 'option1', label: 'Option 1' },
659
+ { value: 'option2', label: 'Option 2' }
660
+ ]
661
+ });
662
+
663
+ let eventCount = 0;
664
+
665
+ const handler = () => {
666
+ eventCount++;
667
+ };
668
+
669
+ radios.on('change', handler);
670
+
671
+ radios.setValue('option1');
672
+ expect(eventCount).toBe(1);
673
+
674
+ radios.off('change', handler);
675
+
676
+ radios.setValue('option2');
677
+ expect(eventCount).toBe(1); // Count should not increase
678
+ });
679
+
680
+ test('should be properly destroyed', () => {
681
+ const radios = createMockRadios({
682
+ name: 'test-radios',
683
+ options: [
684
+ { value: 'option1', label: 'Option 1' },
685
+ { value: 'option2', label: 'Option 2' }
686
+ ]
687
+ });
688
+
689
+ document.body.appendChild(radios.element);
690
+
691
+ expect(document.body.contains(radios.element)).toBe(true);
692
+ expect(radios.radios.length).toBe(2);
693
+
694
+ radios.destroy();
695
+
696
+ expect(document.body.contains(radios.element)).toBe(false);
697
+ expect(radios.radios.length).toBe(0);
698
+ });
699
+ });