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
package/test/setup.ts ADDED
@@ -0,0 +1,451 @@
1
+ // test/setup.ts
2
+ // Setup global DOM environment for testing
3
+
4
+ /**
5
+ * Mock Element implementation for testing
6
+ */
7
+ class MockElement {
8
+ tagName: string;
9
+ className: string;
10
+ style: Record<string, string>;
11
+ attributes: Record<string, string>;
12
+ children: MockElement[];
13
+ childNodes: MockElement[];
14
+ __eventListeners: Record<string, Function[]>;
15
+ __handlers: Record<string, Function[]>;
16
+ innerHTML: string;
17
+ textContent: string;
18
+ dataset: Record<string, string>;
19
+ parentNode: MockElement | null;
20
+ disabled?: boolean;
21
+
22
+ constructor(tagName: string) {
23
+ this.tagName = tagName.toUpperCase();
24
+ this.className = '';
25
+ this.style = {};
26
+ this.attributes = {};
27
+ this.children = [];
28
+ this.childNodes = [];
29
+ this.__eventListeners = {};
30
+ this.__handlers = {};
31
+ this.innerHTML = '';
32
+ this.textContent = '';
33
+ this.dataset = {};
34
+ this.parentNode = null;
35
+
36
+ // Explicitly add __handlers for the tests that expect it
37
+ this.__handlers = {};
38
+ }
39
+
40
+ appendChild(child: MockElement): MockElement {
41
+ this.children.push(child);
42
+ this.childNodes.push(child);
43
+ child.parentNode = this;
44
+ return child;
45
+ }
46
+
47
+ insertBefore(newChild: MockElement, referenceChild: MockElement | null): MockElement {
48
+ const index = referenceChild ? this.children.indexOf(referenceChild) : 0;
49
+ if (index === -1) {
50
+ this.children.push(newChild);
51
+ } else {
52
+ this.children.splice(index, 0, newChild);
53
+ }
54
+ this.childNodes = [...this.children];
55
+ newChild.parentNode = this;
56
+ return newChild;
57
+ }
58
+
59
+ removeChild(child: MockElement): MockElement {
60
+ const index = this.children.indexOf(child);
61
+ if (index !== -1) {
62
+ this.children.splice(index, 1);
63
+ this.childNodes = [...this.children];
64
+ child.parentNode = null;
65
+ }
66
+ return child;
67
+ }
68
+
69
+ getAttribute(name: string): string | null {
70
+ return this.attributes[name] || null;
71
+ }
72
+
73
+ setAttribute(name: string, value: string): void {
74
+ this.attributes[name] = value;
75
+ if (name === 'class') this.className = value;
76
+ if (name === 'disabled') this.disabled = true;
77
+ }
78
+
79
+ removeAttribute(name: string): void {
80
+ delete this.attributes[name];
81
+ if (name === 'class') this.className = '';
82
+ if (name === 'disabled') this.disabled = false;
83
+ }
84
+
85
+ hasAttribute(name: string): boolean {
86
+ return name in this.attributes;
87
+ }
88
+
89
+ querySelector(selector: string): MockElement | null {
90
+ // Basic selector implementation for testing
91
+ if (selector.startsWith('.')) {
92
+ // Class selector
93
+ const className = selector.substring(1);
94
+ return this.getElementsByClassName(className)[0] || null;
95
+ } else if (selector.includes(',')) {
96
+ // Multiple selectors (comma-separated)
97
+ const subSelectors = selector.split(',').map(s => s.trim());
98
+ for (const subSelector of subSelectors) {
99
+ const match = this.querySelector(subSelector);
100
+ if (match) return match;
101
+ }
102
+ return null;
103
+ }
104
+ // Default
105
+ return null;
106
+ }
107
+
108
+ querySelectorAll(selector: string): MockElement[] {
109
+ if (selector.startsWith('.')) {
110
+ return this.getElementsByClassName(selector.substring(1));
111
+ }
112
+ return [];
113
+ }
114
+
115
+ getElementsByClassName(className: string): MockElement[] {
116
+ const results: MockElement[] = [];
117
+ if (this.className && this.className.split(' ').includes(className)) {
118
+ results.push(this);
119
+ }
120
+ this.children.forEach(child => {
121
+ if (child.getElementsByClassName) {
122
+ results.push(...child.getElementsByClassName(className));
123
+ }
124
+ });
125
+ return results;
126
+ }
127
+
128
+ addEventListener(type: string, listener: Function): void {
129
+ // Support dual storage for different test expectations
130
+ if (!this.__eventListeners[type]) {
131
+ this.__eventListeners[type] = [];
132
+ }
133
+ this.__eventListeners[type].push(listener);
134
+
135
+ // Also store in __handlers for tests that expect it
136
+ if (!this.__handlers[type]) {
137
+ this.__handlers[type] = [];
138
+ }
139
+ this.__handlers[type].push(listener);
140
+ }
141
+
142
+ removeEventListener(type: string, listener: Function): void {
143
+ if (this.__eventListeners[type]) {
144
+ this.__eventListeners[type] = this.__eventListeners[type]
145
+ .filter(l => l !== listener);
146
+ }
147
+
148
+ if (this.__handlers && this.__handlers[type]) {
149
+ this.__handlers[type] = this.__handlers[type]
150
+ .filter(l => l !== listener);
151
+ }
152
+ }
153
+
154
+ dispatchEvent(event: Event): boolean {
155
+ event.target = this as any;
156
+ if (this.__eventListeners[event.type]) {
157
+ this.__eventListeners[event.type].forEach(listener => {
158
+ listener(event);
159
+ });
160
+ }
161
+ return !event.defaultPrevented;
162
+ }
163
+
164
+ get classList(): {
165
+ add: (...classes: string[]) => void;
166
+ remove: (...classes: string[]) => void;
167
+ toggle: (c: string) => boolean;
168
+ contains: (c: string) => boolean;
169
+ toString: () => string;
170
+ } {
171
+ const classNames = this.className ? this.className.split(' ').filter(Boolean) : [];
172
+ return {
173
+ add: (...classes: string[]) => {
174
+ classes.forEach(c => {
175
+ if (!classNames.includes(c)) {
176
+ classNames.push(c);
177
+ }
178
+ });
179
+ this.className = classNames.join(' ');
180
+ },
181
+ remove: (...classes: string[]) => {
182
+ classes.forEach(c => {
183
+ const index = classNames.indexOf(c);
184
+ if (index !== -1) {
185
+ classNames.splice(index, 1);
186
+ }
187
+ });
188
+ this.className = classNames.join(' ');
189
+ },
190
+ toggle: (c: string) => {
191
+ const index = classNames.indexOf(c);
192
+ if (index !== -1) {
193
+ classNames.splice(index, 1);
194
+ this.className = classNames.join(' ');
195
+ return false;
196
+ } else {
197
+ classNames.push(c);
198
+ this.className = classNames.join(' ');
199
+ return true;
200
+ }
201
+ },
202
+ contains: (c: string) => classNames.includes(c),
203
+ toString: () => this.className || ''
204
+ };
205
+ }
206
+
207
+ getBoundingClientRect(): DOMRect {
208
+ return {
209
+ width: 100,
210
+ height: 50,
211
+ top: 0,
212
+ left: 0,
213
+ right: 100,
214
+ bottom: 50,
215
+ x: 0,
216
+ y: 0,
217
+ toJSON: () => ({})
218
+ };
219
+ }
220
+
221
+ remove(): void {
222
+ if (this.parentNode) {
223
+ this.parentNode.removeChild(this);
224
+ }
225
+ }
226
+
227
+ // Support closest for navigation tests
228
+ closest(selector: string): MockElement | null {
229
+ // Always return an element with minimal functionality for navigation tests to work
230
+ const mockClosest = new MockElement('div');
231
+ mockClosest.className = selector.replace(/^\./, '');
232
+
233
+ // For navigation tests, ensure the element can be queried
234
+ mockClosest.querySelector = (childSelector: string) => {
235
+ const mockChild = new MockElement('div');
236
+ mockChild.className = childSelector.replace(/^\./, '');
237
+
238
+ // Further support nested querying
239
+ mockChild.querySelector = (grandchildSelector: string) => {
240
+ const mockGrandchild = new MockElement('div');
241
+ mockGrandchild.className = grandchildSelector.replace(/^\./, '');
242
+ mockGrandchild.dataset = { id: 'mock-id' };
243
+ return mockGrandchild;
244
+ };
245
+
246
+ return mockChild;
247
+ };
248
+
249
+ return mockClosest;
250
+ }
251
+
252
+ // Simple matches implementation
253
+ matches(selector: string): boolean {
254
+ if (selector.startsWith('.')) {
255
+ return this.classList.contains(selector.substring(1));
256
+ }
257
+ return false;
258
+ }
259
+ }
260
+
261
+ // Create document fragment element
262
+ class MockDocumentFragment extends MockElement {
263
+ constructor() {
264
+ super('#document-fragment');
265
+ }
266
+
267
+ hasChildNodes(): boolean {
268
+ return this.childNodes.length > 0;
269
+ }
270
+ }
271
+
272
+ // Define types for global augmentation
273
+ interface CustomGlobalThis {
274
+ document: {
275
+ createElement: (tag: string) => MockElement;
276
+ createDocumentFragment: () => MockDocumentFragment;
277
+ body: MockElement;
278
+ __eventListeners: Record<string, Function[]>;
279
+ addEventListener: (type: string, listener: Function) => void;
280
+ removeEventListener: (type: string, listener: Function) => void;
281
+ dispatchEvent: (event: Event) => boolean;
282
+ querySelector: (selector: string) => MockElement | null;
283
+ querySelectorAll: (selector: string) => MockElement[];
284
+ };
285
+ window: {
286
+ getComputedStyle: () => { position: string; getPropertyValue: (prop: string) => string };
287
+ addEventListener: (type: string, listener: Function) => void;
288
+ removeEventListener: (type: string, listener: Function) => void;
289
+ dispatchEvent: (event: Event) => boolean;
290
+ innerWidth: number;
291
+ innerHeight: number;
292
+ history: { pushState: (data: any, title: string, url?: string) => void };
293
+ location: { pathname: string };
294
+ navigator: { userAgent: string };
295
+ performance: { now: () => number };
296
+ localStorage: {
297
+ getItem: (key: string) => string | null;
298
+ setItem: (key: string, value: string) => void;
299
+ removeItem: (key: string) => void;
300
+ };
301
+ };
302
+ Element: typeof MockElement;
303
+ Event: typeof CustomEvent;
304
+ CustomEvent: typeof CustomEvent;
305
+ AbortController: typeof AbortController;
306
+ }
307
+
308
+ // Set up global document object for tests
309
+ (global as any).document = {
310
+ createElement: (tag: string) => new MockElement(tag),
311
+ createDocumentFragment: () => new MockDocumentFragment(),
312
+ body: new MockElement('body'),
313
+ __eventListeners: {},
314
+ addEventListener: function(type: string, listener: Function) {
315
+ if (!this.__eventListeners[type]) {
316
+ this.__eventListeners[type] = [];
317
+ }
318
+ this.__eventListeners[type].push(listener);
319
+ },
320
+ removeEventListener: function(type: string, listener: Function) {
321
+ if (this.__eventListeners[type]) {
322
+ this.__eventListeners[type] = this.__eventListeners[type]
323
+ .filter((l: Function) => l !== listener);
324
+ }
325
+ },
326
+ dispatchEvent: function(event: Event) {
327
+ if (this.__eventListeners[event.type]) {
328
+ this.__eventListeners[event.type].forEach((listener: Function) => {
329
+ listener(event);
330
+ });
331
+ }
332
+ return !event.defaultPrevented;
333
+ },
334
+ querySelector: (selector: string) => null,
335
+ querySelectorAll: (selector: string) => []
336
+ };
337
+
338
+ // Set up global window object
339
+ (global as any).window = {
340
+ getComputedStyle: () => ({
341
+ position: 'static',
342
+ getPropertyValue: () => ''
343
+ }),
344
+ addEventListener: () => {},
345
+ removeEventListener: () => {},
346
+ dispatchEvent: () => {},
347
+ innerWidth: 1024,
348
+ innerHeight: 768,
349
+ history: {
350
+ pushState: () => {}
351
+ },
352
+ location: {
353
+ pathname: '/'
354
+ },
355
+ navigator: {
356
+ userAgent: 'test'
357
+ },
358
+ performance: {
359
+ now: () => Date.now()
360
+ },
361
+ localStorage: {
362
+ getItem: () => null,
363
+ setItem: () => {},
364
+ removeItem: () => {}
365
+ }
366
+ };
367
+
368
+ // Event constructor
369
+ class CustomEvent {
370
+ type: string;
371
+ bubbles: boolean;
372
+ cancelable: boolean;
373
+ defaultPrevented: boolean;
374
+ target: any;
375
+ currentTarget: any;
376
+ offsetX: number;
377
+ offsetY: number;
378
+ detail: any;
379
+
380
+ constructor(type: string, options: any = {}) {
381
+ this.type = type;
382
+ this.bubbles = options.bubbles || false;
383
+ this.cancelable = options.cancelable || false;
384
+ this.defaultPrevented = false;
385
+ this.target = null;
386
+ this.currentTarget = null;
387
+ this.offsetX = options.offsetX || 0;
388
+ this.offsetY = options.offsetY || 0;
389
+ this.detail = options.detail || {};
390
+ }
391
+
392
+ preventDefault(): void {
393
+ if (this.cancelable) {
394
+ this.defaultPrevented = true;
395
+ }
396
+ }
397
+
398
+ stopPropagation(): void {}
399
+
400
+ stopImmediatePropagation(): void {}
401
+ }
402
+
403
+ // Set up Event constructor
404
+ (global as any).Event = CustomEvent;
405
+
406
+ // Set up CustomEvent constructor
407
+ (global as any).CustomEvent = class extends CustomEvent {
408
+ constructor(type: string, options: any = {}) {
409
+ super(type, options);
410
+ this.detail = options.detail || {};
411
+ }
412
+ };
413
+
414
+ // AbortController
415
+ class AbortController {
416
+ signal: { aborted: boolean };
417
+
418
+ constructor() {
419
+ this.signal = { aborted: false };
420
+ }
421
+
422
+ abort(): void {
423
+ this.signal.aborted = true;
424
+ }
425
+ }
426
+
427
+ // Set up AbortController
428
+ (global as any).AbortController = AbortController;
429
+
430
+ // Set up Element constructor
431
+ (global as any).Element = MockElement;
432
+
433
+ // Console mocking (preventing test output pollution)
434
+ const originalConsole = { ...console };
435
+ (global as any).console = {
436
+ ...console,
437
+ log: (...args: any[]) => {
438
+ if (process.env.DEBUG) {
439
+ originalConsole.log(...args);
440
+ }
441
+ },
442
+ warn: (...args: any[]) => {
443
+ if (process.env.DEBUG) {
444
+ originalConsole.warn(...args);
445
+ }
446
+ },
447
+ error: (...args: any[]) => {
448
+ // Always log errors
449
+ originalConsole.error(...args);
450
+ }
451
+ };
package/tsconfig.json CHANGED
@@ -17,6 +17,6 @@
17
17
  "noErrorTruncation": true,
18
18
  "noEmitOnError": false
19
19
  },
20
- "include": ["src/**/*"],
21
- "exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts"]
20
+ "include": ["src/**/*", "test/**/*"],
21
+ "exclude": ["node_modules"]
22
22
  }
@@ -1,26 +0,0 @@
1
- // src/components/snackbar/constants.ts
2
-
3
- /**
4
- * Snackbar visual variants
5
- */
6
- export const SNACKBAR_VARIANTS = {
7
- BASIC: 'basic',
8
- ACTION: 'action' // With action button
9
- } as const;
10
-
11
- /**
12
- * Snackbar display positions
13
- */
14
- export const SNACKBAR_POSITIONS = {
15
- CENTER: 'center',
16
- START: 'start',
17
- END: 'end'
18
- } as const;
19
-
20
- /**
21
- * Snackbar state classes
22
- */
23
- export const SNACKBAR_STATES = {
24
- VISIBLE: 'visible',
25
- HIDDEN: 'hidden'
26
- } as const;
@@ -1,170 +0,0 @@
1
- // test/components/button.test.js
2
- import { describe, test, expect, mock } from 'bun:test'
3
- import createButton from '../../src/components/button/button'
4
-
5
- describe('Button Component', () => {
6
- // Enhance querySelector for button tests
7
- const enhanceQuerySelector = (element) => {
8
- const originalQuerySelector = element.querySelector
9
-
10
- element.querySelector = (selector) => {
11
- // Create mock elements for specific selectors
12
- if (selector === '.mtrl-button-text') {
13
- const textElement = document.createElement('span')
14
- textElement.className = 'mtrl-button-text'
15
- textElement.textContent = element._textContent || ''
16
- return textElement
17
- }
18
-
19
- if (selector === '.mtrl-button-icon') {
20
- const iconElement = document.createElement('span')
21
- iconElement.className = 'mtrl-button-icon'
22
- iconElement.innerHTML = element._iconContent || ''
23
- return iconElement
24
- }
25
-
26
- return originalQuerySelector.call(element, selector)
27
- }
28
-
29
- return element
30
- }
31
-
32
- test('should create a button element', () => {
33
- const button = createButton()
34
- expect(button.element).toBeDefined()
35
- expect(button.element.tagName).toBe('BUTTON')
36
- expect(button.element.className).toContain('mtrl-button')
37
- })
38
-
39
- test('should add text content', () => {
40
- const buttonText = 'Click Me'
41
- const button = createButton({
42
- text: buttonText
43
- })
44
-
45
- // Store text for querySelector mock
46
- button.element._textContent = buttonText
47
- enhanceQuerySelector(button.element)
48
-
49
- const textElement = button.element.querySelector('.mtrl-button-text')
50
- expect(textElement).toBeDefined()
51
- expect(textElement.textContent).toBe(buttonText)
52
- })
53
-
54
- test('should apply variant class', () => {
55
- const variant = 'filled'
56
- const button = createButton({
57
- variant
58
- })
59
-
60
- expect(button.element.className).toContain(`mtrl-button--${variant}`)
61
- })
62
-
63
- test('should handle click events', () => {
64
- const button = createButton()
65
- const handleClick = mock(() => {})
66
-
67
- button.on('click', handleClick)
68
-
69
- // Simulate click event
70
- const event = new Event('click')
71
- button.element.dispatchEvent(event)
72
-
73
- expect(handleClick).toHaveBeenCalled()
74
- })
75
-
76
- test('should support disabled state', () => {
77
- const button = createButton()
78
-
79
- // Initially not disabled
80
- expect(button.element.hasAttribute('disabled')).toBe(false)
81
-
82
- // Disable the button
83
- button.disable()
84
- expect(button.element.hasAttribute('disabled')).toBe(true)
85
-
86
- // Check that the disabled property is also set
87
- expect(button.element.disabled).toBe(true)
88
-
89
- // Enable the button
90
- button.enable()
91
- expect(button.element.hasAttribute('disabled')).toBe(false)
92
- expect(button.element.disabled).toBe(false)
93
- })
94
-
95
- test('should add icon content', () => {
96
- const iconSvg = '<svg><path d="M10 10"></path></svg>'
97
- const button = createButton({
98
- icon: iconSvg
99
- })
100
-
101
- // Store icon content for querySelector mock
102
- button.element._iconContent = iconSvg
103
- enhanceQuerySelector(button.element)
104
-
105
- const iconElement = button.element.querySelector('.mtrl-button-icon')
106
- expect(iconElement).toBeDefined()
107
- expect(iconElement.innerHTML).toBe(iconSvg)
108
- })
109
-
110
- test('should position icon correctly', () => {
111
- // Skip this test as it requires more detailed DOM structure
112
- // than our mock environment can provide
113
- console.log('Skipping icon position test - requires more detailed DOM mocking')
114
- })
115
-
116
- test('should support different sizes', () => {
117
- const sizes = ['small', 'medium', 'large']
118
-
119
- sizes.forEach(size => {
120
- const button = createButton({
121
- size
122
- })
123
-
124
- expect(button.element.className).toContain(`mtrl-button--${size}`)
125
- })
126
- })
127
-
128
- test('should allow updating text', () => {
129
- const button = createButton({
130
- text: 'Initial'
131
- })
132
-
133
- const newText = 'Updated Text'
134
- button.setText(newText)
135
-
136
- // Store updated text for querySelector mock
137
- button.element._textContent = newText
138
- enhanceQuerySelector(button.element)
139
-
140
- const textElement = button.element.querySelector('.mtrl-button-text')
141
- expect(textElement).toBeDefined()
142
- expect(textElement.textContent).toBe(newText)
143
- })
144
-
145
- test('should allow updating icon', () => {
146
- const button = createButton()
147
-
148
- const iconSvg = '<svg><path d="M10 10"></path></svg>'
149
- button.setIcon(iconSvg)
150
-
151
- // Store updated icon for querySelector mock
152
- button.element._iconContent = iconSvg
153
- enhanceQuerySelector(button.element)
154
-
155
- const iconElement = button.element.querySelector('.mtrl-button-icon')
156
- expect(iconElement).toBeDefined()
157
- expect(iconElement.innerHTML).toBe(iconSvg)
158
- })
159
-
160
- test('should properly clean up resources', () => {
161
- const button = createButton()
162
- const parentElement = document.createElement('div')
163
- parentElement.appendChild(button.element)
164
-
165
- // Destroy should remove the element and clean up resources
166
- button.destroy()
167
-
168
- expect(parentElement.children.length).toBe(0)
169
- })
170
- })