Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SSR of cloned slides #444

Merged
merged 7 commits into from
Dec 9, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix SSR of cloned slides
Tofandel committed Dec 8, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 569dd7f8f3200cfc4e232ad1097e928bd815251a
13 changes: 10 additions & 3 deletions src/components/Carousel/Carousel.ts
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import {
ComponentInternalInstance,
watchEffect,
shallowReactive,
nextTick,
} from 'vue'

import { ARIA as ARIAComponent } from '@/components/ARIA'
@@ -227,7 +228,12 @@ export const Carousel = defineComponent({
}
}

const ssrReady = ref(false)

onMounted((): void => {
nextTick(() => {
ssrReady.value = true
})
updateBreakpointsConfig()
initAutoplay()

@@ -650,6 +656,7 @@ export const Carousel = defineComponent({
const slotAddons = slots.addons

let output: VNode[] | Array<Array<VNode>> = slotSlides?.(data) || []

if (!config.enabled || !output.length) {
return h(
'section',
@@ -663,11 +670,11 @@ export const Carousel = defineComponent({

const addonsElements = slotAddons?.(data) || []

if (config.wrapAround) {
if (ssrReady.value && config.wrapAround) {
const toShow = Math.ceil(clonedSlidesCount.value)
const slidesBefore = slides.slice(-toShow).map(({ vnode }, index: number) =>
cloneVNode(vnode, {
index: -slides.length + toShow + index,
index: -toShow + index,
isClone: true,
key: `clone-before-${String(vnode.key)}`,
})
@@ -679,7 +686,7 @@ export const Carousel = defineComponent({
key: `clone-after-${String(vnode.key)}`,
})
)
output = [slidesBefore, output, slidesAfter]
output = [...slidesBefore, ...output, ...slidesAfter]
}

const trackEl = h(
4 changes: 2 additions & 2 deletions tests/components/SlottedApp.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script setup lang="ts">
import Carousel from './Carousel.vue'
import Slides from './Slides.vue'
const { slideNum = 5 } = defineProps<{ slideNum: number }>()
const { slideNum = 5 } = defineProps<{ slideNum: number, wrapAround?: boolean }>()
</script>

<template>
<Carousel>
<Carousel :wrap-around="wrapAround">
<Slides :slide-num="slideNum"></Slides>
</Carousel>
</template>
53 changes: 52 additions & 1 deletion tests/integration/__snapshots__/carousel.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,57 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Wrap around Carousel.ts > It renders wrapAround correctly 1`] = `
exports[`SSR Carousel > renders server side properly 1`] = `"<div id="app"><section class="carousel is-ltr" style="" dir="ltr" aria-label="Gallery" tabindex="0"><div class="carousel__viewport"><ol class="carousel__track" style="transform:translateX(0px);"><!--[--><li style="width:50%;" class="carousel__slide carousel__slide--visible carousel__slide--prev" id="v-0">1 <input type="text"></li><li style="width:50%;" class="carousel__slide carousel__slide--visible carousel__slide--active" id="v-1">2 <input type="text"></li><li style="width:50%;" class="carousel__slide carousel__slide--visible carousel__slide--next" id="v-2">3 <input type="text"></li><li style="width:50%;" class="carousel__slide" id="v-3">4 <input type="text"></li><li style="width:50%;" class="carousel__slide" id="v-4">5 <input type="text"></li><!--]--></ol></div><!--[--><!--[--><button type="button" class="carousel__prev" aria-label="Navigate to previous slide" title="Navigate to previous slide"><svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing to the left"><title>Arrow pointing to the left</title><path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"></path></svg></button><button type="button" class="carousel__next" aria-label="Navigate to next slide" title="Navigate to next slide"><svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing to the right"><title>Arrow pointing to the right</title><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"></path></svg></button><!--]--><ol class="carousel__pagination"><li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 1" aria-pressed="false" aria-controls="v-0" title="Navigate to slide 1"></button></li><li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button carousel__pagination-button--active" aria-label="Navigate to slide 2" aria-pressed="true" aria-controls="v-1" title="Navigate to slide 2"></button></li><li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 3" aria-pressed="false" aria-controls="v-2" title="Navigate to slide 3"></button></li><li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 4" aria-pressed="false" aria-controls="v-3" title="Navigate to slide 4"></button></li><li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 5" aria-pressed="false" aria-controls="v-4" title="Navigate to slide 5"></button></li></ol><!--]--><div class="carousel__liveregion carousel__sr-only" aria-live="polite" aria-atomic="true">Item 2 of 5</div></section></div>"`;

exports[`SSR Carousel > renders server side properly 2`] = `
"<div id="app">
<section class="carousel is-ltr" dir="ltr" aria-label="Gallery" tabindex="0">
<div class="carousel__viewport">
<ol class="carousel__track" style="transform: translateX(0px);">
<li style="width: 50%;" class="carousel__slide carousel__slide--visible carousel__slide--prev" id="v-0">1 <input type="text"></li>
<li style="width: 50%;" class="carousel__slide carousel__slide--visible carousel__slide--active" id="v-1">2 <input type="text"></li>
<li style="width: 50%;" class="carousel__slide carousel__slide--visible carousel__slide--next" id="v-2">3 <input type="text"></li>
<li style="width: 50%;" class="carousel__slide" id="v-3">4 <input type="text"></li>
<li style="width: 50%;" class="carousel__slide" id="v-4">5 <input type="text"></li>
</ol>
</div><button type="button" class="carousel__prev" aria-label="Navigate to previous slide" title="Navigate to previous slide"><svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing to the left">
<title>Arrow pointing to the left</title>
<path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"></path>
</svg></button><button type="button" class="carousel__next" aria-label="Navigate to next slide" title="Navigate to next slide"><svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing to the right">
<title>Arrow pointing to the right</title>
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"></path>
</svg></button>
<ol class="carousel__pagination">
<li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 1" aria-pressed="false" aria-controls="v-0" title="Navigate to slide 1"></button></li>
<li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button carousel__pagination-button--active" aria-label="Navigate to slide 2" aria-pressed="true" aria-controls="v-1" title="Navigate to slide 2"></button></li>
<li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 3" aria-pressed="false" aria-controls="v-2" title="Navigate to slide 3"></button></li>
<li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 4" aria-pressed="false" aria-controls="v-3" title="Navigate to slide 4"></button></li>
<li class="carousel__pagination-item"><button type="button" class="carousel__pagination-button" aria-label="Navigate to slide 5" aria-pressed="false" aria-controls="v-4" title="Navigate to slide 5"></button></li>
</ol>
<div class="carousel__liveregion carousel__sr-only" aria-live="polite" aria-atomic="true">Item 2 of 5</div>
</section>
</div>"
`;

exports[`SSR Carousel > renders slotted server side properly 1`] = `"<div id="app"><section class="carousel is-ltr" style="" dir="ltr" aria-label="Gallery" tabindex="0"><div class="carousel__viewport"><ol class="carousel__track" style="transform:translateX(0px);"><!--[--><!--[--><li style="width:100%;" class="carousel__slide carousel__slide--visible carousel__slide--active" id="v-0">1</li><li style="width:100%;" class="carousel__slide carousel__slide--next" id="v-1">2</li><li style="width:100%;" class="carousel__slide" id="v-2">3</li><li style="width:100%;" class="carousel__slide" id="v-3">4</li><li style="width:100%;" class="carousel__slide" id="v-4">5</li><!--]--><!--]--></ol></div><!--[--><!--]--><div class="carousel__liveregion carousel__sr-only" aria-live="polite" aria-atomic="true">Item 1 of 5</div></section></div>"`;

exports[`SSR Carousel > renders slotted server side properly 2`] = `
"<div id="app">
<section class="carousel is-ltr" dir="ltr" aria-label="Gallery" tabindex="0">
<div class="carousel__viewport">
<ol class="carousel__track" style="transform: translateX(0px);">
<li style="width: 100%;" class="carousel__slide carousel__slide--visible carousel__slide--active" id="v-0">1</li>
<li style="width: 100%;" class="carousel__slide carousel__slide--next" id="v-1">2</li>
<li style="width: 100%;" class="carousel__slide" id="v-2">3</li>
<li style="width: 100%;" class="carousel__slide" id="v-3">4</li>
<li style="width: 100%;" class="carousel__slide" id="v-4">5</li>
</ol>
</div>
<div class="carousel__liveregion carousel__sr-only" aria-live="polite" aria-atomic="true">Item 1 of 5</div>
</section>
</div>"
`;

exports[`Wrap around Carousel.ts > renders wrapAround correctly 1`] = `
"<section class="carousel is-ltr" dir="ltr" aria-label="Gallery" tabindex="0">
<div class="carousel__viewport">
<ol class="carousel__track" style="transform: translateX(0px);">
64 changes: 55 additions & 9 deletions tests/integration/carousel.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { mount } from '@vue/test-utils'
import { expect, it, describe, beforeAll } from 'vitest'

import { expect, it, describe, beforeAll, vi } from 'vitest'
import { createSSRApp, h } from 'vue'
import { renderToString } from 'vue/server-renderer'

import App from '../components/BasicApp.vue'
import SlottedApp from '../components/SlottedApp.vue'

describe('Carousel.ts', () => {
let wrapper: any
let wrapper: ReturnType<typeof mount<typeof App>>

beforeAll(async () => {
wrapper = mount(App, { props: { slideNum: 5 } })
wrapper = await mount(App, { props: { slideNum: 5 } })
})

it('It renders *five* slides correctly', () => {
@@ -29,10 +30,10 @@ describe('Carousel.ts', () => {
})

describe('Slotted Carousel.ts', () => {
let wrapper: any
let wrapper: ReturnType<typeof mount<typeof SlottedApp>>

beforeAll(async () => {
wrapper = mount(SlottedApp, { props: { slideNum: 3 } })
wrapper = await mount(SlottedApp, { props: { slideNum: 3 } })
})

it('It renders *three* slides correctly', () => {
@@ -52,15 +53,60 @@ describe('Slotted Carousel.ts', () => {
})

describe('Wrap around Carousel.ts', () => {
let wrapper: any
let wrapper: ReturnType<typeof mount<typeof App>>

beforeAll(async () => {
wrapper = mount(App, {
wrapper = await mount(App, {
props: { wrapAround: true, itemsToShow: 3, slideNum: 9, modelValue: 8 },
})
})

it('It renders wrapAround correctly', () => {
it('renders wrapAround correctly', () => {
expect(wrapper.html()).toMatchSnapshot()
})
})

describe('SSR Carousel', () => {
it('renders server side properly', async () => {
const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => undefined)

const comp = {
render() {
return h('div', { id: 'app' }, h(App, {
wrapAround: true,
modelValue: 1,
itemsToShow: 2,
}))
},
}
const app = createSSRApp(comp)
const html = await renderToString(app)
document.body.innerHTML = html
const wrapper = await mount(comp, { attachTo: '#app' })

expect(consoleMock).not.toHaveBeenCalled()
expect(html).toMatchSnapshot()
expect(wrapper.html()).toMatchSnapshot()
})

it('renders slotted server side properly', async () => {
const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => undefined)

const comp = {
render() {
return h('div', { id: 'app' }, h(SlottedApp, {
wrapAround: true,
slideNum: 5,
}))
},
}
const app = createSSRApp(comp)
const html = await renderToString(app)
document.body.innerHTML = html
const wrapper = await mount(comp, { attachTo: '#app' })

expect(consoleMock).not.toHaveBeenCalled()
expect(html).toMatchSnapshot()
expect(wrapper.html()).toMatchSnapshot()
})
})
Loading