|
1 | 1 | import type { VueWrapper } from '@vue/test-utils' |
| 2 | +import { renderToString } from '@vue/server-renderer' |
2 | 3 | import { flushPromises, mount } from '@vue/test-utils' |
3 | | -import { beforeEach, describe, expect, it } from 'vitest' |
| 4 | +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' |
4 | 5 | import { axe } from 'vitest-axe' |
| 6 | +import { createSSRApp, defineComponent, h, nextTick } from 'vue' |
| 7 | +import { ConfigProvider } from '@/ConfigProvider' |
5 | 8 | import { TabsContent, TabsList, TabsRoot, TabsTrigger } from '.' |
6 | 9 | import Tabs from './story/_Tabs.vue' |
7 | 10 |
|
| 11 | +const TabsHydrationFixture = defineComponent({ |
| 12 | + setup() { |
| 13 | + let count = 0 |
| 14 | + const useId = () => `nuxt-${++count}` |
| 15 | + |
| 16 | + return () => |
| 17 | + h(ConfigProvider, { useId }, () => |
| 18 | + h(TabsRoot, { defaultValue: 'account' }, () => [ |
| 19 | + h(TabsList, () => [ |
| 20 | + h(TabsTrigger, { value: 'account' }, () => 'Account'), |
| 21 | + h(TabsTrigger, { value: 'password' }, () => 'Password'), |
| 22 | + ]), |
| 23 | + h(TabsContent, { value: 'account' }, () => 'Account content'), |
| 24 | + h(TabsContent, { value: 'password' }, () => 'Password content'), |
| 25 | + ])) |
| 26 | + }, |
| 27 | +}) |
| 28 | + |
| 29 | +afterEach(() => { |
| 30 | + vi.restoreAllMocks() |
| 31 | +}) |
| 32 | + |
| 33 | +describe('ssr hydration', () => { |
| 34 | + it('uses ConfigProvider ids when Vue app id prefixes differ', async () => { |
| 35 | + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) |
| 36 | + const error = vi.spyOn(console, 'error').mockImplementation(() => {}) |
| 37 | + |
| 38 | + // Tabs derives trigger/content IDs from one base ID, so this catches the |
| 39 | + // shared source-order bug for components that build related IDs. |
| 40 | + const serverApp = createSSRApp(TabsHydrationFixture) |
| 41 | + serverApp.config.idPrefix = 'v-1' |
| 42 | + |
| 43 | + const container = document.createElement('div') |
| 44 | + container.innerHTML = await renderToString(serverApp) |
| 45 | + document.body.innerHTML = '' |
| 46 | + document.body.append(container) |
| 47 | + |
| 48 | + expect(container.innerHTML).toContain('id="reka-tabs-nuxt-1-trigger-account"') |
| 49 | + expect(container.innerHTML).toContain('id="reka-tabs-nuxt-1-content-account"') |
| 50 | + const triggerId = container.querySelector('[role="tab"]')?.id |
| 51 | + const contentId = container.querySelector('[role="tabpanel"]')?.id |
| 52 | + |
| 53 | + const clientApp = createSSRApp(TabsHydrationFixture) |
| 54 | + clientApp.config.idPrefix = 'v-0' |
| 55 | + clientApp.mount(container) |
| 56 | + await nextTick() |
| 57 | + |
| 58 | + expect(container.querySelector('[role="tab"]')?.id).toBe(triggerId) |
| 59 | + expect(container.querySelector('[role="tabpanel"]')?.id).toBe(contentId) |
| 60 | + |
| 61 | + const warnings = warn.mock.calls.flat().join('\n') |
| 62 | + expect(warnings).not.toContain('Hydration attribute mismatch') |
| 63 | + expect(error.mock.calls.flat().join('\n')).not.toContain('Hydration completed but contains mismatches') |
| 64 | + }) |
| 65 | +}) |
| 66 | + |
8 | 67 | describe('given default Tabs', () => { |
9 | 68 | let wrapper: VueWrapper<InstanceType<typeof Tabs>> |
10 | 69 |
|
|
0 commit comments