mtrl 0.2.9 → 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/package.json +3 -1
- package/src/components/button/button.ts +34 -5
- package/src/components/navigation/index.ts +4 -1
- package/src/components/navigation/system/core.ts +302 -0
- package/src/components/navigation/system/events.ts +240 -0
- package/src/components/navigation/system/index.ts +184 -0
- package/src/components/navigation/system/mobile.ts +278 -0
- package/src/components/navigation/system/state.ts +77 -0
- package/src/components/navigation/system/types.ts +364 -0
- package/src/components/navigation/types.ts +33 -0
- package/src/components/slider/config.ts +2 -2
- package/src/components/slider/features/controller.ts +1 -25
- package/src/components/slider/features/handlers.ts +0 -1
- package/src/components/slider/features/range.ts +7 -7
- package/src/components/slider/{structure.ts → schema.ts} +2 -13
- package/src/components/slider/slider.ts +3 -2
- package/src/components/snackbar/index.ts +7 -1
- package/src/components/snackbar/types.ts +25 -0
- package/src/components/switch/api.ts +16 -0
- package/src/components/switch/config.ts +1 -18
- package/src/components/switch/features.ts +198 -0
- package/src/components/switch/index.ts +6 -1
- package/src/components/switch/switch.ts +3 -3
- package/src/components/switch/types.ts +27 -2
- package/src/components/textfield/index.ts +7 -1
- package/src/components/textfield/types.ts +36 -0
- package/src/core/composition/features/dom.ts +26 -14
- package/src/core/composition/features/icon.ts +18 -18
- package/src/core/composition/features/index.ts +3 -2
- package/src/core/composition/features/label.ts +16 -17
- package/src/core/composition/features/layout.ts +47 -0
- package/src/core/composition/index.ts +4 -4
- package/src/core/layout/README.md +350 -0
- package/src/core/layout/array.ts +181 -0
- package/src/core/layout/create.ts +55 -0
- package/src/core/layout/index.ts +26 -0
- package/src/core/layout/object.ts +124 -0
- package/src/core/layout/processor.ts +58 -0
- package/src/core/layout/result.ts +85 -0
- package/src/core/layout/types.ts +125 -0
- package/src/core/layout/utils.ts +136 -0
- package/src/styles/abstract/_variables.scss +28 -0
- package/src/styles/components/_switch.scss +133 -69
- package/src/styles/components/_textfield.scss +9 -16
- 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/navigation/system-types.ts +0 -124
- package/src/components/navigation/system.ts +0 -776
- package/src/components/snackbar/constants.ts +0 -26
- package/src/core/composition/features/structure.ts +0 -22
- package/src/core/layout/index.js +0 -95
- package/src/core/structure.ts +0 -288
- 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
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
// test/components/timepicker.test.ts
|
|
2
|
+
import { describe, test, expect, mock, beforeAll, afterAll } from 'bun:test';
|
|
3
|
+
import { JSDOM } from 'jsdom';
|
|
4
|
+
|
|
5
|
+
// Setup jsdom environment
|
|
6
|
+
let dom: JSDOM;
|
|
7
|
+
let window: Window;
|
|
8
|
+
let document: Document;
|
|
9
|
+
let originalGlobalDocument: any;
|
|
10
|
+
let originalGlobalWindow: any;
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
// Create a new JSDOM instance
|
|
14
|
+
dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
15
|
+
url: 'http://localhost/',
|
|
16
|
+
pretendToBeVisual: true
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Get window and document from jsdom
|
|
20
|
+
window = dom.window;
|
|
21
|
+
document = window.document;
|
|
22
|
+
|
|
23
|
+
// Store original globals
|
|
24
|
+
originalGlobalDocument = global.document;
|
|
25
|
+
originalGlobalWindow = global.window;
|
|
26
|
+
|
|
27
|
+
// Set globals to use jsdom
|
|
28
|
+
global.document = document;
|
|
29
|
+
global.window = window;
|
|
30
|
+
global.Element = window.Element;
|
|
31
|
+
global.HTMLElement = window.HTMLElement;
|
|
32
|
+
global.HTMLButtonElement = window.HTMLButtonElement;
|
|
33
|
+
global.Event = window.Event;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterAll(() => {
|
|
37
|
+
// Restore original globals
|
|
38
|
+
global.document = originalGlobalDocument;
|
|
39
|
+
global.window = originalGlobalWindow;
|
|
40
|
+
|
|
41
|
+
// Clean up jsdom
|
|
42
|
+
window.close();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Import only the types we need from the component
|
|
46
|
+
import {
|
|
47
|
+
TimePickerComponent,
|
|
48
|
+
TimePickerConfig,
|
|
49
|
+
TimeValue,
|
|
50
|
+
TIME_PICKER_TYPE,
|
|
51
|
+
TIME_PICKER_ORIENTATION,
|
|
52
|
+
TIME_FORMAT,
|
|
53
|
+
TIME_PERIOD
|
|
54
|
+
} from '../../src/components/timepicker/types';
|
|
55
|
+
|
|
56
|
+
// Create a mock implementation of the timepicker component
|
|
57
|
+
const createMockTimePicker = (config: TimePickerConfig = {}): TimePickerComponent => {
|
|
58
|
+
// Create the main container element
|
|
59
|
+
const element = document.createElement('div');
|
|
60
|
+
element.className = `mtrl-time-picker ${config.class || ''}`;
|
|
61
|
+
|
|
62
|
+
// Create modal element
|
|
63
|
+
const modalElement = document.createElement('div');
|
|
64
|
+
modalElement.className = 'mtrl-time-picker-modal';
|
|
65
|
+
modalElement.style.display = 'none';
|
|
66
|
+
|
|
67
|
+
// Create dialog element
|
|
68
|
+
const dialogElement = document.createElement('div');
|
|
69
|
+
dialogElement.className = [
|
|
70
|
+
'mtrl-time-picker-dialog',
|
|
71
|
+
`mtrl-time-picker-dialog--${config.type || TIME_PICKER_TYPE.DIAL}`,
|
|
72
|
+
`mtrl-time-picker-dialog--${config.orientation || TIME_PICKER_ORIENTATION.VERTICAL}`,
|
|
73
|
+
`mtrl-time-picker-dialog--${config.format || TIME_FORMAT.AMPM}`
|
|
74
|
+
].join(' ');
|
|
75
|
+
|
|
76
|
+
// Append dialog to modal
|
|
77
|
+
modalElement.appendChild(dialogElement);
|
|
78
|
+
|
|
79
|
+
// Append modal to document body
|
|
80
|
+
document.body.appendChild(modalElement);
|
|
81
|
+
|
|
82
|
+
// Parse initial time value or use current time
|
|
83
|
+
let timeValue: TimeValue;
|
|
84
|
+
if (config.value) {
|
|
85
|
+
const parts = config.value.split(':');
|
|
86
|
+
const hours = parseInt(parts[0], 10);
|
|
87
|
+
const minutes = parseInt(parts[1], 10);
|
|
88
|
+
const seconds = parts[2] ? parseInt(parts[2], 10) : 0;
|
|
89
|
+
|
|
90
|
+
timeValue = {
|
|
91
|
+
hours,
|
|
92
|
+
minutes,
|
|
93
|
+
seconds,
|
|
94
|
+
period: hours >= 12 ? TIME_PERIOD.PM : TIME_PERIOD.AM
|
|
95
|
+
};
|
|
96
|
+
} else {
|
|
97
|
+
// Default to current time
|
|
98
|
+
const now = new Date();
|
|
99
|
+
timeValue = {
|
|
100
|
+
hours: now.getHours(),
|
|
101
|
+
minutes: now.getMinutes(),
|
|
102
|
+
seconds: config.showSeconds ? now.getSeconds() : 0,
|
|
103
|
+
period: now.getHours() >= 12 ? TIME_PERIOD.PM : TIME_PERIOD.AM
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Track state
|
|
108
|
+
let isOpen = !!config.isOpen;
|
|
109
|
+
let currentType = config.type || TIME_PICKER_TYPE.DIAL;
|
|
110
|
+
let currentFormat = config.format || TIME_FORMAT.AMPM;
|
|
111
|
+
let currentOrientation = config.orientation || TIME_PICKER_ORIENTATION.VERTICAL;
|
|
112
|
+
let currentTitle = config.title || '';
|
|
113
|
+
|
|
114
|
+
// Event handlers
|
|
115
|
+
const eventHandlers: Record<string, Function[]> = {};
|
|
116
|
+
|
|
117
|
+
// Update modal visibility based on isOpen state
|
|
118
|
+
const updateVisibility = () => {
|
|
119
|
+
modalElement.style.display = isOpen ? 'block' : 'none';
|
|
120
|
+
if (isOpen) {
|
|
121
|
+
modalElement.classList.add('active');
|
|
122
|
+
dialogElement.classList.add('active');
|
|
123
|
+
element.classList.add('mtrl-time-picker--open');
|
|
124
|
+
} else {
|
|
125
|
+
modalElement.classList.remove('active');
|
|
126
|
+
dialogElement.classList.remove('active');
|
|
127
|
+
element.classList.remove('mtrl-time-picker--open');
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Set initial visibility
|
|
132
|
+
updateVisibility();
|
|
133
|
+
|
|
134
|
+
// Format the time value as a string
|
|
135
|
+
const formatTimeValue = (): string => {
|
|
136
|
+
const { hours, minutes, seconds } = timeValue;
|
|
137
|
+
const use24HourFormat = currentFormat === TIME_FORMAT.MILITARY;
|
|
138
|
+
|
|
139
|
+
// Helper function to pad numbers with leading zeros
|
|
140
|
+
const padZero = (num: number): string => {
|
|
141
|
+
return num.toString().padStart(2, '0');
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (use24HourFormat) {
|
|
145
|
+
if (config.showSeconds) {
|
|
146
|
+
return `${padZero(hours)}:${padZero(minutes)}:${padZero(seconds || 0)}`;
|
|
147
|
+
}
|
|
148
|
+
return `${padZero(hours)}:${padZero(minutes)}`;
|
|
149
|
+
} else {
|
|
150
|
+
// Convert to 12-hour format for display
|
|
151
|
+
let displayHours = hours % 12;
|
|
152
|
+
if (displayHours === 0) {
|
|
153
|
+
displayHours = 12;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (config.showSeconds) {
|
|
157
|
+
return `${padZero(displayHours)}:${padZero(minutes)}:${padZero(seconds || 0)} ${timeValue.period}`;
|
|
158
|
+
}
|
|
159
|
+
return `${padZero(displayHours)}:${padZero(minutes)} ${timeValue.period}`;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Emit event helper
|
|
164
|
+
const emitEvent = (eventName: string, data?: any) => {
|
|
165
|
+
if (eventHandlers[eventName]) {
|
|
166
|
+
eventHandlers[eventName].forEach(handler => {
|
|
167
|
+
handler(data);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Call appropriate callback if provided
|
|
172
|
+
switch (eventName) {
|
|
173
|
+
case 'change':
|
|
174
|
+
if (config.onChange) config.onChange(formatTimeValue());
|
|
175
|
+
break;
|
|
176
|
+
case 'open':
|
|
177
|
+
if (config.onOpen) config.onOpen();
|
|
178
|
+
break;
|
|
179
|
+
case 'close':
|
|
180
|
+
if (config.onClose) config.onClose();
|
|
181
|
+
break;
|
|
182
|
+
case 'confirm':
|
|
183
|
+
if (config.onConfirm) config.onConfirm(formatTimeValue());
|
|
184
|
+
break;
|
|
185
|
+
case 'cancel':
|
|
186
|
+
if (config.onCancel) config.onCancel();
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const timePickerObj = {
|
|
192
|
+
element,
|
|
193
|
+
modalElement,
|
|
194
|
+
dialogElement,
|
|
195
|
+
isOpen,
|
|
196
|
+
|
|
197
|
+
open() {
|
|
198
|
+
if (isOpen) return this;
|
|
199
|
+
|
|
200
|
+
isOpen = true;
|
|
201
|
+
this.isOpen = true; // Update the public property too
|
|
202
|
+
updateVisibility();
|
|
203
|
+
emitEvent('open');
|
|
204
|
+
|
|
205
|
+
return this;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
close() {
|
|
209
|
+
if (!isOpen) return this;
|
|
210
|
+
|
|
211
|
+
isOpen = false;
|
|
212
|
+
this.isOpen = false; // Update the public property too
|
|
213
|
+
updateVisibility();
|
|
214
|
+
emitEvent('close');
|
|
215
|
+
|
|
216
|
+
return this;
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
toggle() {
|
|
220
|
+
return isOpen ? this.close() : this.open();
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
getValue() {
|
|
224
|
+
return formatTimeValue();
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
getTimeObject() {
|
|
228
|
+
return { ...timeValue };
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
setValue(time: string) {
|
|
232
|
+
try {
|
|
233
|
+
const parts = time.split(':');
|
|
234
|
+
const hours = parseInt(parts[0], 10);
|
|
235
|
+
const minutes = parseInt(parts[1], 10);
|
|
236
|
+
const seconds = parts[2] ? parseInt(parts[2], 10) : 0;
|
|
237
|
+
|
|
238
|
+
if (
|
|
239
|
+
isNaN(hours) || hours < 0 || hours > 23 ||
|
|
240
|
+
isNaN(minutes) || minutes < 0 || minutes > 59 ||
|
|
241
|
+
isNaN(seconds) || seconds < 0 || seconds > 59
|
|
242
|
+
) {
|
|
243
|
+
throw new Error('Invalid time format');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
timeValue = {
|
|
247
|
+
hours,
|
|
248
|
+
minutes,
|
|
249
|
+
seconds,
|
|
250
|
+
period: hours >= 12 ? TIME_PERIOD.PM : TIME_PERIOD.AM
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
emitEvent('change', formatTimeValue());
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error('Error setting time value:', error);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return this;
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
setType(type: TIME_PICKER_TYPE) {
|
|
262
|
+
if (currentType === type) return this;
|
|
263
|
+
|
|
264
|
+
// Update class
|
|
265
|
+
dialogElement.classList.remove(`mtrl-time-picker-dialog--${currentType}`);
|
|
266
|
+
dialogElement.classList.add(`mtrl-time-picker-dialog--${type}`);
|
|
267
|
+
|
|
268
|
+
currentType = type;
|
|
269
|
+
return this;
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
getType() {
|
|
273
|
+
return currentType;
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
setFormat(format: TIME_FORMAT) {
|
|
277
|
+
if (currentFormat === format) return this;
|
|
278
|
+
|
|
279
|
+
// Update class
|
|
280
|
+
dialogElement.classList.remove(`mtrl-time-picker-dialog--${currentFormat}`);
|
|
281
|
+
dialogElement.classList.add(`mtrl-time-picker-dialog--${format}`);
|
|
282
|
+
|
|
283
|
+
currentFormat = format;
|
|
284
|
+
emitEvent('change', formatTimeValue());
|
|
285
|
+
|
|
286
|
+
return this;
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
getFormat() {
|
|
290
|
+
return currentFormat;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
setOrientation(orientation: TIME_PICKER_ORIENTATION) {
|
|
294
|
+
if (currentOrientation === orientation) return this;
|
|
295
|
+
|
|
296
|
+
// Update class
|
|
297
|
+
dialogElement.classList.remove(`mtrl-time-picker-dialog--${currentOrientation}`);
|
|
298
|
+
dialogElement.classList.add(`mtrl-time-picker-dialog--${orientation}`);
|
|
299
|
+
|
|
300
|
+
currentOrientation = orientation;
|
|
301
|
+
return this;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
getOrientation() {
|
|
305
|
+
return currentOrientation;
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
setTitle(title: string) {
|
|
309
|
+
currentTitle = title;
|
|
310
|
+
|
|
311
|
+
// If there's a title element, update it
|
|
312
|
+
const titleElement = dialogElement.querySelector('.mtrl-time-picker-title');
|
|
313
|
+
if (titleElement) {
|
|
314
|
+
titleElement.textContent = title;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return this;
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
getTitle() {
|
|
321
|
+
return currentTitle;
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
destroy() {
|
|
325
|
+
// Close if open
|
|
326
|
+
if (isOpen) {
|
|
327
|
+
this.close();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Remove from DOM
|
|
331
|
+
if (modalElement.parentNode) {
|
|
332
|
+
modalElement.parentNode.removeChild(modalElement);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Clean up event handlers
|
|
336
|
+
Object.keys(eventHandlers).forEach(event => {
|
|
337
|
+
eventHandlers[event] = [];
|
|
338
|
+
});
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
on(event: string, handler: Function) {
|
|
342
|
+
if (!eventHandlers[event]) {
|
|
343
|
+
eventHandlers[event] = [];
|
|
344
|
+
}
|
|
345
|
+
eventHandlers[event].push(handler);
|
|
346
|
+
return this;
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
off(event: string, handler: Function) {
|
|
350
|
+
if (eventHandlers[event]) {
|
|
351
|
+
eventHandlers[event] = eventHandlers[event].filter(h => h !== handler);
|
|
352
|
+
}
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
return timePickerObj;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
describe('TimePicker Component', () => {
|
|
361
|
+
test('should create a time picker component', () => {
|
|
362
|
+
const timePicker = createMockTimePicker();
|
|
363
|
+
|
|
364
|
+
expect(timePicker.element).toBeDefined();
|
|
365
|
+
expect(timePicker.modalElement).toBeDefined();
|
|
366
|
+
expect(timePicker.dialogElement).toBeDefined();
|
|
367
|
+
expect(timePicker.isOpen).toBe(false);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('should initialize with custom time value', () => {
|
|
371
|
+
const timePicker = createMockTimePicker({
|
|
372
|
+
value: '14:30'
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(timePicker.getValue()).toBe('02:30 PM');
|
|
376
|
+
|
|
377
|
+
const timeObject = timePicker.getTimeObject();
|
|
378
|
+
expect(timeObject.hours).toBe(14);
|
|
379
|
+
expect(timeObject.minutes).toBe(30);
|
|
380
|
+
expect(timeObject.period).toBe(TIME_PERIOD.PM);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('should initialize in 24-hour format', () => {
|
|
384
|
+
const timePicker = createMockTimePicker({
|
|
385
|
+
value: '14:30',
|
|
386
|
+
format: TIME_FORMAT.MILITARY
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(timePicker.getValue()).toBe('14:30');
|
|
390
|
+
|
|
391
|
+
const timeObject = timePicker.getTimeObject();
|
|
392
|
+
expect(timeObject.hours).toBe(14);
|
|
393
|
+
expect(timeObject.minutes).toBe(30);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test('should toggle open/close state', () => {
|
|
397
|
+
const timePicker = createMockTimePicker();
|
|
398
|
+
|
|
399
|
+
// Initial state is closed
|
|
400
|
+
expect(timePicker.isOpen).toBe(false);
|
|
401
|
+
expect(timePicker.modalElement.style.display).toBe('none');
|
|
402
|
+
|
|
403
|
+
// Open the time picker
|
|
404
|
+
timePicker.open();
|
|
405
|
+
expect(timePicker.isOpen).toBe(true);
|
|
406
|
+
expect(timePicker.modalElement.style.display).toBe('block');
|
|
407
|
+
expect(timePicker.modalElement.classList.contains('active')).toBe(true);
|
|
408
|
+
|
|
409
|
+
// Close the time picker
|
|
410
|
+
timePicker.close();
|
|
411
|
+
expect(timePicker.isOpen).toBe(false);
|
|
412
|
+
expect(timePicker.modalElement.classList.contains('active')).toBe(false);
|
|
413
|
+
|
|
414
|
+
// Toggle should open it again
|
|
415
|
+
timePicker.toggle();
|
|
416
|
+
expect(timePicker.isOpen).toBe(true);
|
|
417
|
+
|
|
418
|
+
// Toggle again should close it
|
|
419
|
+
timePicker.toggle();
|
|
420
|
+
expect(timePicker.isOpen).toBe(false);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('should set and get time values', () => {
|
|
424
|
+
const timePicker = createMockTimePicker();
|
|
425
|
+
|
|
426
|
+
// Set a new time
|
|
427
|
+
timePicker.setValue('09:45');
|
|
428
|
+
expect(timePicker.getValue()).toBe('09:45 AM');
|
|
429
|
+
|
|
430
|
+
const timeObject = timePicker.getTimeObject();
|
|
431
|
+
expect(timeObject.hours).toBe(9);
|
|
432
|
+
expect(timeObject.minutes).toBe(45);
|
|
433
|
+
expect(timeObject.period).toBe(TIME_PERIOD.AM);
|
|
434
|
+
|
|
435
|
+
// Set an afternoon time
|
|
436
|
+
timePicker.setValue('16:20');
|
|
437
|
+
expect(timePicker.getValue()).toBe('04:20 PM');
|
|
438
|
+
|
|
439
|
+
const pmTimeObject = timePicker.getTimeObject();
|
|
440
|
+
expect(pmTimeObject.hours).toBe(16);
|
|
441
|
+
expect(pmTimeObject.minutes).toBe(20);
|
|
442
|
+
expect(pmTimeObject.period).toBe(TIME_PERIOD.PM);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test('should change time picker type', () => {
|
|
446
|
+
const timePicker = createMockTimePicker({
|
|
447
|
+
type: TIME_PICKER_TYPE.DIAL
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
expect(timePicker.getType()).toBe(TIME_PICKER_TYPE.DIAL);
|
|
451
|
+
expect(timePicker.dialogElement.classList.contains('mtrl-time-picker-dialog--dial')).toBe(true);
|
|
452
|
+
|
|
453
|
+
// Change to INPUT type
|
|
454
|
+
timePicker.setType(TIME_PICKER_TYPE.INPUT);
|
|
455
|
+
expect(timePicker.getType()).toBe(TIME_PICKER_TYPE.INPUT);
|
|
456
|
+
expect(timePicker.dialogElement.classList.contains('mtrl-time-picker-dialog--input')).toBe(true);
|
|
457
|
+
expect(timePicker.dialogElement.classList.contains('mtrl-time-picker-dialog--dial')).toBe(false);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('should change time format', () => {
|
|
461
|
+
const timePicker = createMockTimePicker({
|
|
462
|
+
value: '14:30',
|
|
463
|
+
format: TIME_FORMAT.AMPM
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
expect(timePicker.getFormat()).toBe(TIME_FORMAT.AMPM);
|
|
467
|
+
expect(timePicker.getValue()).toBe('02:30 PM');
|
|
468
|
+
|
|
469
|
+
// Change to 24-hour format
|
|
470
|
+
timePicker.setFormat(TIME_FORMAT.MILITARY);
|
|
471
|
+
expect(timePicker.getFormat()).toBe(TIME_FORMAT.MILITARY);
|
|
472
|
+
expect(timePicker.getValue()).toBe('14:30');
|
|
473
|
+
expect(timePicker.dialogElement.classList.contains('mtrl-time-picker-dialog--24h')).toBe(true);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('should change orientation', () => {
|
|
477
|
+
const timePicker = createMockTimePicker({
|
|
478
|
+
orientation: TIME_PICKER_ORIENTATION.VERTICAL
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
expect(timePicker.getOrientation()).toBe(TIME_PICKER_ORIENTATION.VERTICAL);
|
|
482
|
+
expect(timePicker.dialogElement.classList.contains('mtrl-time-picker-dialog--vertical')).toBe(true);
|
|
483
|
+
|
|
484
|
+
// Change to horizontal orientation
|
|
485
|
+
timePicker.setOrientation(TIME_PICKER_ORIENTATION.HORIZONTAL);
|
|
486
|
+
expect(timePicker.getOrientation()).toBe(TIME_PICKER_ORIENTATION.HORIZONTAL);
|
|
487
|
+
expect(timePicker.dialogElement.classList.contains('mtrl-time-picker-dialog--horizontal')).toBe(true);
|
|
488
|
+
expect(timePicker.dialogElement.classList.contains('mtrl-time-picker-dialog--vertical')).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test('should set and get title', () => {
|
|
492
|
+
const initialTitle = 'Select Time';
|
|
493
|
+
const timePicker = createMockTimePicker({
|
|
494
|
+
title: initialTitle
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
expect(timePicker.getTitle()).toBe(initialTitle);
|
|
498
|
+
|
|
499
|
+
// Update the title
|
|
500
|
+
const newTitle = 'Choose Departure Time';
|
|
501
|
+
timePicker.setTitle(newTitle);
|
|
502
|
+
expect(timePicker.getTitle()).toBe(newTitle);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('should handle event callbacks', () => {
|
|
506
|
+
const changeHandler = mock(() => {});
|
|
507
|
+
const openHandler = mock(() => {});
|
|
508
|
+
const closeHandler = mock(() => {});
|
|
509
|
+
|
|
510
|
+
const timePicker = createMockTimePicker();
|
|
511
|
+
|
|
512
|
+
// Register event handlers
|
|
513
|
+
timePicker.on('change', changeHandler);
|
|
514
|
+
timePicker.on('open', openHandler);
|
|
515
|
+
timePicker.on('close', closeHandler);
|
|
516
|
+
|
|
517
|
+
// Trigger events
|
|
518
|
+
timePicker.open();
|
|
519
|
+
expect(openHandler).toHaveBeenCalled();
|
|
520
|
+
|
|
521
|
+
timePicker.setValue('10:15');
|
|
522
|
+
expect(changeHandler).toHaveBeenCalled();
|
|
523
|
+
|
|
524
|
+
timePicker.close();
|
|
525
|
+
expect(closeHandler).toHaveBeenCalled();
|
|
526
|
+
|
|
527
|
+
// Remove an event handler
|
|
528
|
+
timePicker.off('change', changeHandler);
|
|
529
|
+
changeHandler.mockClear();
|
|
530
|
+
|
|
531
|
+
// The handler should no longer be called
|
|
532
|
+
timePicker.setValue('11:30');
|
|
533
|
+
expect(changeHandler).not.toHaveBeenCalled();
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test('should trigger config callbacks', () => {
|
|
537
|
+
const onChangeMock = mock(() => {});
|
|
538
|
+
const onOpenMock = mock(() => {});
|
|
539
|
+
const onCloseMock = mock(() => {});
|
|
540
|
+
|
|
541
|
+
const timePicker = createMockTimePicker({
|
|
542
|
+
onChange: onChangeMock,
|
|
543
|
+
onOpen: onOpenMock,
|
|
544
|
+
onClose: onCloseMock
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Trigger events
|
|
548
|
+
timePicker.open();
|
|
549
|
+
expect(onOpenMock).toHaveBeenCalled();
|
|
550
|
+
|
|
551
|
+
timePicker.setValue('09:30');
|
|
552
|
+
expect(onChangeMock).toHaveBeenCalledWith('09:30 AM');
|
|
553
|
+
|
|
554
|
+
timePicker.close();
|
|
555
|
+
expect(onCloseMock).toHaveBeenCalled();
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test('should handle time with seconds when showSeconds is true', () => {
|
|
559
|
+
const timePicker = createMockTimePicker({
|
|
560
|
+
value: '14:30:45',
|
|
561
|
+
showSeconds: true
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
expect(timePicker.getValue()).toBe('02:30:45 PM');
|
|
565
|
+
|
|
566
|
+
const timeObject = timePicker.getTimeObject();
|
|
567
|
+
expect(timeObject.hours).toBe(14);
|
|
568
|
+
expect(timeObject.minutes).toBe(30);
|
|
569
|
+
expect(timeObject.seconds).toBe(45);
|
|
570
|
+
|
|
571
|
+
// Update time with seconds
|
|
572
|
+
timePicker.setValue('08:15:30');
|
|
573
|
+
expect(timePicker.getValue()).toBe('08:15:30 AM');
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test('should cleanup properly when destroyed', () => {
|
|
577
|
+
const timePicker = createMockTimePicker();
|
|
578
|
+
const modalElement = timePicker.modalElement;
|
|
579
|
+
|
|
580
|
+
// Add to document
|
|
581
|
+
document.body.appendChild(timePicker.element);
|
|
582
|
+
|
|
583
|
+
// Verify modal is in the document
|
|
584
|
+
expect(document.body.contains(modalElement)).toBe(true);
|
|
585
|
+
|
|
586
|
+
// Destroy the component
|
|
587
|
+
timePicker.destroy();
|
|
588
|
+
|
|
589
|
+
// Verify modal was removed
|
|
590
|
+
expect(document.body.contains(modalElement)).toBe(false);
|
|
591
|
+
});
|
|
592
|
+
});
|