11// @vitest -environment node
22
3- import { describe , it , expect , beforeEach , vi } from 'vitest'
3+ import { beforeEach , describe , expect , it , vi } from 'vitest'
44import { experimental_AstroContainer as AstroContainer } from 'astro/container'
5- import { Window } from 'happy-dom'
65import CarouselComponent from '@components/Carousel/index.astro'
6+ import type { Props as CarouselProps } from '@components/Carousel/index.astro'
7+ import type { CarouselElement } from '@components/Carousel/client'
8+ import type { WebComponentModule } from '@components/scripts/@types/webComponentModule'
9+ import { executeRender } from '@test/unit/helpers/litRuntime'
10+ import sampleCollection from '@components/Carousel/client/__fixtures__/collection.fixture'
711
8- type CarouselProps = {
9- title ?: string
10- limit ?: number
11- variant ?: 'featured' | 'suggested' | 'random'
12- currentSlug ?: string
13- type ?: 'services' | 'case-studies' | 'articles'
14- }
15-
16- const sampleCollection = [
17- {
18- id : 'article-one' ,
19- data : {
20- title : 'Article One' ,
21- description : 'First test entry' ,
22- publishDate : '2024-01-01' ,
23- featured : true ,
24- icon : '/icons/one.svg' ,
25- } ,
26- } ,
27- {
28- id : 'article-two' ,
29- data : {
30- title : 'Article Two' ,
31- description : 'Second test entry' ,
32- publishDate : '2024-02-01' ,
33- featured : true ,
34- } ,
35- } ,
36- {
37- id : 'article-three' ,
38- data : {
39- title : 'Article Three' ,
40- description : 'Third test entry' ,
41- publishDate : '2024-03-01' ,
42- featured : false ,
43- } ,
44- } ,
45- {
46- id : 'article-four' ,
47- data : {
48- title : 'Article Four' ,
49- description : 'Fourth test entry' ,
50- publishDate : '2024-04-01' ,
51- featured : false ,
52- } ,
53- } ,
54- ]
12+ type CarouselComponentModule = WebComponentModule < CarouselElement >
13+ type ConcreteCarouselProps = Required < CarouselProps < 'articles' > >
5514
5615vi . mock ( 'astro:content' , ( ) => ( {
5716 getCollection : vi . fn ( async ( ) => sampleCollection ) ,
5817} ) )
5918
60- const baseProps : Required < CarouselProps > = {
19+ vi . mock ( 'embla-carousel' , ( ) => {
20+ const createEmbla = vi . fn ( ( ) => ( {
21+ canScrollPrev : vi . fn ( ( ) => false ) ,
22+ canScrollNext : vi . fn ( ( ) => false ) ,
23+ scrollPrev : vi . fn ( ) ,
24+ scrollNext : vi . fn ( ) ,
25+ on : vi . fn ( ( ) => createEmbla . mock . results . at ( - 1 ) ?. value ?? { } ) ,
26+ off : vi . fn ( ( ) => createEmbla . mock . results . at ( - 1 ) ?. value ?? { } ) ,
27+ destroy : vi . fn ( ) ,
28+ scrollSnapList : vi . fn ( ( ) => [ 0 , 1 , 2 ] ) ,
29+ selectedScrollSnap : vi . fn ( ( ) => 0 ) ,
30+ scrollTo : vi . fn ( ) ,
31+ } ) )
32+
33+ return {
34+ default : createEmbla ,
35+ }
36+ } )
37+
38+ vi . mock ( 'embla-carousel-autoplay' , ( ) => ( {
39+ __esModule : true ,
40+ default : vi . fn ( ( ) => ( {
41+ play : vi . fn ( ) ,
42+ stop : vi . fn ( ) ,
43+ } ) ) ,
44+ } ) )
45+
46+ const defaultCarouselProps : ConcreteCarouselProps = {
6147 title : 'Featured Articles' ,
6248 limit : 3 ,
6349 variant : 'featured' ,
6450 currentSlug : 'article-two' ,
6551 type : 'articles' ,
6652}
6753
68- const renderCarousel = async ( props ?: Partial < CarouselProps > ) => {
54+ /**
55+ * Renders the Carousel component in a shared JSDOM instance and exposes the hydrated element.
56+ */
57+ const renderCarousel = async (
58+ assertion : ( _context : { root : CarouselElement } ) => Promise < void > | void ,
59+ props : Partial < ConcreteCarouselProps > = { } ,
60+ ) => {
6961 const container = await AstroContainer . create ( )
70- const html = await container . renderToString ( CarouselComponent , {
71- props : { ...baseProps , ...props } ,
72- } )
73-
74- const window = new Window ( )
75- const domParser = new window . DOMParser ( )
76- const document = domParser . parseFromString ( html , 'text/html' )
7762
78- return { html, document }
63+ await executeRender < CarouselComponentModule > ( {
64+ container,
65+ component : CarouselComponent ,
66+ moduleSpecifier : '@components/Carousel/client/index' ,
67+ args : {
68+ props : {
69+ ...defaultCarouselProps ,
70+ ...props ,
71+ } ,
72+ } ,
73+ assert : async ( { element } ) => {
74+ await assertion ( { root : element } )
75+ } ,
76+ } )
7977}
8078
8179describe ( 'Carousel component (server output)' , ( ) => {
@@ -84,48 +82,52 @@ describe('Carousel component (server output)', () => {
8482 } )
8583
8684 it ( 'renders the provided title and respects the requested limit' , async ( ) => {
87- const { document } = await renderCarousel ( { title : 'Latest Reads' , limit : 2 , currentSlug : 'article-four' } )
88-
89- const heading = document . querySelector ( 'h2' )
90- const slides = document . querySelectorAll ( '[data-carousel-slide]' )
85+ await renderCarousel ( ( { root } ) => {
86+ const heading = root . querySelector ( 'h2' )
87+ const slides = root . querySelectorAll ( '[data-carousel-slide]' )
9188
92- expect ( heading ?. textContent ) . toBe ( 'Latest Reads' )
93- expect ( slides ) . toHaveLength ( 2 )
89+ expect ( heading ?. textContent ) . toBe ( 'Latest Reads' )
90+ expect ( slides ) . toHaveLength ( 2 )
91+ } , { title : 'Latest Reads' , limit : 2 , currentSlug : 'article-four' } )
9492 } )
9593
9694 it ( 'excludes the current slug from rendered cards' , async ( ) => {
97- const { document } = await renderCarousel ( { currentSlug : 'article-one' } )
98- const links = Array . from ( document . querySelectorAll ( 'a[href^="/articles/"]' ) )
99- const hrefs = links . map ( link => link . getAttribute ( 'href' ) )
95+ await renderCarousel ( ( { root } ) => {
96+ const links = Array . from ( root . querySelectorAll ( 'a[href^="/articles/"]' ) )
97+ const hrefs = links . map ( link => link . getAttribute ( 'href' ) )
10098
101- expect ( hrefs ) . not . toContain ( '/articles/article-one' )
99+ expect ( hrefs ) . not . toContain ( '/articles/article-one' )
100+ } , { currentSlug : 'article-one' } )
102101 } )
103102
104103 it ( 'renders featured items with icons when available' , async ( ) => {
105- const { document } = await renderCarousel ( )
106- const cardWithIcon = document . querySelector ( '[data-carousel-slide] img' )
104+ await renderCarousel ( ( { root } ) => {
105+ const cardWithIcon = root . querySelector ( '[data-carousel-slide] img' )
107106
108- expect ( cardWithIcon ) . toBeTruthy ( )
109- expect ( cardWithIcon ?. getAttribute ( 'src' ) ) . toBe ( '/icons/one.svg' )
107+ expect ( cardWithIcon ) . toBeTruthy ( )
108+ expect ( cardWithIcon ?. getAttribute ( 'src' ) ) . toBe ( '/icons/one.svg' )
109+ } )
110110 } )
111111
112112 it ( 'orders suggested items by publish date when variant is suggested' , async ( ) => {
113- const { document } = await renderCarousel ( { variant : 'suggested' , limit : 3 } )
114- const cardTitles = Array . from ( document . querySelectorAll ( '[data-carousel-slide] h3' ) ) . map ( title =>
115- title . textContent ?. trim ( ) ,
116- )
117-
118- expect ( cardTitles [ 0 ] ) . toBe ( 'Article Four' )
119- expect ( cardTitles [ 1 ] ) . toBe ( 'Article Three' )
113+ await renderCarousel ( ( { root } ) => {
114+ const cardTitles = Array . from ( root . querySelectorAll ( '[data-carousel-slide] h3' ) ) . map ( title =>
115+ title . textContent ?. trim ( ) ,
116+ )
117+
118+ expect ( cardTitles [ 0 ] ) . toBe ( 'Article Four' )
119+ expect ( cardTitles [ 1 ] ) . toBe ( 'Article Three' )
120+ } , { variant : 'suggested' , limit : 3 } )
120121 } )
121122
122123 it ( 'renders randomized order when variant is random' , async ( ) => {
123- const { document } = await renderCarousel ( { variant : 'random' , limit : 3 } )
124- const cardTitles = Array . from ( document . querySelectorAll ( '[data-carousel-slide] h3' ) ) . map ( title =>
125- title . textContent ?. trim ( ) ,
126- )
127-
128- expect ( cardTitles ) . toHaveLength ( 3 )
129- expect ( new Set ( cardTitles ) ) . toEqual ( new Set ( [ 'Article Four' , 'Article Three' , 'Article One' ] ) )
124+ await renderCarousel ( ( { root } ) => {
125+ const cardTitles = Array . from ( root . querySelectorAll ( '[data-carousel-slide] h3' ) ) . map ( title =>
126+ title . textContent ?. trim ( ) ,
127+ )
128+
129+ expect ( cardTitles ) . toHaveLength ( 3 )
130+ expect ( new Set ( cardTitles ) ) . toEqual ( new Set ( [ 'Article Four' , 'Article Three' , 'Article One' ] ) )
131+ } , { variant : 'random' , limit : 3 } )
130132 } )
131133} )
0 commit comments