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
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
// test/components/carousel.test.ts
|
|
2
|
+
import { describe, test, expect, mock, beforeAll, afterAll } from 'bun:test';
|
|
3
|
+
import { JSDOM } from 'jsdom';
|
|
4
|
+
import {
|
|
5
|
+
type CarouselComponent,
|
|
6
|
+
type CarouselConfig,
|
|
7
|
+
type CarouselSlide,
|
|
8
|
+
type CarouselLayout,
|
|
9
|
+
type CarouselScrollBehavior
|
|
10
|
+
} from '../../src/components/carousel/types';
|
|
11
|
+
|
|
12
|
+
// Setup jsdom environment
|
|
13
|
+
let dom: JSDOM;
|
|
14
|
+
let window: Window;
|
|
15
|
+
let document: Document;
|
|
16
|
+
let originalGlobalDocument: any;
|
|
17
|
+
let originalGlobalWindow: any;
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
// Create a new JSDOM instance
|
|
21
|
+
dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
22
|
+
url: 'http://localhost/',
|
|
23
|
+
pretendToBeVisual: true
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Get window and document from jsdom
|
|
27
|
+
window = dom.window;
|
|
28
|
+
document = window.document;
|
|
29
|
+
|
|
30
|
+
// Store original globals
|
|
31
|
+
originalGlobalDocument = global.document;
|
|
32
|
+
originalGlobalWindow = global.window;
|
|
33
|
+
|
|
34
|
+
// Set globals to use jsdom
|
|
35
|
+
global.document = document;
|
|
36
|
+
global.window = window;
|
|
37
|
+
global.Element = window.Element;
|
|
38
|
+
global.HTMLElement = window.HTMLElement;
|
|
39
|
+
global.HTMLButtonElement = window.HTMLButtonElement;
|
|
40
|
+
global.Event = window.Event;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterAll(() => {
|
|
44
|
+
// Restore original globals
|
|
45
|
+
global.document = originalGlobalDocument;
|
|
46
|
+
global.window = originalGlobalWindow;
|
|
47
|
+
|
|
48
|
+
// Clean up jsdom
|
|
49
|
+
window.close();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Mock carousel implementation
|
|
53
|
+
const createMockCarousel = (config: CarouselConfig = {}): CarouselComponent => {
|
|
54
|
+
const element = document.createElement('div');
|
|
55
|
+
element.className = 'mtrl-carousel';
|
|
56
|
+
|
|
57
|
+
// Default settings
|
|
58
|
+
const settings = {
|
|
59
|
+
initialSlide: config.initialSlide || 0,
|
|
60
|
+
loop: config.loop !== undefined ? config.loop : true,
|
|
61
|
+
transition: config.transition || 'slide',
|
|
62
|
+
transitionDuration: config.transitionDuration || 300,
|
|
63
|
+
borderRadius: config.borderRadius || 16,
|
|
64
|
+
gap: config.gap || 8,
|
|
65
|
+
layout: config.layout || 'multi-browse',
|
|
66
|
+
scrollBehavior: config.scrollBehavior || 'snap',
|
|
67
|
+
centered: config.centered || false,
|
|
68
|
+
componentName: config.componentName || 'carousel',
|
|
69
|
+
prefix: config.prefix || 'mtrl'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Apply additional classes
|
|
73
|
+
if (config.class) {
|
|
74
|
+
const classes = config.class.split(' ');
|
|
75
|
+
classes.forEach(className => element.classList.add(className));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Apply layout class
|
|
79
|
+
if (settings.layout) {
|
|
80
|
+
element.classList.add(`mtrl-carousel--${settings.layout}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Apply scrollBehavior class
|
|
84
|
+
if (settings.scrollBehavior) {
|
|
85
|
+
element.classList.add(`mtrl-carousel--${settings.scrollBehavior}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Create slides container
|
|
89
|
+
const slidesContainer = document.createElement('div');
|
|
90
|
+
slidesContainer.className = 'mtrl-carousel__slides';
|
|
91
|
+
|
|
92
|
+
// Create slides elements
|
|
93
|
+
const slideElements: HTMLElement[] = [];
|
|
94
|
+
const slides: CarouselSlide[] = config.slides || [];
|
|
95
|
+
|
|
96
|
+
// Setup slides
|
|
97
|
+
slides.forEach((slide, index) => {
|
|
98
|
+
const slideElement = document.createElement('div');
|
|
99
|
+
slideElement.className = 'mtrl-carousel__slide';
|
|
100
|
+
slideElement.setAttribute('data-index', index.toString());
|
|
101
|
+
|
|
102
|
+
// Add slide content
|
|
103
|
+
if (slide.image) {
|
|
104
|
+
const img = document.createElement('img');
|
|
105
|
+
img.className = 'mtrl-carousel__image';
|
|
106
|
+
img.src = slide.image;
|
|
107
|
+
img.alt = slide.title || `Slide ${index + 1}`;
|
|
108
|
+
slideElement.appendChild(img);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (slide.title) {
|
|
112
|
+
const title = document.createElement('h3');
|
|
113
|
+
title.className = 'mtrl-carousel__title';
|
|
114
|
+
title.textContent = slide.title;
|
|
115
|
+
slideElement.appendChild(title);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (slide.description) {
|
|
119
|
+
const desc = document.createElement('p');
|
|
120
|
+
desc.className = 'mtrl-carousel__description';
|
|
121
|
+
desc.textContent = slide.description;
|
|
122
|
+
slideElement.appendChild(desc);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (slide.buttonText && slide.buttonUrl) {
|
|
126
|
+
const button = document.createElement('a');
|
|
127
|
+
button.className = 'mtrl-carousel__button';
|
|
128
|
+
button.textContent = slide.buttonText;
|
|
129
|
+
button.href = slide.buttonUrl;
|
|
130
|
+
slideElement.appendChild(button);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Apply accent color if available
|
|
134
|
+
if (slide.accent) {
|
|
135
|
+
slideElement.style.setProperty('--carousel-accent-color', slide.accent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Apply size class if specified
|
|
139
|
+
if (slide.size) {
|
|
140
|
+
slideElement.classList.add(`mtrl-carousel__slide--${slide.size}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
slidesContainer.appendChild(slideElement);
|
|
144
|
+
slideElements.push(slideElement);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
element.appendChild(slidesContainer);
|
|
148
|
+
|
|
149
|
+
// Create navigation elements
|
|
150
|
+
const prevButton = document.createElement('button');
|
|
151
|
+
prevButton.className = 'mtrl-carousel__prev';
|
|
152
|
+
prevButton.setAttribute('aria-label', 'Previous slide');
|
|
153
|
+
|
|
154
|
+
const nextButton = document.createElement('button');
|
|
155
|
+
nextButton.className = 'mtrl-carousel__next';
|
|
156
|
+
nextButton.setAttribute('aria-label', 'Next slide');
|
|
157
|
+
|
|
158
|
+
element.appendChild(prevButton);
|
|
159
|
+
element.appendChild(nextButton);
|
|
160
|
+
|
|
161
|
+
// Create indicators
|
|
162
|
+
const indicators = document.createElement('div');
|
|
163
|
+
indicators.className = 'mtrl-carousel__indicators';
|
|
164
|
+
|
|
165
|
+
slides.forEach((_, index) => {
|
|
166
|
+
const indicator = document.createElement('button');
|
|
167
|
+
indicator.className = 'mtrl-carousel__indicator';
|
|
168
|
+
indicator.setAttribute('data-index', index.toString());
|
|
169
|
+
indicator.setAttribute('aria-label', `Go to slide ${index + 1}`);
|
|
170
|
+
|
|
171
|
+
if (index === settings.initialSlide) {
|
|
172
|
+
indicator.classList.add('mtrl-carousel__indicator--active');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
indicators.appendChild(indicator);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
element.appendChild(indicators);
|
|
179
|
+
|
|
180
|
+
// Create Show All link if needed
|
|
181
|
+
if (config.showAllLink !== false) {
|
|
182
|
+
const showAllLink = document.createElement('a');
|
|
183
|
+
showAllLink.className = 'mtrl-carousel__show-all';
|
|
184
|
+
showAllLink.textContent = 'Show all';
|
|
185
|
+
showAllLink.href = '#';
|
|
186
|
+
showAllLink.addEventListener('click', (e) => {
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
if (config.onShowAll) {
|
|
189
|
+
config.onShowAll();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
element.appendChild(showAllLink);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Set up current slide
|
|
197
|
+
let currentSlide = settings.initialSlide;
|
|
198
|
+
|
|
199
|
+
// Set active slide
|
|
200
|
+
const setActiveSlide = (index: number) => {
|
|
201
|
+
// Update indicators
|
|
202
|
+
const allIndicators = indicators.querySelectorAll('.mtrl-carousel__indicator');
|
|
203
|
+
allIndicators.forEach((ind) => ind.classList.remove('mtrl-carousel__indicator--active'));
|
|
204
|
+
|
|
205
|
+
const activeIndicator = indicators.querySelector(`.mtrl-carousel__indicator[data-index="${index}"]`);
|
|
206
|
+
if (activeIndicator) {
|
|
207
|
+
activeIndicator.classList.add('mtrl-carousel__indicator--active');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Update carousel position
|
|
211
|
+
slidesContainer.style.transform = `translateX(-${index * 100}%)`;
|
|
212
|
+
|
|
213
|
+
// Update current slide
|
|
214
|
+
currentSlide = index;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Set initial active slide
|
|
218
|
+
setActiveSlide(currentSlide);
|
|
219
|
+
|
|
220
|
+
// Event handlers
|
|
221
|
+
const eventHandlers: Record<string, Function[]> = {};
|
|
222
|
+
|
|
223
|
+
const emit = (event: string, data?: any) => {
|
|
224
|
+
if (eventHandlers[event]) {
|
|
225
|
+
eventHandlers[event].forEach(handler => handler(data));
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Navigation event listeners
|
|
230
|
+
prevButton.addEventListener('click', () => {
|
|
231
|
+
carousel.prev();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
nextButton.addEventListener('click', () => {
|
|
235
|
+
carousel.next();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Indicator event listeners
|
|
239
|
+
const indicatorButtons = indicators.querySelectorAll('.mtrl-carousel__indicator');
|
|
240
|
+
indicatorButtons.forEach(button => {
|
|
241
|
+
button.addEventListener('click', () => {
|
|
242
|
+
const index = parseInt(button.getAttribute('data-index') || '0');
|
|
243
|
+
carousel.goTo(index);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Create the SlidesAPI
|
|
248
|
+
const slidesAPI = {
|
|
249
|
+
addSlide: (slide: CarouselSlide, index?: number) => {
|
|
250
|
+
const slideElement = document.createElement('div');
|
|
251
|
+
slideElement.className = 'mtrl-carousel__slide';
|
|
252
|
+
|
|
253
|
+
// Add slide content
|
|
254
|
+
if (slide.image) {
|
|
255
|
+
const img = document.createElement('img');
|
|
256
|
+
img.className = 'mtrl-carousel__image';
|
|
257
|
+
img.src = slide.image;
|
|
258
|
+
img.alt = slide.title || `Slide ${slides.length + 1}`;
|
|
259
|
+
slideElement.appendChild(img);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (slide.title) {
|
|
263
|
+
const title = document.createElement('h3');
|
|
264
|
+
title.className = 'mtrl-carousel__title';
|
|
265
|
+
title.textContent = slide.title;
|
|
266
|
+
slideElement.appendChild(title);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Insert slide at specified index or append
|
|
270
|
+
if (index !== undefined && index >= 0 && index <= slides.length) {
|
|
271
|
+
slides.splice(index, 0, slide);
|
|
272
|
+
const insertBeforeElement = index < slideElements.length ? slideElements[index] : null;
|
|
273
|
+
|
|
274
|
+
if (insertBeforeElement) {
|
|
275
|
+
slidesContainer.insertBefore(slideElement, insertBeforeElement);
|
|
276
|
+
slideElements.splice(index, 0, slideElement);
|
|
277
|
+
} else {
|
|
278
|
+
slidesContainer.appendChild(slideElement);
|
|
279
|
+
slideElements.push(slideElement);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
slides.push(slide);
|
|
283
|
+
slidesContainer.appendChild(slideElement);
|
|
284
|
+
slideElements.push(slideElement);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Update data-index attributes
|
|
288
|
+
slideElements.forEach((el, i) => {
|
|
289
|
+
el.setAttribute('data-index', i.toString());
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Create new indicator
|
|
293
|
+
const indicator = document.createElement('button');
|
|
294
|
+
indicator.className = 'mtrl-carousel__indicator';
|
|
295
|
+
indicator.setAttribute('data-index', (slides.length - 1).toString());
|
|
296
|
+
indicator.setAttribute('aria-label', `Go to slide ${slides.length}`);
|
|
297
|
+
|
|
298
|
+
indicator.addEventListener('click', () => {
|
|
299
|
+
const indicatorIndex = parseInt(indicator.getAttribute('data-index') || '0');
|
|
300
|
+
carousel.goTo(indicatorIndex);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
indicators.appendChild(indicator);
|
|
304
|
+
|
|
305
|
+
// Emit change event
|
|
306
|
+
emit('slideAdded', { slide, index });
|
|
307
|
+
|
|
308
|
+
return slidesAPI;
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
removeSlide: (index: number) => {
|
|
312
|
+
if (index >= 0 && index < slides.length) {
|
|
313
|
+
// Remove slide from arrays
|
|
314
|
+
const [removedSlide] = slides.splice(index, 1);
|
|
315
|
+
const [removedElement] = slideElements.splice(index, 1);
|
|
316
|
+
|
|
317
|
+
// Remove slide element from DOM
|
|
318
|
+
slidesContainer.removeChild(removedElement);
|
|
319
|
+
|
|
320
|
+
// Remove indicator
|
|
321
|
+
const indicatorToRemove = indicators.querySelector(`.mtrl-carousel__indicator[data-index="${index}"]`);
|
|
322
|
+
if (indicatorToRemove) {
|
|
323
|
+
indicators.removeChild(indicatorToRemove);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Update remaining indicators
|
|
327
|
+
const remainingIndicators = indicators.querySelectorAll('.mtrl-carousel__indicator');
|
|
328
|
+
remainingIndicators.forEach((ind, i) => {
|
|
329
|
+
ind.setAttribute('data-index', i.toString());
|
|
330
|
+
ind.setAttribute('aria-label', `Go to slide ${i + 1}`);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Update data-index attributes on remaining slides
|
|
334
|
+
slideElements.forEach((el, i) => {
|
|
335
|
+
el.setAttribute('data-index', i.toString());
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Adjust current slide if necessary
|
|
339
|
+
if (currentSlide >= slides.length) {
|
|
340
|
+
const newCurrentSlide = Math.max(0, slides.length - 1);
|
|
341
|
+
setActiveSlide(newCurrentSlide);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Emit change event
|
|
345
|
+
emit('slideRemoved', { slide: removedSlide, index });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return slidesAPI;
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
updateSlide: (index: number, slide: CarouselSlide) => {
|
|
352
|
+
if (index >= 0 && index < slides.length) {
|
|
353
|
+
slides[index] = { ...slides[index], ...slide };
|
|
354
|
+
const slideElement = slideElements[index];
|
|
355
|
+
|
|
356
|
+
// Update image if provided
|
|
357
|
+
if (slide.image) {
|
|
358
|
+
let img = slideElement.querySelector('.mtrl-carousel__image');
|
|
359
|
+
if (!img) {
|
|
360
|
+
img = document.createElement('img');
|
|
361
|
+
img.className = 'mtrl-carousel__image';
|
|
362
|
+
slideElement.appendChild(img);
|
|
363
|
+
}
|
|
364
|
+
(img as HTMLImageElement).src = slide.image;
|
|
365
|
+
(img as HTMLImageElement).alt = slide.title || `Slide ${index + 1}`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Update title if provided
|
|
369
|
+
if (slide.title !== undefined) {
|
|
370
|
+
let title = slideElement.querySelector('.mtrl-carousel__title');
|
|
371
|
+
if (!title && slide.title) {
|
|
372
|
+
title = document.createElement('h3');
|
|
373
|
+
title.className = 'mtrl-carousel__title';
|
|
374
|
+
slideElement.appendChild(title);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (title) {
|
|
378
|
+
if (slide.title) {
|
|
379
|
+
title.textContent = slide.title;
|
|
380
|
+
} else {
|
|
381
|
+
slideElement.removeChild(title);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Update accent color if provided
|
|
387
|
+
if (slide.accent) {
|
|
388
|
+
slideElement.style.setProperty('--carousel-accent-color', slide.accent);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Emit change event
|
|
392
|
+
emit('slideUpdated', { slide, index });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return slidesAPI;
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
getSlide: (index: number) => {
|
|
399
|
+
return (index >= 0 && index < slides.length) ? slides[index] : null;
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
getCount: () => {
|
|
403
|
+
return slides.length;
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
getElements: () => {
|
|
407
|
+
return [...slideElements];
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// Create the carousel component
|
|
412
|
+
const carousel: CarouselComponent = {
|
|
413
|
+
element,
|
|
414
|
+
slides: slidesAPI,
|
|
415
|
+
|
|
416
|
+
lifecycle: {
|
|
417
|
+
destroy: () => {
|
|
418
|
+
// Clean up event listeners and remove element
|
|
419
|
+
carousel.destroy();
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
getClass: (name: string) => {
|
|
424
|
+
return `${settings.prefix}-${name}`;
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
next: () => {
|
|
428
|
+
let nextSlide = currentSlide + 1;
|
|
429
|
+
|
|
430
|
+
if (nextSlide >= slides.length) {
|
|
431
|
+
if (settings.loop) {
|
|
432
|
+
nextSlide = 0;
|
|
433
|
+
} else {
|
|
434
|
+
nextSlide = slides.length - 1;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
setActiveSlide(nextSlide);
|
|
439
|
+
emit('slideChange', { current: nextSlide, previous: currentSlide });
|
|
440
|
+
|
|
441
|
+
return carousel;
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
prev: () => {
|
|
445
|
+
let prevSlide = currentSlide - 1;
|
|
446
|
+
|
|
447
|
+
if (prevSlide < 0) {
|
|
448
|
+
if (settings.loop) {
|
|
449
|
+
prevSlide = slides.length - 1;
|
|
450
|
+
} else {
|
|
451
|
+
prevSlide = 0;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
setActiveSlide(prevSlide);
|
|
456
|
+
emit('slideChange', { current: prevSlide, previous: currentSlide });
|
|
457
|
+
|
|
458
|
+
return carousel;
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
goTo: (index: number) => {
|
|
462
|
+
if (index >= 0 && index < slides.length) {
|
|
463
|
+
const previousSlide = currentSlide;
|
|
464
|
+
setActiveSlide(index);
|
|
465
|
+
emit('slideChange', { current: index, previous: previousSlide });
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return carousel;
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
getCurrentSlide: () => {
|
|
472
|
+
return currentSlide;
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
addSlide: (slide: CarouselSlide, index?: number) => {
|
|
476
|
+
slidesAPI.addSlide(slide, index);
|
|
477
|
+
return carousel;
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
removeSlide: (index: number) => {
|
|
481
|
+
slidesAPI.removeSlide(index);
|
|
482
|
+
return carousel;
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
enableLoop: () => {
|
|
486
|
+
settings.loop = true;
|
|
487
|
+
return carousel;
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
disableLoop: () => {
|
|
491
|
+
settings.loop = false;
|
|
492
|
+
return carousel;
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
setBorderRadius: (radius: number) => {
|
|
496
|
+
settings.borderRadius = radius;
|
|
497
|
+
element.style.setProperty('--carousel-border-radius', `${radius}px`);
|
|
498
|
+
return carousel;
|
|
499
|
+
},
|
|
500
|
+
|
|
501
|
+
setGap: (gap: number) => {
|
|
502
|
+
settings.gap = gap;
|
|
503
|
+
element.style.setProperty('--carousel-gap', `${gap}px`);
|
|
504
|
+
return carousel;
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
destroy: () => {
|
|
508
|
+
// Clean up event listeners
|
|
509
|
+
prevButton.removeEventListener('click', carousel.prev);
|
|
510
|
+
nextButton.removeEventListener('click', carousel.next);
|
|
511
|
+
|
|
512
|
+
// Remove the element from the DOM if it has a parent
|
|
513
|
+
if (element.parentNode) {
|
|
514
|
+
element.parentNode.removeChild(element);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Clear event handlers
|
|
518
|
+
for (const event in eventHandlers) {
|
|
519
|
+
eventHandlers[event] = [];
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
|
|
523
|
+
on: (event: string, handler: Function) => {
|
|
524
|
+
if (!eventHandlers[event]) {
|
|
525
|
+
eventHandlers[event] = [];
|
|
526
|
+
}
|
|
527
|
+
eventHandlers[event].push(handler);
|
|
528
|
+
return carousel;
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
off: (event: string, handler: Function) => {
|
|
532
|
+
if (eventHandlers[event]) {
|
|
533
|
+
eventHandlers[event] = eventHandlers[event].filter(h => h !== handler);
|
|
534
|
+
}
|
|
535
|
+
return carousel;
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
addClass: (...classes: string[]) => {
|
|
539
|
+
classes.forEach(className => element.classList.add(className));
|
|
540
|
+
return carousel;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Apply initial settings
|
|
545
|
+
carousel.setBorderRadius(settings.borderRadius);
|
|
546
|
+
carousel.setGap(settings.gap);
|
|
547
|
+
|
|
548
|
+
return carousel;
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
describe('Carousel Component', () => {
|
|
552
|
+
test('should create a carousel element', () => {
|
|
553
|
+
const carousel = createMockCarousel();
|
|
554
|
+
expect(carousel.element).toBeDefined();
|
|
555
|
+
expect(carousel.element.tagName).toBe('DIV');
|
|
556
|
+
expect(carousel.element.className).toContain('mtrl-carousel');
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test('should create carousel with specified layout', () => {
|
|
560
|
+
const layouts: CarouselLayout[] = ['multi-browse', 'uncontained', 'hero', 'full-screen'];
|
|
561
|
+
|
|
562
|
+
layouts.forEach(layout => {
|
|
563
|
+
const carousel = createMockCarousel({ layout });
|
|
564
|
+
expect(carousel.element.className).toContain(`mtrl-carousel--${layout}`);
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test('should create carousel with specified scroll behavior', () => {
|
|
569
|
+
const behaviors: CarouselScrollBehavior[] = ['default', 'snap'];
|
|
570
|
+
|
|
571
|
+
behaviors.forEach(behavior => {
|
|
572
|
+
const carousel = createMockCarousel({ scrollBehavior: behavior });
|
|
573
|
+
expect(carousel.element.className).toContain(`mtrl-carousel--${behavior}`);
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test('should create carousel with slides', () => {
|
|
578
|
+
const slides: CarouselSlide[] = [
|
|
579
|
+
{ image: 'image1.jpg', title: 'Slide 1', description: 'Description 1' },
|
|
580
|
+
{ image: 'image2.jpg', title: 'Slide 2', description: 'Description 2' },
|
|
581
|
+
{ image: 'image3.jpg', title: 'Slide 3', description: 'Description 3' }
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
const carousel = createMockCarousel({ slides });
|
|
585
|
+
|
|
586
|
+
const slideElements = carousel.element.querySelectorAll('.mtrl-carousel__slide');
|
|
587
|
+
expect(slideElements.length).toBe(3);
|
|
588
|
+
|
|
589
|
+
const imageElements = carousel.element.querySelectorAll('.mtrl-carousel__image');
|
|
590
|
+
expect(imageElements.length).toBe(3);
|
|
591
|
+
|
|
592
|
+
const titleElements = carousel.element.querySelectorAll('.mtrl-carousel__title');
|
|
593
|
+
expect(titleElements.length).toBe(3);
|
|
594
|
+
expect(titleElements[0].textContent).toBe('Slide 1');
|
|
595
|
+
expect(titleElements[1].textContent).toBe('Slide 2');
|
|
596
|
+
expect(titleElements[2].textContent).toBe('Slide 3');
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('should create navigation controls', () => {
|
|
600
|
+
const carousel = createMockCarousel();
|
|
601
|
+
|
|
602
|
+
const prevButton = carousel.element.querySelector('.mtrl-carousel__prev');
|
|
603
|
+
expect(prevButton).toBeDefined();
|
|
604
|
+
|
|
605
|
+
const nextButton = carousel.element.querySelector('.mtrl-carousel__next');
|
|
606
|
+
expect(nextButton).toBeDefined();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
test('should create slide indicators', () => {
|
|
610
|
+
const slides: CarouselSlide[] = [
|
|
611
|
+
{ image: 'image1.jpg' },
|
|
612
|
+
{ image: 'image2.jpg' },
|
|
613
|
+
{ image: 'image3.jpg' }
|
|
614
|
+
];
|
|
615
|
+
|
|
616
|
+
const carousel = createMockCarousel({ slides });
|
|
617
|
+
|
|
618
|
+
const indicators = carousel.element.querySelectorAll('.mtrl-carousel__indicator');
|
|
619
|
+
expect(indicators.length).toBe(3);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
test('should show "Show All" link by default', () => {
|
|
623
|
+
const carousel = createMockCarousel();
|
|
624
|
+
const showAllLink = carousel.element.querySelector('.mtrl-carousel__show-all');
|
|
625
|
+
expect(showAllLink).toBeDefined();
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test('should hide "Show All" link when specified', () => {
|
|
629
|
+
const carousel = createMockCarousel({ showAllLink: false });
|
|
630
|
+
const showAllLink = carousel.element.querySelector('.mtrl-carousel__show-all');
|
|
631
|
+
expect(showAllLink).toBeNull();
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test('should set initial slide', () => {
|
|
635
|
+
const slides: CarouselSlide[] = [
|
|
636
|
+
{ image: 'image1.jpg' },
|
|
637
|
+
{ image: 'image2.jpg' },
|
|
638
|
+
{ image: 'image3.jpg' }
|
|
639
|
+
];
|
|
640
|
+
|
|
641
|
+
const carousel = createMockCarousel({ slides, initialSlide: 1 });
|
|
642
|
+
|
|
643
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
644
|
+
|
|
645
|
+
const activeIndicator = carousel.element.querySelector('.mtrl-carousel__indicator--active');
|
|
646
|
+
expect(activeIndicator).toBeDefined();
|
|
647
|
+
expect(activeIndicator?.getAttribute('data-index')).toBe('1');
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test('should navigate to next slide', () => {
|
|
651
|
+
const slides: CarouselSlide[] = [
|
|
652
|
+
{ image: 'image1.jpg' },
|
|
653
|
+
{ image: 'image2.jpg' },
|
|
654
|
+
{ image: 'image3.jpg' }
|
|
655
|
+
];
|
|
656
|
+
|
|
657
|
+
const carousel = createMockCarousel({ slides });
|
|
658
|
+
|
|
659
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
660
|
+
|
|
661
|
+
carousel.next();
|
|
662
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
663
|
+
|
|
664
|
+
carousel.next();
|
|
665
|
+
expect(carousel.getCurrentSlide()).toBe(2);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test('should navigate to previous slide', () => {
|
|
669
|
+
const slides: CarouselSlide[] = [
|
|
670
|
+
{ image: 'image1.jpg' },
|
|
671
|
+
{ image: 'image2.jpg' },
|
|
672
|
+
{ image: 'image3.jpg' }
|
|
673
|
+
];
|
|
674
|
+
|
|
675
|
+
const carousel = createMockCarousel({ slides, initialSlide: 2 });
|
|
676
|
+
|
|
677
|
+
expect(carousel.getCurrentSlide()).toBe(2);
|
|
678
|
+
|
|
679
|
+
carousel.prev();
|
|
680
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
681
|
+
|
|
682
|
+
carousel.prev();
|
|
683
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test('should loop to first slide when reaching the end', () => {
|
|
687
|
+
const slides: CarouselSlide[] = [
|
|
688
|
+
{ image: 'image1.jpg' },
|
|
689
|
+
{ image: 'image2.jpg' },
|
|
690
|
+
{ image: 'image3.jpg' }
|
|
691
|
+
];
|
|
692
|
+
|
|
693
|
+
const carousel = createMockCarousel({ slides, loop: true });
|
|
694
|
+
|
|
695
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
696
|
+
|
|
697
|
+
carousel.next();
|
|
698
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
699
|
+
|
|
700
|
+
carousel.next();
|
|
701
|
+
expect(carousel.getCurrentSlide()).toBe(2);
|
|
702
|
+
|
|
703
|
+
carousel.next();
|
|
704
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test('should loop to last slide when going back from first', () => {
|
|
708
|
+
const slides: CarouselSlide[] = [
|
|
709
|
+
{ image: 'image1.jpg' },
|
|
710
|
+
{ image: 'image2.jpg' },
|
|
711
|
+
{ image: 'image3.jpg' }
|
|
712
|
+
];
|
|
713
|
+
|
|
714
|
+
const carousel = createMockCarousel({ slides, loop: true });
|
|
715
|
+
|
|
716
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
717
|
+
|
|
718
|
+
carousel.prev();
|
|
719
|
+
expect(carousel.getCurrentSlide()).toBe(2);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test('should not loop when loop is disabled', () => {
|
|
723
|
+
const slides: CarouselSlide[] = [
|
|
724
|
+
{ image: 'image1.jpg' },
|
|
725
|
+
{ image: 'image2.jpg' },
|
|
726
|
+
{ image: 'image3.jpg' }
|
|
727
|
+
];
|
|
728
|
+
|
|
729
|
+
const carousel = createMockCarousel({ slides, loop: false });
|
|
730
|
+
|
|
731
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
732
|
+
|
|
733
|
+
carousel.prev();
|
|
734
|
+
expect(carousel.getCurrentSlide()).toBe(0); // Stays at first slide
|
|
735
|
+
|
|
736
|
+
carousel.next();
|
|
737
|
+
carousel.next();
|
|
738
|
+
expect(carousel.getCurrentSlide()).toBe(2);
|
|
739
|
+
|
|
740
|
+
carousel.next();
|
|
741
|
+
expect(carousel.getCurrentSlide()).toBe(2); // Stays at last slide
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test('should go to specific slide', () => {
|
|
745
|
+
const slides: CarouselSlide[] = [
|
|
746
|
+
{ image: 'image1.jpg' },
|
|
747
|
+
{ image: 'image2.jpg' },
|
|
748
|
+
{ image: 'image3.jpg' }
|
|
749
|
+
];
|
|
750
|
+
|
|
751
|
+
const carousel = createMockCarousel({ slides });
|
|
752
|
+
|
|
753
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
754
|
+
|
|
755
|
+
carousel.goTo(2);
|
|
756
|
+
expect(carousel.getCurrentSlide()).toBe(2);
|
|
757
|
+
|
|
758
|
+
carousel.goTo(1);
|
|
759
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
760
|
+
|
|
761
|
+
// Should ignore invalid indices
|
|
762
|
+
carousel.goTo(-1);
|
|
763
|
+
expect(carousel.getCurrentSlide()).toBe(1); // Remains unchanged
|
|
764
|
+
|
|
765
|
+
carousel.goTo(10);
|
|
766
|
+
expect(carousel.getCurrentSlide()).toBe(1); // Remains unchanged
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test('should add a new slide', () => {
|
|
770
|
+
const slides: CarouselSlide[] = [
|
|
771
|
+
{ image: 'image1.jpg' },
|
|
772
|
+
{ image: 'image2.jpg' }
|
|
773
|
+
];
|
|
774
|
+
|
|
775
|
+
const carousel = createMockCarousel({ slides });
|
|
776
|
+
|
|
777
|
+
expect(carousel.slides.getCount()).toBe(2);
|
|
778
|
+
|
|
779
|
+
const newSlide: CarouselSlide = { image: 'image3.jpg', title: 'New Slide' };
|
|
780
|
+
carousel.addSlide(newSlide);
|
|
781
|
+
|
|
782
|
+
expect(carousel.slides.getCount()).toBe(3);
|
|
783
|
+
expect(carousel.slides.getSlide(2)).toEqual(newSlide);
|
|
784
|
+
|
|
785
|
+
const slideElements = carousel.slides.getElements();
|
|
786
|
+
expect(slideElements.length).toBe(3);
|
|
787
|
+
|
|
788
|
+
const indicators = carousel.element.querySelectorAll('.mtrl-carousel__indicator');
|
|
789
|
+
expect(indicators.length).toBe(3);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test('should add a slide at specific index', () => {
|
|
793
|
+
const slides: CarouselSlide[] = [
|
|
794
|
+
{ image: 'image1.jpg' },
|
|
795
|
+
{ image: 'image3.jpg' }
|
|
796
|
+
];
|
|
797
|
+
|
|
798
|
+
const carousel = createMockCarousel({ slides });
|
|
799
|
+
|
|
800
|
+
const newSlide: CarouselSlide = { image: 'image2.jpg', title: 'Middle Slide' };
|
|
801
|
+
carousel.addSlide(newSlide, 1);
|
|
802
|
+
|
|
803
|
+
expect(carousel.slides.getCount()).toBe(3);
|
|
804
|
+
expect(carousel.slides.getSlide(1)).toEqual(newSlide);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
test('should remove a slide', () => {
|
|
808
|
+
// Store the original slides separately for reference
|
|
809
|
+
const originalSlides = [
|
|
810
|
+
{ image: 'image1.jpg' },
|
|
811
|
+
{ image: 'image2.jpg' },
|
|
812
|
+
{ image: 'image3.jpg' }
|
|
813
|
+
];
|
|
814
|
+
|
|
815
|
+
// Create a copy for the carousel
|
|
816
|
+
const slides = [...originalSlides];
|
|
817
|
+
|
|
818
|
+
const carousel = createMockCarousel({ slides });
|
|
819
|
+
|
|
820
|
+
expect(carousel.slides.getCount()).toBe(3);
|
|
821
|
+
|
|
822
|
+
carousel.removeSlide(1);
|
|
823
|
+
|
|
824
|
+
expect(carousel.slides.getCount()).toBe(2);
|
|
825
|
+
|
|
826
|
+
// Check that the first and third slides from the original array remain
|
|
827
|
+
expect(carousel.slides.getSlide(0)?.image).toBe('image1.jpg');
|
|
828
|
+
expect(carousel.slides.getSlide(1)?.image).toBe('image3.jpg');
|
|
829
|
+
|
|
830
|
+
const slideElements = carousel.slides.getElements();
|
|
831
|
+
expect(slideElements.length).toBe(2);
|
|
832
|
+
|
|
833
|
+
const indicators = carousel.element.querySelectorAll('.mtrl-carousel__indicator');
|
|
834
|
+
expect(indicators.length).toBe(2);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
test('should update a slide', () => {
|
|
838
|
+
const slides: CarouselSlide[] = [
|
|
839
|
+
{ image: 'image1.jpg', title: 'Original Title' },
|
|
840
|
+
{ image: 'image2.jpg' }
|
|
841
|
+
];
|
|
842
|
+
|
|
843
|
+
const carousel = createMockCarousel({ slides });
|
|
844
|
+
|
|
845
|
+
const updatedSlide: CarouselSlide = {
|
|
846
|
+
title: 'Updated Title',
|
|
847
|
+
accent: '#FF0000'
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
carousel.slides.updateSlide(0, updatedSlide);
|
|
851
|
+
|
|
852
|
+
const updatedSlideData = carousel.slides.getSlide(0);
|
|
853
|
+
expect(updatedSlideData?.image).toBe('image1.jpg'); // Original property
|
|
854
|
+
expect(updatedSlideData?.title).toBe('Updated Title'); // Updated property
|
|
855
|
+
expect(updatedSlideData?.accent).toBe('#FF0000'); // New property
|
|
856
|
+
|
|
857
|
+
const slideElement = carousel.slides.getElements()[0];
|
|
858
|
+
const title = slideElement.querySelector('.mtrl-carousel__title');
|
|
859
|
+
expect(title?.textContent).toBe('Updated Title');
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
test('should enable and disable loop mode', () => {
|
|
863
|
+
const slides: CarouselSlide[] = [
|
|
864
|
+
{ image: 'image1.jpg' },
|
|
865
|
+
{ image: 'image2.jpg' }
|
|
866
|
+
];
|
|
867
|
+
|
|
868
|
+
const carousel = createMockCarousel({ slides, loop: false });
|
|
869
|
+
|
|
870
|
+
// With loop disabled, we should stay at the first slide
|
|
871
|
+
carousel.prev();
|
|
872
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
873
|
+
|
|
874
|
+
// Enable loop
|
|
875
|
+
carousel.enableLoop();
|
|
876
|
+
|
|
877
|
+
// Now we should loop to the last slide
|
|
878
|
+
carousel.prev();
|
|
879
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
880
|
+
|
|
881
|
+
// Go back to the first slide
|
|
882
|
+
carousel.next();
|
|
883
|
+
expect(carousel.getCurrentSlide()).toBe(0);
|
|
884
|
+
|
|
885
|
+
// Disable loop
|
|
886
|
+
carousel.disableLoop();
|
|
887
|
+
|
|
888
|
+
// Move to last slide
|
|
889
|
+
carousel.next();
|
|
890
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
891
|
+
|
|
892
|
+
// With loop disabled, we should stay at the last slide
|
|
893
|
+
carousel.next();
|
|
894
|
+
expect(carousel.getCurrentSlide()).toBe(1);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test('should set border radius', () => {
|
|
898
|
+
const carousel = createMockCarousel();
|
|
899
|
+
|
|
900
|
+
carousel.setBorderRadius(24);
|
|
901
|
+
expect(carousel.element.style.getPropertyValue('--carousel-border-radius')).toBe('24px');
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
test('should set gap between slides', () => {
|
|
905
|
+
const carousel = createMockCarousel();
|
|
906
|
+
|
|
907
|
+
carousel.setGap(16);
|
|
908
|
+
expect(carousel.element.style.getPropertyValue('--carousel-gap')).toBe('16px');
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
test('should add event listener', () => {
|
|
912
|
+
const carousel = createMockCarousel();
|
|
913
|
+
let eventFired = false;
|
|
914
|
+
|
|
915
|
+
carousel.on('slideChange', () => {
|
|
916
|
+
eventFired = true;
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
carousel.next();
|
|
920
|
+
expect(eventFired).toBe(true);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test('should remove event listener', () => {
|
|
924
|
+
const carousel = createMockCarousel();
|
|
925
|
+
let eventCount = 0;
|
|
926
|
+
|
|
927
|
+
const handler = () => {
|
|
928
|
+
eventCount++;
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
carousel.on('slideChange', handler);
|
|
932
|
+
|
|
933
|
+
carousel.next();
|
|
934
|
+
expect(eventCount).toBe(1);
|
|
935
|
+
|
|
936
|
+
carousel.off('slideChange', handler);
|
|
937
|
+
|
|
938
|
+
carousel.next();
|
|
939
|
+
expect(eventCount).toBe(1); // Counter should not increase
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
test('should be properly destroyed', () => {
|
|
943
|
+
const carousel = createMockCarousel();
|
|
944
|
+
document.body.appendChild(carousel.element);
|
|
945
|
+
|
|
946
|
+
expect(document.body.contains(carousel.element)).toBe(true);
|
|
947
|
+
|
|
948
|
+
carousel.destroy();
|
|
949
|
+
expect(document.body.contains(carousel.element)).toBe(false);
|
|
950
|
+
});
|
|
951
|
+
});
|