@excom/content-carousel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.rush/temp/chunked-rush-logs/content-carousel.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/content-carousel.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/content-carousel.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +5 -0
  15. package/content-carousel-slide.ts +19 -0
  16. package/content-carousel.ts +218 -0
  17. package/index.css +5 -0
  18. package/index.ts +24 -0
  19. package/package.json +43 -0
  20. package/rush-logs/content-carousel.apply-exports.cache.log +1 -0
  21. package/rush-logs/content-carousel.apply-exports.log +1 -0
  22. package/rush-logs/content-carousel.build_docs.cache.log +1 -0
  23. package/rush-logs/content-carousel.build_docs.log +1 -0
  24. package/rush-logs/content-carousel.build_package-metas.cache.log +1 -0
  25. package/rush-logs/content-carousel.build_package-metas.log +1 -0
  26. package/src/content-carousel.css +239 -0
  27. package/support/custom-elements.json +398 -0
  28. package/support/demos/fade.html +7 -0
  29. package/support/demos/manual.html +9 -0
  30. package/support/demos/simple.html +7 -0
  31. package/support/dist-docs/content-carousel-slide.md +20 -0
  32. package/support/dist-docs/content-carousel.md +184 -0
  33. package/support/docs/INTERNAL.md +4 -0
  34. package/support/docs/README.md +66 -0
  35. package/support/package-meta.json +287 -0
  36. package/support/tests/content-carousel.test.ts +534 -0
  37. package/support/tests/fade.view.test.ts +23 -0
  38. package/support/tests/manual.view.test.ts +34 -0
  39. package/support/tests/simple.view.test.ts +24 -0
  40. package/tsconfig.json +5 -0
@@ -0,0 +1,534 @@
1
+ import {
2
+ afterEach,
3
+ describe,
4
+ expect,
5
+ fixture,
6
+ it,
7
+ vi,
8
+ wait,
9
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
10
+ import "../../index";
11
+ import { invokeCommand } from "@excom/neutron";
12
+
13
+ /** Invoke a command and let the handler's microtask run. */
14
+ const command = async (el: Element, name: string) => {
15
+ invokeCommand(el, name);
16
+ await Promise.resolve();
17
+ };
18
+ import type { ContentCarouselSlideChangedDetail } from "../../content-carousel";
19
+
20
+ describe("content-carousel-slide", () => {
21
+ afterEach(() => {
22
+ document.body.innerHTML = "";
23
+ vi.restoreAllMocks();
24
+ });
25
+
26
+ it("has isActive boolean", async () => {
27
+ const slide = fixture<HTMLContentCarouselSlideElement>(
28
+ `<content-carousel-slide></content-carousel-slide>`,
29
+ );
30
+ expect(slide).dom.to.equalTag(
31
+ `<content-carousel-slide></content-carousel-slide>`,
32
+ );
33
+ slide.setAttribute("is-active", "");
34
+ expect(slide).dom.to.equalTag(
35
+ `<content-carousel-slide is-active></content-carousel-slide>`,
36
+ );
37
+ });
38
+ });
39
+
40
+ describe("content-carousel", () => {
41
+ afterEach(() => {
42
+ document.body.innerHTML = "";
43
+ vi.restoreAllMocks();
44
+ });
45
+
46
+ it("defaults to first slide if no slides are active", async () => {
47
+ const carousel = fixture<HTMLContentCarouselElement>(
48
+ `<content-carousel>
49
+ <content-carousel-slide></content-carousel-slide>
50
+ <content-carousel-slide></content-carousel-slide>
51
+ </content-carousel>`,
52
+ );
53
+ const firstSlide = carousel.querySelector("content-carousel-slide");
54
+ expect(carousel.getActiveSlide()).to.equal(firstSlide);
55
+ });
56
+
57
+ it("auto-plays", async () => {
58
+ const carousel = fixture<HTMLContentCarouselElement>(
59
+ `<content-carousel auto-play="0.1">
60
+ <content-carousel-slide></content-carousel-slide>
61
+ <content-carousel-slide></content-carousel-slide>
62
+ </content-carousel>`,
63
+ );
64
+ const firstSlide = carousel.getActiveSlide();
65
+ await wait(101);
66
+ const secondSlide = carousel.getActiveSlide();
67
+ expect(firstSlide?.localName).to.equal("content-carousel-slide");
68
+ expect(secondSlide?.localName).to.equal("content-carousel-slide");
69
+ expect(secondSlide).not.to.equal(firstSlide);
70
+ });
71
+
72
+ it("navigates", async () => {
73
+ const carousel = fixture<HTMLContentCarouselElement>(
74
+ `<content-carousel>
75
+ <content-carousel-slide></content-carousel-slide>
76
+ <content-carousel-slide is-active></content-carousel-slide>
77
+ <content-carousel-slide></content-carousel-slide>
78
+ </content-carousel>`,
79
+ );
80
+ const secondSlide = carousel.getActiveSlide()!;
81
+ await command(carousel, "--next");
82
+ expect(secondSlide).dom.to.equalTag(
83
+ `<content-carousel-slide></content-carousel-slide>`,
84
+ );
85
+ expect(carousel.getActiveSlide()).not.to.equal(secondSlide);
86
+ expect(secondSlide.nextElementSibling).dom.to.equalTag(
87
+ `<content-carousel-slide is-active></content-carousel-slide>`,
88
+ );
89
+ await command(carousel, "--back");
90
+ expect(secondSlide).dom.to.equalTag(
91
+ `<content-carousel-slide is-active></content-carousel-slide>`,
92
+ );
93
+ });
94
+
95
+ it("stops auto-play on manual navigation", async () => {
96
+ const carousel = fixture<HTMLContentCarouselElement>(
97
+ `<content-carousel auto-play="0.1">
98
+ <content-carousel-slide></content-carousel-slide>
99
+ <content-carousel-slide></content-carousel-slide>
100
+ </content-carousel>`,
101
+ );
102
+ const firstSlide = carousel.querySelectorAll("content-carousel-slide")[0]!;
103
+ const secondSlide = carousel.querySelectorAll("content-carousel-slide")[1]!;
104
+ await command(carousel, "--next");
105
+ await wait(105);
106
+ expect(carousel.getActiveSlide()).to.equal(secondSlide);
107
+ expect(firstSlide).dom.to.equalTag(
108
+ `<content-carousel-slide></content-carousel-slide>`,
109
+ );
110
+ expect(secondSlide).dom.to.equalTag(
111
+ `<content-carousel-slide is-active></content-carousel-slide>`,
112
+ );
113
+ expect(carousel).dom.to.equalTag(
114
+ `<content-carousel auto-play="0.1" auto-play-stopped last-move="forward"></content-carousel>`,
115
+ );
116
+ });
117
+ });
118
+
119
+ describe("content-carousel (navigation edge cases)", () => {
120
+ afterEach(() => {
121
+ document.body.innerHTML = "";
122
+ vi.restoreAllMocks();
123
+ });
124
+
125
+ const build = (attrs = "", active = 0) =>
126
+ fixture<HTMLContentCarouselElement>(
127
+ `<content-carousel ${attrs}>
128
+ <content-carousel-slide ${active === 0 ? "is-active" : ""}></content-carousel-slide>
129
+ <content-carousel-slide ${active === 1 ? "is-active" : ""}></content-carousel-slide>
130
+ <content-carousel-slide ${active === 2 ? "is-active" : ""}></content-carousel-slide>
131
+ </content-carousel>`,
132
+ );
133
+
134
+ it("wraps from the last slide to the first on next", async () => {
135
+ const carousel = build("", 2);
136
+ const slides = carousel.querySelectorAll("content-carousel-slide");
137
+ await command(carousel, "--next");
138
+ expect(carousel.getActiveSlide()).toBe(slides[0]);
139
+ expect(slides[2].hasAttribute("is-active")).toBe(false);
140
+ expect(carousel).dom.to.equalTag(
141
+ `<content-carousel last-move="forward"></content-carousel>`,
142
+ );
143
+ });
144
+
145
+ it("wraps from the first slide to the last on back", async () => {
146
+ const carousel = build("", 0);
147
+ const slides = carousel.querySelectorAll("content-carousel-slide");
148
+ await command(carousel, "--back");
149
+ expect(carousel.getActiveSlide()).toBe(slides[2]);
150
+ expect(slides[0].hasAttribute("is-active")).toBe(false);
151
+ expect(carousel).dom.to.equalTag(
152
+ `<content-carousel last-move="back"></content-carousel>`,
153
+ );
154
+ });
155
+
156
+ it("fires content-carousel-slide-changed with the new and previous slides", async () => {
157
+ const carousel = build("", 1);
158
+ const slides = carousel.querySelectorAll("content-carousel-slide");
159
+ const details: ContentCarouselSlideChangedDetail[] = [];
160
+ carousel.addEventListener("content-carousel-slide-changed", (e) => {
161
+ details.push((e as CustomEvent).detail);
162
+ });
163
+ await command(carousel, "--next");
164
+ await command(carousel, "--back");
165
+ expect(details).toEqual([
166
+ { activeSlide: slides[2], previousSlide: slides[1] },
167
+ { activeSlide: slides[1], previousSlide: slides[2] },
168
+ ]);
169
+ });
170
+
171
+ it("stops navigation events from bubbling past the carousel", async () => {
172
+ const wrapper = fixture<HTMLDivElement>(
173
+ `<div>
174
+ <content-carousel>
175
+ <content-carousel-slide is-active></content-carousel-slide>
176
+ <content-carousel-slide></content-carousel-slide>
177
+ </content-carousel>
178
+ </div>`,
179
+ );
180
+ const seen = vi.fn();
181
+ wrapper.addEventListener("command", seen);
182
+ invokeCommand(wrapper.querySelector("content-carousel")!, "--next");
183
+ expect(seen).not.toHaveBeenCalled();
184
+ });
185
+
186
+ it("does nothing useful without slides", async () => {
187
+ const carousel = fixture<HTMLContentCarouselElement>(
188
+ `<content-carousel></content-carousel>`,
189
+ );
190
+ const details: ContentCarouselSlideChangedDetail[] = [];
191
+ carousel.addEventListener("content-carousel-slide-changed", (e) => {
192
+ details.push((e as CustomEvent).detail);
193
+ });
194
+ expect(carousel.getActiveSlide()).toBe(null);
195
+ await command(carousel, "--next");
196
+ expect(details).toEqual([{ activeSlide: null, previousSlide: null }]);
197
+ expect(carousel).dom.to.equalTag(
198
+ `<content-carousel last-move="forward"></content-carousel>`,
199
+ );
200
+ });
201
+
202
+ it("does not mark auto-play stopped on manual navigation when auto-play is unset", async () => {
203
+ const carousel = build("", 0);
204
+ await command(carousel, "--next");
205
+ expect(carousel.autoPlayStopped).toBe(false);
206
+ expect(carousel.autoPlayIntervalId).toBe(null);
207
+ });
208
+ });
209
+
210
+ describe("content-carousel (provision)", () => {
211
+ afterEach(() => {
212
+ document.body.innerHTML = "";
213
+ vi.restoreAllMocks();
214
+ });
215
+
216
+ const build = (active = 0) =>
217
+ fixture<HTMLContentCarouselElement>(
218
+ `<content-carousel>
219
+ <content-carousel-slide ${active === 0 ? "is-active" : ""}></content-carousel-slide>
220
+ <content-carousel-slide ${active === 1 ? "is-active" : ""}></content-carousel-slide>
221
+ <content-carousel-slide ${active === 2 ? "is-active" : ""}></content-carousel-slide>
222
+ </content-carousel>`,
223
+ );
224
+
225
+ it("reads the initial position on connect", async () => {
226
+ const carousel = build(1);
227
+ expect(carousel.provision).toEqual({ index: 1, count: 3, lastMove: null });
228
+ });
229
+
230
+ it("defaults to the first slide when none is active", async () => {
231
+ const carousel = fixture<HTMLContentCarouselElement>(
232
+ `<content-carousel>
233
+ <content-carousel-slide></content-carousel-slide>
234
+ <content-carousel-slide></content-carousel-slide>
235
+ </content-carousel>`,
236
+ );
237
+ expect(carousel.provision).toEqual({ index: 0, count: 2, lastMove: null });
238
+ });
239
+
240
+ it("reports -1 / 0 without slides", async () => {
241
+ const carousel = fixture<HTMLContentCarouselElement>(
242
+ `<content-carousel></content-carousel>`,
243
+ );
244
+ expect(carousel.provision).toEqual({ index: -1, count: 0, lastMove: null });
245
+ });
246
+
247
+ it("updates after next, back and wrap-around, alongside slide-changed", async () => {
248
+ const carousel = build(1);
249
+ const provisionSpy = vi.fn();
250
+ const changedSpy = vi.fn();
251
+ carousel.addEventListener("neutron-provision", provisionSpy);
252
+ carousel.addEventListener("content-carousel-slide-changed", changedSpy);
253
+
254
+ await command(carousel, "--next");
255
+ expect(carousel.provision).toEqual({
256
+ index: 2,
257
+ count: 3,
258
+ lastMove: "forward",
259
+ });
260
+
261
+ // wrap forward
262
+ await command(carousel, "--next");
263
+ expect(carousel.provision).toEqual({
264
+ index: 0,
265
+ count: 3,
266
+ lastMove: "forward",
267
+ });
268
+
269
+ // wrap back
270
+ await command(carousel, "--back");
271
+ expect(carousel.provision).toEqual({ index: 2, count: 3, lastMove: "back" });
272
+
273
+ expect(provisionSpy).toHaveBeenCalledTimes(3);
274
+ expect(changedSpy).toHaveBeenCalledTimes(3);
275
+ expect(carousel).dom.to.equalTag(
276
+ `<content-carousel last-move="back"></content-carousel>`,
277
+ );
278
+ });
279
+
280
+ it("assigns a new object on every move", async () => {
281
+ const carousel = build(0);
282
+ const before = carousel.provision;
283
+ await command(carousel, "--next");
284
+ expect(carousel.provision).not.toBe(before);
285
+ expect(before).toEqual({ index: 0, count: 3, lastMove: null });
286
+ });
287
+
288
+ it("counts only its own slides when carousels nest", async () => {
289
+ const outer = fixture<HTMLContentCarouselElement>(
290
+ `<content-carousel>
291
+ <content-carousel-slide>
292
+ <content-carousel>
293
+ <content-carousel-slide></content-carousel-slide>
294
+ <content-carousel-slide is-active></content-carousel-slide>
295
+ </content-carousel>
296
+ </content-carousel-slide>
297
+ <content-carousel-slide is-active></content-carousel-slide>
298
+ <content-carousel-slide></content-carousel-slide>
299
+ </content-carousel>`,
300
+ );
301
+ const inner = outer.querySelector<HTMLContentCarouselElement>(
302
+ "content-carousel",
303
+ )!;
304
+ expect(outer.provision).toEqual({ index: 1, count: 3, lastMove: null });
305
+ expect(inner.provision).toEqual({ index: 1, count: 2, lastMove: null });
306
+
307
+ await command(inner, "--next");
308
+ expect(inner.provision).toEqual({ index: 0, count: 2, lastMove: "forward" });
309
+ expect(outer.provision).toEqual({ index: 1, count: 3, lastMove: null });
310
+
311
+ await command(outer, "--back");
312
+ expect(outer.provision).toEqual({ index: 0, count: 3, lastMove: "back" });
313
+ expect(inner.provision).toEqual({ index: 0, count: 2, lastMove: "forward" });
314
+ });
315
+ });
316
+
317
+ describe("content-carousel (auto-play lifecycle)", () => {
318
+ afterEach(() => {
319
+ document.body.innerHTML = "";
320
+ vi.restoreAllMocks();
321
+ });
322
+
323
+ it("re-creates the interval when auto-play changes and drops it when unset", async () => {
324
+ const carousel = fixture<HTMLContentCarouselElement>(
325
+ `<content-carousel auto-play="10">
326
+ <content-carousel-slide is-active></content-carousel-slide>
327
+ <content-carousel-slide></content-carousel-slide>
328
+ </content-carousel>`,
329
+ );
330
+ const firstId = carousel.autoPlayIntervalId;
331
+ expect(firstId).not.toBe(null);
332
+ const clearSpy = vi.spyOn(globalThis, "clearInterval");
333
+
334
+ carousel.autoPlay = 0.05;
335
+ expect(clearSpy).toHaveBeenCalledWith(firstId);
336
+ const secondId = carousel.autoPlayIntervalId;
337
+ expect(secondId).not.toBe(null);
338
+ expect(secondId).not.toBe(firstId);
339
+ const slides = carousel.querySelectorAll("content-carousel-slide");
340
+ await wait(80);
341
+ expect(carousel.getActiveSlide()).toBe(slides[1]);
342
+
343
+ carousel.autoPlay = null;
344
+ expect(clearSpy).toHaveBeenCalledWith(secondId);
345
+ expect(carousel.autoPlayIntervalId).toBe(null);
346
+ await wait(80);
347
+ expect(carousel.getActiveSlide()).toBe(slides[1]);
348
+ });
349
+
350
+ it("does not start the interval when auto-play-stopped is preset", async () => {
351
+ const carousel = fixture<HTMLContentCarouselElement>(
352
+ `<content-carousel auto-play="0.05" auto-play-stopped>
353
+ <content-carousel-slide is-active></content-carousel-slide>
354
+ <content-carousel-slide></content-carousel-slide>
355
+ </content-carousel>`,
356
+ );
357
+ const slides = carousel.querySelectorAll("content-carousel-slide");
358
+ expect(carousel.autoPlayIntervalId).toBe(null);
359
+ await wait(80);
360
+ expect(carousel.getActiveSlide()).toBe(slides[0]);
361
+ });
362
+
363
+ it("pauses via auto-play-stopped and resumes when it is unset", async () => {
364
+ const carousel = fixture<HTMLContentCarouselElement>(
365
+ `<content-carousel auto-play="0.05">
366
+ <content-carousel-slide is-active></content-carousel-slide>
367
+ <content-carousel-slide></content-carousel-slide>
368
+ </content-carousel>`,
369
+ );
370
+ const slides = carousel.querySelectorAll("content-carousel-slide");
371
+ carousel.autoPlayStopped = true;
372
+ expect(carousel.autoPlayIntervalId).toBe(null);
373
+ await wait(80);
374
+ expect(carousel.getActiveSlide()).toBe(slides[0]);
375
+ carousel.autoPlayStopped = false;
376
+ // unsetting the flag alone does not restart; a new auto-play value does
377
+ expect(carousel.autoPlayIntervalId).toBe(null);
378
+ carousel.autoPlay = 0.04;
379
+ expect(carousel.autoPlayIntervalId).not.toBe(null);
380
+ await wait(80);
381
+ expect(carousel.getActiveSlide()).toBe(slides[1]);
382
+ });
383
+
384
+ it("tolerates auto-play-stopped without an interval", async () => {
385
+ const carousel = fixture<HTMLContentCarouselElement>(
386
+ `<content-carousel>
387
+ <content-carousel-slide is-active></content-carousel-slide>
388
+ </content-carousel>`,
389
+ );
390
+ const clearSpy = vi.spyOn(globalThis, "clearInterval");
391
+ carousel.autoPlayStopped = true;
392
+ expect(clearSpy).not.toHaveBeenCalled();
393
+ expect(carousel.autoPlayIntervalId).toBe(null);
394
+ expect(carousel).dom.to.equalTag(
395
+ `<content-carousel auto-play-stopped></content-carousel>`,
396
+ );
397
+ });
398
+
399
+ it("stops auto-play on disconnect", async () => {
400
+ const carousel = fixture<HTMLContentCarouselElement>(
401
+ `<content-carousel auto-play="0.05">
402
+ <content-carousel-slide is-active></content-carousel-slide>
403
+ <content-carousel-slide></content-carousel-slide>
404
+ </content-carousel>`,
405
+ );
406
+ const slides = carousel.querySelectorAll("content-carousel-slide");
407
+ expect(carousel.autoPlayIntervalId).not.toBe(null);
408
+ carousel.remove();
409
+ await wait(0);
410
+ expect(carousel.autoPlayStopped).toBe(true);
411
+ expect(carousel.autoPlayIntervalId).toBe(null);
412
+ await wait(80);
413
+ expect(carousel.getActiveSlide()).toBe(slides[0]);
414
+ });
415
+
416
+ it("leaves auto-play-stopped alone on disconnect when auto-play is unset", async () => {
417
+ const carousel = fixture<HTMLContentCarouselElement>(
418
+ `<content-carousel>
419
+ <content-carousel-slide is-active></content-carousel-slide>
420
+ </content-carousel>`,
421
+ );
422
+ carousel.remove();
423
+ await wait(0);
424
+ expect(carousel.autoPlayStopped).toBe(false);
425
+ });
426
+
427
+ it("keeps auto-play running when moved between parents", async () => {
428
+ const carousel = fixture<HTMLContentCarouselElement>(
429
+ `<content-carousel auto-play="0.05">
430
+ <content-carousel-slide is-active></content-carousel-slide>
431
+ <content-carousel-slide></content-carousel-slide>
432
+ </content-carousel>`,
433
+ );
434
+ const slides = carousel.querySelectorAll("content-carousel-slide");
435
+ const intervalId = carousel.autoPlayIntervalId;
436
+ expect(intervalId).not.toBe(null);
437
+ const clearSpy = vi.spyOn(globalThis, "clearInterval");
438
+
439
+ const other = document.createElement("div");
440
+ document.body.append(other);
441
+ other.append(carousel);
442
+ await wait(0);
443
+
444
+ // a synchronous re-parent is a move, not a removal
445
+ expect(carousel.autoPlayStopped).toBe(false);
446
+ expect(carousel.autoPlayIntervalId).toBe(intervalId);
447
+ expect(clearSpy).not.toHaveBeenCalled();
448
+ await wait(80);
449
+ expect(carousel.getActiveSlide()).toBe(slides[1]);
450
+ });
451
+ });
452
+
453
+ describe("content-carousel (nested carousels)", () => {
454
+ afterEach(() => {
455
+ document.body.innerHTML = "";
456
+ vi.restoreAllMocks();
457
+ });
458
+
459
+ const nestedFixture = () => {
460
+ const outer = fixture<HTMLContentCarouselElement>(
461
+ `<content-carousel>
462
+ <content-carousel-slide>
463
+ <content-carousel>
464
+ <content-carousel-slide></content-carousel-slide>
465
+ <content-carousel-slide is-active></content-carousel-slide>
466
+ </content-carousel>
467
+ </content-carousel-slide>
468
+ <content-carousel-slide is-active></content-carousel-slide>
469
+ <content-carousel-slide></content-carousel-slide>
470
+ </content-carousel>`,
471
+ );
472
+ const inner = outer.querySelector<HTMLContentCarouselElement>(
473
+ "content-carousel",
474
+ )!;
475
+ const outerSlides = [...outer.children].filter(
476
+ (child) => child.localName === "content-carousel-slide",
477
+ );
478
+ const innerSlides = [...inner.children];
479
+ return { outer, inner, outerSlides, innerSlides };
480
+ };
481
+
482
+ it("resolves each carousel's active slide from its own slides", async () => {
483
+ const { outer, inner, outerSlides, innerSlides } = nestedFixture();
484
+ expect(outer.getActiveSlide()).toBe(outerSlides[1]);
485
+ expect(inner.getActiveSlide()).toBe(innerSlides[1]);
486
+ });
487
+
488
+ it("--next on the outer only changes outer slides", async () => {
489
+ const { outer, inner, outerSlides, innerSlides } = nestedFixture();
490
+ const spy = vi.fn();
491
+ outer.addEventListener("content-carousel-slide-changed", spy);
492
+
493
+ await command(outer, "--next");
494
+
495
+ expect(outer.getActiveSlide()).toBe(outerSlides[2]);
496
+ expect(outerSlides[1].hasAttribute("is-active")).toBe(false);
497
+ expect(inner.getActiveSlide()).toBe(innerSlides[1]);
498
+ expect(innerSlides[0].hasAttribute("is-active")).toBe(false);
499
+ expect(spy).toHaveBeenCalledTimes(1);
500
+ expect(
501
+ (spy.mock.calls[0][0] as CustomEvent<ContentCarouselSlideChangedDetail>)
502
+ .detail,
503
+ ).toEqual({ activeSlide: outerSlides[2], previousSlide: outerSlides[1] });
504
+ });
505
+
506
+ it("--back on the outer wraps within the outer's own slides", async () => {
507
+ const { outer, inner, outerSlides, innerSlides } = nestedFixture();
508
+ outerSlides[1].removeAttribute("is-active");
509
+ outerSlides[0].setAttribute("is-active", "");
510
+
511
+ await command(outer, "--back");
512
+
513
+ expect(outer.getActiveSlide()).toBe(outerSlides[2]);
514
+ expect(outerSlides[0].hasAttribute("is-active")).toBe(false);
515
+ expect(inner.getActiveSlide()).toBe(innerSlides[1]);
516
+ });
517
+
518
+ it("navigating the inner carousel leaves the outer untouched", async () => {
519
+ const { outer, inner, outerSlides, innerSlides } = nestedFixture();
520
+ const spy = vi.fn();
521
+ outer.addEventListener("content-carousel-slide-changed", spy);
522
+
523
+ await command(inner, "--next");
524
+
525
+ expect(inner.getActiveSlide()).toBe(innerSlides[0]);
526
+ expect(innerSlides[1].hasAttribute("is-active")).toBe(false);
527
+ expect(outer.getActiveSlide()).toBe(outerSlides[1]);
528
+ // the inner change bubbles to the outer listener, but the outer's own
529
+ // slides never changed
530
+ expect(outerSlides.map((slide) => slide.hasAttribute("is-active"))).toEqual(
531
+ [false, true, false],
532
+ );
533
+ });
534
+ });
@@ -0,0 +1,23 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
9
+
10
+ describe("fade view", () => {
11
+ afterEach(() => {
12
+ document.body.innerHTML = "";
13
+ });
14
+
15
+ it("starts on the first fade slide", async () => {
16
+ const { root } = await mountView(readDemo(import.meta.url, "fade"));
17
+ const carousel = root.querySelector("content-carousel")!;
18
+ expect(carousel.getAttribute("slide-animation")).toBe("fade");
19
+ expect(
20
+ root.querySelector("content-carousel-slide")?.hasAttribute("is-active"),
21
+ ).toBe(true);
22
+ });
23
+ });
@@ -0,0 +1,34 @@
1
+ import "@excom/event-handler";
2
+ import "../../index";
3
+ import {
4
+ afterEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
9
+ import {
10
+ click,
11
+ flush,
12
+ mountView,
13
+ readDemo,
14
+ } from "@excom/quark/support/tests/view-helpers";
15
+
16
+ describe("manual view", () => {
17
+ afterEach(() => {
18
+ document.body.innerHTML = "";
19
+ });
20
+
21
+ it("steps forward and back", async () => {
22
+ const { root } = await mountView(readDemo(import.meta.url, "manual"));
23
+ const slides = root.querySelectorAll("content-carousel-slide");
24
+ const next = root.querySelector("[rel='next']");
25
+ const prev = root.querySelector("[rel='prev']");
26
+ expect(slides[0].hasAttribute("is-active")).toBe(true);
27
+ click(next);
28
+ await flush();
29
+ expect(slides[1].hasAttribute("is-active")).toBe(true);
30
+ click(prev);
31
+ await flush();
32
+ expect(slides[0].hasAttribute("is-active")).toBe(true);
33
+ });
34
+ });
@@ -0,0 +1,24 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ wait,
8
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
9
+ import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
10
+
11
+ describe("simple view", () => {
12
+ afterEach(() => {
13
+ document.body.innerHTML = "";
14
+ });
15
+
16
+ it("auto-plays from slide one to two", async () => {
17
+ const { root } = await mountView(readDemo(import.meta.url, "simple"));
18
+ const slides = root.querySelectorAll("content-carousel-slide");
19
+ expect(slides[0].hasAttribute("is-active")).toBe(true);
20
+ await wait(1600);
21
+ expect(slides[1].hasAttribute("is-active")).toBe(true);
22
+ expect(slides[0].hasAttribute("is-active")).toBe(false);
23
+ });
24
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
3
+ "include": ["./*.ts"],
4
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
5
+ }