mtrl 0.3.1 → 0.3.2
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/.env +15 -0
- package/CONTRIBUTING.md +8 -8
- package/DOCS.md +3 -3
- package/README.md +43 -20
- package/TESTING.md +128 -18
- package/dist/index.js +14865 -0
- package/git-user-stats.js +545 -0
- package/index.ts +9 -67
- package/package.json +8 -3
- package/src/components/badge/api.ts +15 -1
- package/src/components/badge/badge.ts +43 -4
- package/src/components/badge/config.ts +40 -8
- package/src/components/badge/index.ts +64 -3
- package/src/components/badge/types.ts +175 -33
- package/src/components/button/api.ts +63 -1
- package/src/components/button/button.ts +39 -3
- package/src/components/button/config.ts +21 -4
- package/src/components/button/index.ts +26 -1
- package/src/components/button/types.ts +7 -1
- package/src/components/card/api.ts +78 -9
- package/src/components/card/card.ts +58 -3
- package/src/components/card/config.ts +41 -11
- package/src/components/card/features.ts +39 -12
- package/src/components/card/index.ts +84 -19
- package/src/components/card/types.ts +218 -29
- package/src/components/carousel/carousel.ts +92 -28
- package/src/components/carousel/constants.ts +107 -21
- package/src/components/carousel/index.ts +31 -13
- package/src/components/checkbox/checkbox.ts +83 -16
- package/src/components/checkbox/index.ts +43 -1
- package/src/components/checkbox/types.ts +219 -32
- package/src/components/chips/api.ts +194 -0
- package/src/components/{chip → chips/chip}/api.ts +42 -2
- package/src/components/chips/chip/chip.ts +131 -0
- package/src/components/{chip → chips/chip}/config.ts +3 -3
- package/src/components/chips/chip/index.ts +3 -0
- package/src/components/chips/chips.md +481 -0
- package/src/components/chips/chips.ts +75 -0
- package/src/components/chips/config.ts +109 -0
- package/src/components/chips/constants.ts +61 -0
- package/src/components/chips/features/chip-items.ts +33 -0
- package/src/components/chips/features/container.ts +77 -0
- package/src/components/chips/features/controller.ts +448 -0
- package/src/components/chips/features/index.ts +5 -0
- package/src/components/chips/features/label.ts +108 -0
- package/src/components/chips/index.ts +11 -0
- package/src/components/chips/schema.ts +61 -0
- package/src/components/{chip → chips}/types.ts +203 -92
- package/src/components/dialog/dialog.ts +99 -16
- package/src/components/dialog/index.ts +97 -1
- package/src/components/dialog/types.ts +375 -69
- package/src/components/divider/config.ts +90 -6
- package/src/components/divider/divider.ts +32 -2
- package/src/components/divider/features.ts +26 -0
- package/src/components/divider/index.ts +30 -0
- package/src/components/divider/types.ts +86 -9
- package/src/components/extended-fab/api.ts +53 -1
- package/src/components/extended-fab/config.ts +29 -1
- package/src/components/extended-fab/extended-fab.ts +28 -0
- package/src/components/extended-fab/index.ts +36 -0
- package/src/components/extended-fab/types.ts +458 -13
- package/src/components/fab/api.ts +42 -2
- package/src/components/fab/config.ts +29 -1
- package/src/components/fab/fab.ts +16 -2
- package/src/components/fab/index.ts +35 -0
- package/src/components/fab/types.ts +374 -10
- package/src/components/list/api.ts +12 -2
- package/src/components/list/config.ts +21 -0
- package/src/components/list/features.ts +6 -0
- package/src/components/list/index.ts +56 -1
- package/src/components/list/list-item.ts +46 -2
- package/src/components/list/list.ts +73 -2
- package/src/components/list/types.ts +172 -0
- package/src/components/list/utils.ts +26 -2
- package/src/components/menu/api.ts +217 -20
- package/src/components/menu/config.ts +27 -0
- package/src/components/menu/features/visibility.ts +55 -6
- package/src/components/menu/index.ts +64 -0
- package/src/components/menu/menu-item.ts +46 -3
- package/src/components/menu/menu.ts +77 -1
- package/src/components/menu/types.ts +404 -39
- package/src/components/sheet/config.ts +1 -2
- package/src/components/sheet/features/gestures.ts +1 -1
- package/src/components/sheet/features/position.ts +1 -2
- package/src/components/sheet/features/state.ts +1 -1
- package/src/components/sheet/index.ts +10 -2
- package/src/components/sheet/sheet.ts +1 -2
- package/src/components/sheet/types.ts +29 -1
- package/src/components/slider/api.ts +1 -1
- package/src/components/slider/config.ts +1 -1
- package/src/components/slider/features/controller.ts +1 -1
- package/src/components/slider/features/handlers.ts +1 -1
- package/src/components/slider/features/states.ts +1 -1
- package/src/components/slider/index.ts +12 -5
- package/src/components/slider/schema.ts +1 -1
- package/src/components/slider/types.ts +31 -0
- package/src/components/tabs/tab-api.ts +1 -1
- package/src/components/tabs/types.ts +1 -1
- package/src/components/tooltip/api.ts +6 -2
- package/src/components/tooltip/config.ts +9 -28
- package/src/components/tooltip/index.ts +10 -1
- package/src/components/tooltip/types.ts +38 -3
- package/src/index.ts +129 -31
- package/src/styles/abstract/_mixins.scss +23 -9
- package/src/styles/abstract/_variables.scss +14 -4
- package/src/styles/components/_card.scss +1 -1
- package/src/styles/components/_chip.scss +323 -113
- package/src/styles/components/_tabs.scss +1 -1
- package/CLAUDE.md +0 -33
- package/src/components/checkbox/constants.ts +0 -37
- package/src/components/chip/chip-set.ts +0 -225
- package/src/components/chip/chip.ts +0 -118
- package/src/components/chip/constants.ts +0 -28
- package/src/components/chip/index.ts +0 -12
- package/src/components/list/constants.ts +0 -116
- package/src/components/sheet/constants.ts +0 -20
- package/src/components/slider/constants.ts +0 -32
- package/src/components/tooltip/constants.ts +0 -27
- package/test/components/badge.test.ts +0 -545
- package/test/components/bottom-app-bar.test.ts +0 -303
- package/test/components/button.test.ts +0 -233
- package/test/components/card.test.ts +0 -560
- package/test/components/carousel.test.ts +0 -951
- package/test/components/checkbox.test.ts +0 -462
- package/test/components/chip.test.ts +0 -692
- package/test/components/datepicker.test.ts +0 -1124
- package/test/components/dialog.test.ts +0 -990
- package/test/components/divider.test.ts +0 -412
- package/test/components/extended-fab.test.ts +0 -672
- package/test/components/fab.test.ts +0 -561
- package/test/components/list.test.ts +0 -365
- package/test/components/menu.test.ts +0 -718
- package/test/components/navigation.test.ts +0 -186
- package/test/components/progress.test.ts +0 -567
- package/test/components/radios.test.ts +0 -699
- package/test/components/search.test.ts +0 -1135
- package/test/components/segmented-button.test.ts +0 -732
- package/test/components/sheet.test.ts +0 -641
- package/test/components/slider.test.ts +0 -1220
- package/test/components/snackbar.test.ts +0 -461
- package/test/components/switch.test.ts +0 -452
- package/test/components/tabs.test.ts +0 -1369
- package/test/components/textfield.test.ts +0 -400
- package/test/components/timepicker.test.ts +0 -592
- package/test/components/tooltip.test.ts +0 -630
- package/test/components/top-app-bar.test.ts +0 -566
- package/test/core/dom.attributes.test.ts +0 -148
- package/test/core/dom.classes.test.ts +0 -152
- package/test/core/dom.events.test.ts +0 -243
- package/test/core/emitter.test.ts +0 -141
- package/test/core/ripple.test.ts +0 -99
- package/test/core/state.store.test.ts +0 -189
- package/test/core/utils.normalize.test.ts +0 -61
- package/test/core/utils.object.test.ts +0 -120
- package/test/setup.js +0 -371
- package/test/setup.ts +0 -451
- package/tsconfig.json +0 -22
- package/typedoc.json +0 -28
- package/typedoc.simple.json +0 -14
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
// test/components/top-app-bar.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 type { TopAppBarConfig, TopAppBar } from '../../src/components/top-app-bar/types';
|
|
47
|
-
import type { TopAppBarType } from '../../src/components/top-app-bar/types';
|
|
48
|
-
|
|
49
|
-
// Create a mock implementation of the TopAppBar component
|
|
50
|
-
const createMockTopAppBar = (config: TopAppBarConfig = {}): TopAppBar => {
|
|
51
|
-
// Default configuration
|
|
52
|
-
const defaultConfig = {
|
|
53
|
-
tag: 'header',
|
|
54
|
-
type: 'small',
|
|
55
|
-
scrollable: true,
|
|
56
|
-
compressible: true,
|
|
57
|
-
scrollThreshold: 4,
|
|
58
|
-
...config
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Create the main element
|
|
62
|
-
const element = document.createElement(defaultConfig.tag || 'header');
|
|
63
|
-
element.className = `mtrl-top-app-bar`;
|
|
64
|
-
element.setAttribute('role', 'banner');
|
|
65
|
-
element.setAttribute('aria-label', 'Top app bar');
|
|
66
|
-
|
|
67
|
-
if (defaultConfig.class) {
|
|
68
|
-
element.className += ` ${defaultConfig.class}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Add type class if not the default 'small'
|
|
72
|
-
if (defaultConfig.type !== 'small') {
|
|
73
|
-
element.className += ` mtrl-top-app-bar--${defaultConfig.type}`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Add compressible class if enabled and type is medium or large
|
|
77
|
-
if (defaultConfig.compressible &&
|
|
78
|
-
(defaultConfig.type === 'medium' || defaultConfig.type === 'large')) {
|
|
79
|
-
element.className += ' mtrl-top-app-bar--compressible';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Create containers
|
|
83
|
-
const leadingContainer = document.createElement('div');
|
|
84
|
-
leadingContainer.className = 'mtrl-top-app-bar-leading';
|
|
85
|
-
|
|
86
|
-
const headlineElement = document.createElement('h1');
|
|
87
|
-
headlineElement.className = 'mtrl-top-app-bar-headline';
|
|
88
|
-
|
|
89
|
-
if (defaultConfig.title) {
|
|
90
|
-
headlineElement.textContent = defaultConfig.title;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const trailingContainer = document.createElement('div');
|
|
94
|
-
trailingContainer.className = 'mtrl-top-app-bar-trailing';
|
|
95
|
-
|
|
96
|
-
// Build DOM structure based on type
|
|
97
|
-
if (defaultConfig.type === 'medium' || defaultConfig.type === 'large') {
|
|
98
|
-
// For medium and large, create rows
|
|
99
|
-
const topRow = document.createElement('div');
|
|
100
|
-
topRow.className = 'mtrl-top-app-bar-row';
|
|
101
|
-
topRow.appendChild(leadingContainer);
|
|
102
|
-
topRow.appendChild(trailingContainer);
|
|
103
|
-
|
|
104
|
-
const bottomRow = document.createElement('div');
|
|
105
|
-
bottomRow.className = 'mtrl-top-app-bar-row';
|
|
106
|
-
bottomRow.appendChild(headlineElement);
|
|
107
|
-
|
|
108
|
-
element.appendChild(topRow);
|
|
109
|
-
element.appendChild(bottomRow);
|
|
110
|
-
} else {
|
|
111
|
-
// For small and center-aligned
|
|
112
|
-
element.appendChild(leadingContainer);
|
|
113
|
-
element.appendChild(headlineElement);
|
|
114
|
-
element.appendChild(trailingContainer);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Track scrolled state
|
|
118
|
-
let isScrolled = false;
|
|
119
|
-
|
|
120
|
-
// Set up scroll events if scrollable is enabled
|
|
121
|
-
if (defaultConfig.scrollable) {
|
|
122
|
-
const handleScroll = () => {
|
|
123
|
-
const shouldBeScrolled = window.scrollY > (defaultConfig.scrollThreshold || 4);
|
|
124
|
-
|
|
125
|
-
if (isScrolled !== shouldBeScrolled) {
|
|
126
|
-
isScrolled = shouldBeScrolled;
|
|
127
|
-
|
|
128
|
-
// Toggle scrolled class
|
|
129
|
-
if (isScrolled) {
|
|
130
|
-
element.classList.add('mtrl-top-app-bar--scrolled');
|
|
131
|
-
} else {
|
|
132
|
-
element.classList.remove('mtrl-top-app-bar--scrolled');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Call the onScroll callback if provided
|
|
136
|
-
if (defaultConfig.onScroll) {
|
|
137
|
-
defaultConfig.onScroll(isScrolled);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Helper function to reorganize DOM for different types
|
|
146
|
-
const reorganizeDom = (type: TopAppBarType) => {
|
|
147
|
-
// Clear existing content
|
|
148
|
-
while (element.firstChild) {
|
|
149
|
-
element.removeChild(element.firstChild);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Update type classes
|
|
153
|
-
['small', 'medium', 'large', 'center'].forEach(t => {
|
|
154
|
-
element.classList.remove(`mtrl-top-app-bar--${t}`);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (type !== 'small') {
|
|
158
|
-
element.classList.add(`mtrl-top-app-bar--${type}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Update compressible class
|
|
162
|
-
element.classList.toggle(
|
|
163
|
-
'mtrl-top-app-bar--compressible',
|
|
164
|
-
defaultConfig.compressible && (type === 'medium' || type === 'large')
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
// Rebuild the DOM structure
|
|
168
|
-
if (type === 'medium' || type === 'large') {
|
|
169
|
-
// For medium and large, create rows
|
|
170
|
-
const topRow = document.createElement('div');
|
|
171
|
-
topRow.className = 'mtrl-top-app-bar-row';
|
|
172
|
-
topRow.appendChild(leadingContainer);
|
|
173
|
-
topRow.appendChild(trailingContainer);
|
|
174
|
-
|
|
175
|
-
const bottomRow = document.createElement('div');
|
|
176
|
-
bottomRow.className = 'mtrl-top-app-bar-row';
|
|
177
|
-
bottomRow.appendChild(headlineElement);
|
|
178
|
-
|
|
179
|
-
element.appendChild(topRow);
|
|
180
|
-
element.appendChild(bottomRow);
|
|
181
|
-
} else {
|
|
182
|
-
// For small and center-aligned
|
|
183
|
-
element.appendChild(leadingContainer);
|
|
184
|
-
element.appendChild(headlineElement);
|
|
185
|
-
element.appendChild(trailingContainer);
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// Create the API object
|
|
190
|
-
const topAppBar: TopAppBar = {
|
|
191
|
-
element,
|
|
192
|
-
|
|
193
|
-
// Base component event methods
|
|
194
|
-
on: () => topAppBar,
|
|
195
|
-
off: () => topAppBar,
|
|
196
|
-
emit: () => topAppBar,
|
|
197
|
-
|
|
198
|
-
// Lifecycle methods
|
|
199
|
-
lifecycle: {
|
|
200
|
-
destroy: () => {
|
|
201
|
-
// Remove event listener if scrollable
|
|
202
|
-
if (defaultConfig.scrollable) {
|
|
203
|
-
window.removeEventListener('scroll', () => {});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Remove element from DOM
|
|
207
|
-
if (element.parentNode) {
|
|
208
|
-
element.parentNode.removeChild(element);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
// Utility methods
|
|
214
|
-
getClass: (name: string) => `mtrl-${name}`,
|
|
215
|
-
|
|
216
|
-
// Component specific methods
|
|
217
|
-
setTitle(title: string) {
|
|
218
|
-
headlineElement.textContent = title;
|
|
219
|
-
return this;
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
getTitle() {
|
|
223
|
-
return headlineElement.textContent || '';
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
addLeadingElement(el: HTMLElement) {
|
|
227
|
-
leadingContainer.appendChild(el);
|
|
228
|
-
return this;
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
addTrailingElement(el: HTMLElement) {
|
|
232
|
-
trailingContainer.appendChild(el);
|
|
233
|
-
return this;
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
setType(type: TopAppBarType) {
|
|
237
|
-
defaultConfig.type = type;
|
|
238
|
-
reorganizeDom(type);
|
|
239
|
-
return this;
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
setScrollState(scrolled: boolean) {
|
|
243
|
-
isScrolled = scrolled;
|
|
244
|
-
|
|
245
|
-
if (scrolled) {
|
|
246
|
-
element.classList.add('mtrl-top-app-bar--scrolled');
|
|
247
|
-
} else {
|
|
248
|
-
element.classList.remove('mtrl-top-app-bar--scrolled');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return this;
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
getHeadlineElement() {
|
|
255
|
-
return headlineElement;
|
|
256
|
-
},
|
|
257
|
-
|
|
258
|
-
getLeadingContainer() {
|
|
259
|
-
return leadingContainer;
|
|
260
|
-
},
|
|
261
|
-
|
|
262
|
-
getTrailingContainer() {
|
|
263
|
-
return trailingContainer;
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
destroy() {
|
|
267
|
-
this.lifecycle.destroy();
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
return topAppBar;
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
describe('TopAppBar Component', () => {
|
|
275
|
-
test('should create a top app bar element', () => {
|
|
276
|
-
const topAppBar = createMockTopAppBar();
|
|
277
|
-
|
|
278
|
-
expect(topAppBar.element).toBeDefined();
|
|
279
|
-
expect(topAppBar.element.tagName).toBe('HEADER');
|
|
280
|
-
expect(topAppBar.element.className).toContain('mtrl-top-app-bar');
|
|
281
|
-
expect(topAppBar.element.getAttribute('role')).toBe('banner');
|
|
282
|
-
|
|
283
|
-
// Clean up
|
|
284
|
-
topAppBar.destroy();
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
test('should create a top app bar with custom tag', () => {
|
|
288
|
-
const topAppBar = createMockTopAppBar({
|
|
289
|
-
tag: 'div'
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
expect(topAppBar.element.tagName).toBe('DIV');
|
|
293
|
-
|
|
294
|
-
// Clean up
|
|
295
|
-
topAppBar.destroy();
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('should initialize with a title', () => {
|
|
299
|
-
const title = 'Application Title';
|
|
300
|
-
const topAppBar = createMockTopAppBar({
|
|
301
|
-
title
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
expect(topAppBar.getTitle()).toBe(title);
|
|
305
|
-
expect(topAppBar.getHeadlineElement().textContent).toBe(title);
|
|
306
|
-
|
|
307
|
-
// Clean up
|
|
308
|
-
topAppBar.destroy();
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test('should apply correct structure for small type', () => {
|
|
312
|
-
const topAppBar = createMockTopAppBar({
|
|
313
|
-
type: 'small'
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
// Small type should have a flat structure
|
|
317
|
-
expect(topAppBar.element.children.length).toBe(3);
|
|
318
|
-
expect(topAppBar.element.children[0]).toBe(topAppBar.getLeadingContainer());
|
|
319
|
-
expect(topAppBar.element.children[1]).toBe(topAppBar.getHeadlineElement());
|
|
320
|
-
expect(topAppBar.element.children[2]).toBe(topAppBar.getTrailingContainer());
|
|
321
|
-
|
|
322
|
-
// Shouldn't have the small variant class (as it's the default)
|
|
323
|
-
expect(topAppBar.element.className).not.toContain('mtrl-top-app-bar--small');
|
|
324
|
-
|
|
325
|
-
// Clean up
|
|
326
|
-
topAppBar.destroy();
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test('should apply correct structure for medium type', () => {
|
|
330
|
-
const topAppBar = createMockTopAppBar({
|
|
331
|
-
type: 'medium'
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Medium type should have two rows
|
|
335
|
-
expect(topAppBar.element.className).toContain('mtrl-top-app-bar--medium');
|
|
336
|
-
expect(topAppBar.element.children.length).toBe(2);
|
|
337
|
-
|
|
338
|
-
// First row should contain leading and trailing
|
|
339
|
-
const topRow = topAppBar.element.children[0] as HTMLElement;
|
|
340
|
-
expect(topRow.className).toContain('mtrl-top-app-bar-row');
|
|
341
|
-
expect(topRow.children[0]).toBe(topAppBar.getLeadingContainer());
|
|
342
|
-
expect(topRow.children[1]).toBe(topAppBar.getTrailingContainer());
|
|
343
|
-
|
|
344
|
-
// Second row should contain headline
|
|
345
|
-
const bottomRow = topAppBar.element.children[1] as HTMLElement;
|
|
346
|
-
expect(bottomRow.className).toContain('mtrl-top-app-bar-row');
|
|
347
|
-
expect(bottomRow.children[0]).toBe(topAppBar.getHeadlineElement());
|
|
348
|
-
|
|
349
|
-
// Clean up
|
|
350
|
-
topAppBar.destroy();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
test('should apply correct structure for large type', () => {
|
|
354
|
-
const topAppBar = createMockTopAppBar({
|
|
355
|
-
type: 'large'
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
// Large type should have two rows like medium
|
|
359
|
-
expect(topAppBar.element.className).toContain('mtrl-top-app-bar--large');
|
|
360
|
-
expect(topAppBar.element.children.length).toBe(2);
|
|
361
|
-
|
|
362
|
-
// Clean up
|
|
363
|
-
topAppBar.destroy();
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test('should apply correct structure for center type', () => {
|
|
367
|
-
const topAppBar = createMockTopAppBar({
|
|
368
|
-
type: 'center'
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// Center type should have a flat structure like small
|
|
372
|
-
expect(topAppBar.element.className).toContain('mtrl-top-app-bar--center');
|
|
373
|
-
expect(topAppBar.element.children.length).toBe(3);
|
|
374
|
-
expect(topAppBar.element.children[0]).toBe(topAppBar.getLeadingContainer());
|
|
375
|
-
expect(topAppBar.element.children[1]).toBe(topAppBar.getHeadlineElement());
|
|
376
|
-
expect(topAppBar.element.children[2]).toBe(topAppBar.getTrailingContainer());
|
|
377
|
-
|
|
378
|
-
// Clean up
|
|
379
|
-
topAppBar.destroy();
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test('should add compressible class to medium/large types when compressible is true', () => {
|
|
383
|
-
const mediumTopAppBar = createMockTopAppBar({
|
|
384
|
-
type: 'medium',
|
|
385
|
-
compressible: true
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
expect(mediumTopAppBar.element.className).toContain('mtrl-top-app-bar--compressible');
|
|
389
|
-
mediumTopAppBar.destroy();
|
|
390
|
-
|
|
391
|
-
const largeTopAppBar = createMockTopAppBar({
|
|
392
|
-
type: 'large',
|
|
393
|
-
compressible: true
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
expect(largeTopAppBar.element.className).toContain('mtrl-top-app-bar--compressible');
|
|
397
|
-
largeTopAppBar.destroy();
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
test('should not add compressible class when compressible is false', () => {
|
|
401
|
-
const topAppBar = createMockTopAppBar({
|
|
402
|
-
type: 'medium',
|
|
403
|
-
compressible: false
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
expect(topAppBar.element.className).not.toContain('mtrl-top-app-bar--compressible');
|
|
407
|
-
|
|
408
|
-
// Clean up
|
|
409
|
-
topAppBar.destroy();
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
test('should set and get title text', () => {
|
|
413
|
-
const topAppBar = createMockTopAppBar();
|
|
414
|
-
const initialTitle = 'Initial Title';
|
|
415
|
-
const updatedTitle = 'Updated Title';
|
|
416
|
-
|
|
417
|
-
// Set initial title
|
|
418
|
-
topAppBar.setTitle(initialTitle);
|
|
419
|
-
expect(topAppBar.getTitle()).toBe(initialTitle);
|
|
420
|
-
expect(topAppBar.getHeadlineElement().textContent).toBe(initialTitle);
|
|
421
|
-
|
|
422
|
-
// Update title
|
|
423
|
-
topAppBar.setTitle(updatedTitle);
|
|
424
|
-
expect(topAppBar.getTitle()).toBe(updatedTitle);
|
|
425
|
-
expect(topAppBar.getHeadlineElement().textContent).toBe(updatedTitle);
|
|
426
|
-
|
|
427
|
-
// Clean up
|
|
428
|
-
topAppBar.destroy();
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test('should add leading elements', () => {
|
|
432
|
-
const topAppBar = createMockTopAppBar();
|
|
433
|
-
const menuButton = document.createElement('button');
|
|
434
|
-
menuButton.textContent = 'Menu';
|
|
435
|
-
|
|
436
|
-
topAppBar.addLeadingElement(menuButton);
|
|
437
|
-
|
|
438
|
-
expect(topAppBar.getLeadingContainer().children.length).toBe(1);
|
|
439
|
-
expect(topAppBar.getLeadingContainer().children[0]).toBe(menuButton);
|
|
440
|
-
|
|
441
|
-
// Add another element
|
|
442
|
-
const backButton = document.createElement('button');
|
|
443
|
-
backButton.textContent = 'Back';
|
|
444
|
-
|
|
445
|
-
topAppBar.addLeadingElement(backButton);
|
|
446
|
-
|
|
447
|
-
expect(topAppBar.getLeadingContainer().children.length).toBe(2);
|
|
448
|
-
expect(topAppBar.getLeadingContainer().children[1]).toBe(backButton);
|
|
449
|
-
|
|
450
|
-
// Clean up
|
|
451
|
-
topAppBar.destroy();
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
test('should add trailing elements', () => {
|
|
455
|
-
const topAppBar = createMockTopAppBar();
|
|
456
|
-
const searchButton = document.createElement('button');
|
|
457
|
-
searchButton.textContent = 'Search';
|
|
458
|
-
|
|
459
|
-
topAppBar.addTrailingElement(searchButton);
|
|
460
|
-
|
|
461
|
-
expect(topAppBar.getTrailingContainer().children.length).toBe(1);
|
|
462
|
-
expect(topAppBar.getTrailingContainer().children[0]).toBe(searchButton);
|
|
463
|
-
|
|
464
|
-
// Add another element
|
|
465
|
-
const moreButton = document.createElement('button');
|
|
466
|
-
moreButton.textContent = 'More';
|
|
467
|
-
|
|
468
|
-
topAppBar.addTrailingElement(moreButton);
|
|
469
|
-
|
|
470
|
-
expect(topAppBar.getTrailingContainer().children.length).toBe(2);
|
|
471
|
-
expect(topAppBar.getTrailingContainer().children[1]).toBe(moreButton);
|
|
472
|
-
|
|
473
|
-
// Clean up
|
|
474
|
-
topAppBar.destroy();
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
test('should change type and reorganize DOM structure', () => {
|
|
478
|
-
const topAppBar = createMockTopAppBar({
|
|
479
|
-
type: 'small'
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// Initially small type (flat structure)
|
|
483
|
-
expect(topAppBar.element.children.length).toBe(3);
|
|
484
|
-
expect(topAppBar.element.className).not.toContain('mtrl-top-app-bar--medium');
|
|
485
|
-
|
|
486
|
-
// Change to medium type
|
|
487
|
-
topAppBar.setType('medium');
|
|
488
|
-
|
|
489
|
-
// Now should have medium type structure (two rows)
|
|
490
|
-
expect(topAppBar.element.className).toContain('mtrl-top-app-bar--medium');
|
|
491
|
-
expect(topAppBar.element.children.length).toBe(2);
|
|
492
|
-
|
|
493
|
-
const topRow = topAppBar.element.children[0] as HTMLElement;
|
|
494
|
-
expect(topRow.className).toContain('mtrl-top-app-bar-row');
|
|
495
|
-
expect(topRow.children[0]).toBe(topAppBar.getLeadingContainer());
|
|
496
|
-
expect(topRow.children[1]).toBe(topAppBar.getTrailingContainer());
|
|
497
|
-
|
|
498
|
-
// Change back to small type
|
|
499
|
-
topAppBar.setType('small');
|
|
500
|
-
|
|
501
|
-
// Should be back to flat structure
|
|
502
|
-
expect(topAppBar.element.className).not.toContain('mtrl-top-app-bar--medium');
|
|
503
|
-
expect(topAppBar.element.children.length).toBe(3);
|
|
504
|
-
expect(topAppBar.element.children[0]).toBe(topAppBar.getLeadingContainer());
|
|
505
|
-
expect(topAppBar.element.children[1]).toBe(topAppBar.getHeadlineElement());
|
|
506
|
-
expect(topAppBar.element.children[2]).toBe(topAppBar.getTrailingContainer());
|
|
507
|
-
|
|
508
|
-
// Clean up
|
|
509
|
-
topAppBar.destroy();
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
test('should set scroll state manually', () => {
|
|
513
|
-
const topAppBar = createMockTopAppBar();
|
|
514
|
-
|
|
515
|
-
// Initially not scrolled
|
|
516
|
-
expect(topAppBar.element.className).not.toContain('mtrl-top-app-bar--scrolled');
|
|
517
|
-
|
|
518
|
-
// Set scrolled state to true
|
|
519
|
-
topAppBar.setScrollState(true);
|
|
520
|
-
expect(topAppBar.element.className).toContain('mtrl-top-app-bar--scrolled');
|
|
521
|
-
|
|
522
|
-
// Set scrolled state back to false
|
|
523
|
-
topAppBar.setScrollState(false);
|
|
524
|
-
expect(topAppBar.element.className).not.toContain('mtrl-top-app-bar--scrolled');
|
|
525
|
-
|
|
526
|
-
// Clean up
|
|
527
|
-
topAppBar.destroy();
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
test('should call onScroll callback when scroll state changes', () => {
|
|
531
|
-
const onScrollMock = mock((scrolled: boolean) => {});
|
|
532
|
-
|
|
533
|
-
const topAppBar = createMockTopAppBar({
|
|
534
|
-
onScroll: onScrollMock
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
// Simulate scroll event
|
|
538
|
-
window.scrollY = 10; // Above the default threshold of 4
|
|
539
|
-
const scrollEvent = new Event('scroll');
|
|
540
|
-
window.dispatchEvent(scrollEvent);
|
|
541
|
-
|
|
542
|
-
// Hard to test in JSDOM, so we'll just test manual state change
|
|
543
|
-
topAppBar.setScrollState(true);
|
|
544
|
-
|
|
545
|
-
// Clean up
|
|
546
|
-
topAppBar.destroy();
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
test('should clean up resources on destroy', () => {
|
|
550
|
-
const parent = document.createElement('div');
|
|
551
|
-
document.body.appendChild(parent);
|
|
552
|
-
|
|
553
|
-
const topAppBar = createMockTopAppBar();
|
|
554
|
-
parent.appendChild(topAppBar.element);
|
|
555
|
-
|
|
556
|
-
expect(parent.contains(topAppBar.element)).toBe(true);
|
|
557
|
-
|
|
558
|
-
// Destroy the top app bar
|
|
559
|
-
topAppBar.destroy();
|
|
560
|
-
|
|
561
|
-
expect(parent.contains(topAppBar.element)).toBe(false);
|
|
562
|
-
|
|
563
|
-
// Clean up
|
|
564
|
-
document.body.removeChild(parent);
|
|
565
|
-
});
|
|
566
|
-
});
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
// test/core/dom.attributes.test.ts
|
|
2
|
-
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
3
|
-
import { JSDOM } from 'jsdom';
|
|
4
|
-
import { setAttributes, removeAttributes } from '../../src/core/dom/attributes';
|
|
5
|
-
|
|
6
|
-
// Setup jsdom environment
|
|
7
|
-
let dom: JSDOM;
|
|
8
|
-
let window: Window;
|
|
9
|
-
let document: Document;
|
|
10
|
-
let originalGlobalDocument: any;
|
|
11
|
-
let originalGlobalWindow: any;
|
|
12
|
-
|
|
13
|
-
beforeAll(() => {
|
|
14
|
-
// Create a new JSDOM instance
|
|
15
|
-
dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
16
|
-
url: 'http://localhost/',
|
|
17
|
-
pretendToBeVisual: true
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Get window and document from jsdom
|
|
21
|
-
window = dom.window;
|
|
22
|
-
document = window.document;
|
|
23
|
-
|
|
24
|
-
// Store original globals
|
|
25
|
-
originalGlobalDocument = global.document;
|
|
26
|
-
originalGlobalWindow = global.window;
|
|
27
|
-
|
|
28
|
-
// Set globals to use jsdom
|
|
29
|
-
global.document = document;
|
|
30
|
-
global.window = window;
|
|
31
|
-
global.Element = window.Element;
|
|
32
|
-
global.HTMLElement = window.HTMLElement;
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterAll(() => {
|
|
36
|
-
// Restore original globals
|
|
37
|
-
global.document = originalGlobalDocument;
|
|
38
|
-
global.window = originalGlobalWindow;
|
|
39
|
-
|
|
40
|
-
// Clean up jsdom
|
|
41
|
-
window.close();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('DOM Attributes Utilities', () => {
|
|
45
|
-
test('should set a single attribute', () => {
|
|
46
|
-
const element = document.createElement('div');
|
|
47
|
-
setAttributes(element, { id: 'test-id' });
|
|
48
|
-
expect(element.getAttribute('id')).toBe('test-id');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('should set multiple attributes', () => {
|
|
52
|
-
const element = document.createElement('div');
|
|
53
|
-
setAttributes(element, {
|
|
54
|
-
id: 'test-id',
|
|
55
|
-
class: 'test-class',
|
|
56
|
-
'data-test': 'test-data'
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
expect(element.getAttribute('id')).toBe('test-id');
|
|
60
|
-
expect(element.getAttribute('class')).toBe('test-class');
|
|
61
|
-
expect(element.getAttribute('data-test')).toBe('test-data');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('should convert attribute values to strings', () => {
|
|
65
|
-
const element = document.createElement('div');
|
|
66
|
-
setAttributes(element, {
|
|
67
|
-
'data-number': 123,
|
|
68
|
-
'data-boolean': true,
|
|
69
|
-
'data-object': { toString: () => 'object-string' }
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
expect(element.getAttribute('data-number')).toBe('123');
|
|
73
|
-
expect(element.getAttribute('data-boolean')).toBe('true');
|
|
74
|
-
expect(element.getAttribute('data-object')).toBe('object-string');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('should handle empty attributes object', () => {
|
|
78
|
-
const element = document.createElement('div');
|
|
79
|
-
element.setAttribute('test', 'value');
|
|
80
|
-
|
|
81
|
-
setAttributes(element, {});
|
|
82
|
-
|
|
83
|
-
expect(element.getAttribute('test')).toBe('value');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('should skip null or undefined attribute values', () => {
|
|
87
|
-
const element = document.createElement('div');
|
|
88
|
-
setAttributes(element, {
|
|
89
|
-
'data-defined': 'value',
|
|
90
|
-
'data-null': null,
|
|
91
|
-
'data-undefined': undefined
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
expect(element.getAttribute('data-defined')).toBe('value');
|
|
95
|
-
expect(element.hasAttribute('data-null')).toBe(false);
|
|
96
|
-
expect(element.hasAttribute('data-undefined')).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('should remove a single attribute', () => {
|
|
100
|
-
const element = document.createElement('div');
|
|
101
|
-
element.setAttribute('id', 'test-id');
|
|
102
|
-
|
|
103
|
-
removeAttributes(element, ['id']);
|
|
104
|
-
|
|
105
|
-
expect(element.hasAttribute('id')).toBe(false);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('should remove multiple attributes', () => {
|
|
109
|
-
const element = document.createElement('div');
|
|
110
|
-
element.setAttribute('id', 'test-id');
|
|
111
|
-
element.setAttribute('class', 'test-class');
|
|
112
|
-
element.setAttribute('data-test', 'test-data');
|
|
113
|
-
|
|
114
|
-
removeAttributes(element, ['id', 'data-test']);
|
|
115
|
-
|
|
116
|
-
expect(element.hasAttribute('id')).toBe(false);
|
|
117
|
-
expect(element.getAttribute('class')).toBe('test-class');
|
|
118
|
-
expect(element.hasAttribute('data-test')).toBe(false);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('should handle empty attributes array', () => {
|
|
122
|
-
const element = document.createElement('div');
|
|
123
|
-
element.setAttribute('test', 'value');
|
|
124
|
-
|
|
125
|
-
removeAttributes(element, []);
|
|
126
|
-
|
|
127
|
-
expect(element.getAttribute('test')).toBe('value');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('should silently ignore non-existent attributes', () => {
|
|
131
|
-
const element = document.createElement('div');
|
|
132
|
-
element.setAttribute('id', 'test-id');
|
|
133
|
-
|
|
134
|
-
removeAttributes(element, ['id', 'non-existent']);
|
|
135
|
-
|
|
136
|
-
expect(element.hasAttribute('id')).toBe(false);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('should return the element from modifier functions', () => {
|
|
140
|
-
const element = document.createElement('div');
|
|
141
|
-
|
|
142
|
-
const result1 = setAttributes(element, { test: 'value' });
|
|
143
|
-
const result2 = removeAttributes(element, ['test']);
|
|
144
|
-
|
|
145
|
-
expect(result1).toBe(element);
|
|
146
|
-
expect(result2).toBe(element);
|
|
147
|
-
});
|
|
148
|
-
});
|