返回 slidev
useNav.test.ts
根目录 / packages / client / composables / useNav.test.ts
1 import type { ClicksContext, SlideRoute } from '@slidev/types'
2 import type { SlidevContextNav } from './useNav'
3 import { renderToString } from '@vue/server-renderer'
4 import { provideLocal } from '@vueuse/core'
5 import { describe, expect, it, vi } from 'vitest'
6 import { computed, createSSRApp, defineComponent, h, reactive, ref } from 'vue'
7 import { injectionSlidevContext } from '../constants'
8 import { useNav, useNavBase } from './useNav'
9
10 vi.mock('#slidev/slides', async () => {
11 const { ref } = await import('vue')
12 return {
13 slides: ref([
14 {
15 no: 1,
16 meta: {
17 slide: { frontmatter: { title: 'First' } },
18 },
19 },
20 {
21 no: 2,
22 meta: {
23 slide: { frontmatter: { title: 'Second' } },
24 },
25 },
26 ]),
27 }
28 })
29
30 vi.mock('../env', async () => {
31 const { ref } = await import('vue')
32 return {
33 configs: { remote: false },
34 slideAspect: ref(16 / 9),
35 }
36 })
37
38 vi.mock('../state', async () => {
39 const { ref } = await import('vue')
40 return { hmrSkipTransition: ref(false) }
41 })
42
43 vi.mock('vue-router', async () => {
44 const { reactive, ref } = await import('vue')
45 const route = reactive({
46 name: 'export',
47 params: { no: '1' },
48 query: {},
49 })
50 const router = {
51 currentRoute: ref(route),
52 push: vi.fn(),
53 replace: vi.fn(),
54 }
55 return {
56 useRoute: () => route,
57 useRouter: () => router,
58 }
59 })
60
61 function route(no: number, frontmatter: Record<string, any>): SlideRoute {
62 return {
63 no,
64 meta: {
65 slide: { frontmatter },
66 },
67 } as unknown as SlideRoute
68 }
69
70 describe('useNavBase', () => {
71 it('exposes reactive frontmatter for the current slide', () => {
72 const currentRoute = ref(route(1, { title: 'Intro', section: 'start' }))
73 const nav = useNavBase(
74 computed(() => currentRoute.value),
75 computed(() => ({ current: 0, clicksStart: 0, total: 0 }) as ClicksContext),
76 ref(0),
77 ref(false),
78 ref(false),
79 )
80
81 expect(nav.currentFrontmatter.value).toEqual({
82 title: 'Intro',
83 section: 'start',
84 })
85
86 currentRoute.value = route(2, { title: 'Details', section: 'body' })
87
88 expect(nav.currentFrontmatter.value).toEqual({
89 title: 'Details',
90 section: 'body',
91 })
92 })
93
94 it('uses the slide-local navigation context while rendering exported slides', async () => {
95 vi.stubGlobal('location', { search: '' })
96 let observedNav: SlidevContextNav | undefined
97 const localNav = {
98 currentSlideNo: computed(() => 2),
99 currentPage: computed(() => 2),
100 currentSlideRoute: computed(() => route(2, { title: 'Second' })),
101 tocTree: computed(() => [
102 { no: 1, active: false },
103 { no: 2, active: true },
104 ]),
105 } as unknown as SlidevContextNav
106
107 const Child = defineComponent({
108 setup() {
109 observedNav = useNav()
110 return () => h('span')
111 },
112 })
113 const App = defineComponent({
114 setup() {
115 provideLocal(injectionSlidevContext, reactive({
116 nav: localNav,
117 configs: {},
118 themeConfigs: {},
119 }) as any)
120 return () => h(Child)
121 },
122 })
123
124 await renderToString(createSSRApp(App))
125
126 expect(observedNav?.currentSlideNo.value).toBe(2)
127 expect(observedNav?.currentSlideRoute.value.meta.slide.frontmatter.title).toBe('Second')
128 expect(observedNav?.tocTree.value[1].active).toBe(true)
129 })
130
131 it('falls back to the shared nav when there is no injection context', () => {
132 // Directive hooks such as `v-motion`'s and `v-mark`'s `mounted` call
133 // `useNav()` while no component instance is current. There is no
134 // slide-local context to read there, so the shared nav is the answer.
135 vi.stubGlobal('location', { search: '' })
136
137 expect(() => useNav()).not.toThrow()
138 expect(useNav().currentSlideNo.value).toBe(1)
139 })
140 })
141
141 lines TYPESCRIPT