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.
- package/CLAUDE.md +33 -0
- package/index.ts +0 -2
- package/package.json +3 -1
- package/src/components/navigation/index.ts +4 -1
- package/src/components/navigation/types.ts +33 -0
- package/src/components/snackbar/index.ts +7 -1
- package/src/components/snackbar/types.ts +25 -0
- package/src/components/switch/index.ts +5 -1
- package/src/components/switch/types.ts +13 -0
- package/src/components/textfield/index.ts +7 -1
- package/src/components/textfield/types.ts +36 -0
- package/test/components/badge.test.ts +545 -0
- package/test/components/bottom-app-bar.test.ts +303 -0
- package/test/components/button.test.ts +233 -0
- package/test/components/card.test.ts +560 -0
- package/test/components/carousel.test.ts +951 -0
- package/test/components/checkbox.test.ts +462 -0
- package/test/components/chip.test.ts +692 -0
- package/test/components/datepicker.test.ts +1124 -0
- package/test/components/dialog.test.ts +990 -0
- package/test/components/divider.test.ts +412 -0
- package/test/components/extended-fab.test.ts +672 -0
- package/test/components/fab.test.ts +561 -0
- package/test/components/list.test.ts +365 -0
- package/test/components/menu.test.ts +718 -0
- package/test/components/navigation.test.ts +186 -0
- package/test/components/progress.test.ts +567 -0
- package/test/components/radios.test.ts +699 -0
- package/test/components/search.test.ts +1135 -0
- package/test/components/segmented-button.test.ts +732 -0
- package/test/components/sheet.test.ts +641 -0
- package/test/components/slider.test.ts +1220 -0
- package/test/components/snackbar.test.ts +461 -0
- package/test/components/switch.test.ts +452 -0
- package/test/components/tabs.test.ts +1369 -0
- package/test/components/textfield.test.ts +400 -0
- package/test/components/timepicker.test.ts +592 -0
- package/test/components/tooltip.test.ts +630 -0
- package/test/components/top-app-bar.test.ts +566 -0
- package/test/core/dom.attributes.test.ts +148 -0
- package/test/core/dom.classes.test.ts +152 -0
- package/test/core/dom.events.test.ts +243 -0
- package/test/core/emitter.test.ts +141 -0
- package/test/core/ripple.test.ts +99 -0
- package/test/core/state.store.test.ts +189 -0
- package/test/core/utils.normalize.test.ts +61 -0
- package/test/core/utils.object.test.ts +120 -0
- package/test/setup.ts +451 -0
- package/tsconfig.json +2 -2
- package/src/components/snackbar/constants.ts +0 -26
- package/test/components/button.test.js +0 -170
- package/test/components/checkbox.test.js +0 -238
- package/test/components/list.test.js +0 -105
- package/test/components/menu.test.js +0 -385
- package/test/components/navigation.test.js +0 -227
- package/test/components/snackbar.test.js +0 -234
- package/test/components/switch.test.js +0 -186
- package/test/components/textfield.test.js +0 -314
- package/test/core/emitter.test.js +0 -141
- 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
|
@@ -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
|
-
})
|