Skip to content

Commit 424fe83

Browse files
committed
chore: add hydrate test to solid-router
1 parent 52cb3e0 commit 424fe83

1 file changed

Lines changed: 265 additions & 0 deletions

File tree

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2+
import {
3+
createMemoryHistory,
4+
createRootRoute,
5+
createRoute,
6+
createRouter,
7+
notFound,
8+
} from '../src'
9+
import { hydrate } from '../../router-core/src/ssr/ssr-client'
10+
import type { TsrSsrGlobal } from '../../router-core/src/ssr/ssr-client'
11+
import type { AnyRouteMatch } from '../src'
12+
13+
describe('hydrate', () => {
14+
let mockWindow: { $_TSR?: TsrSsrGlobal }
15+
let mockRouter: any
16+
let mockHead: any
17+
18+
beforeEach(() => {
19+
// Reset global window mock
20+
mockWindow = {}
21+
;(global as any).window = mockWindow
22+
23+
// Reset mock head function
24+
mockHead = vi.fn()
25+
26+
const history = createMemoryHistory({ initialEntries: ['/'] })
27+
28+
const rootRoute = createRootRoute({})
29+
30+
const indexRoute = createRoute({
31+
getParentRoute: () => rootRoute,
32+
path: '/',
33+
component: () => 'Index',
34+
notFoundComponent: () => 'Not Found',
35+
head: mockHead,
36+
})
37+
38+
const otherRoute = createRoute({
39+
getParentRoute: () => indexRoute,
40+
path: '/other',
41+
component: () => 'Other',
42+
})
43+
44+
const routeTree = rootRoute.addChildren([
45+
indexRoute.addChildren([otherRoute]),
46+
])
47+
48+
mockRouter = createRouter({ routeTree, history, isServer: true })
49+
})
50+
51+
afterEach(() => {
52+
vi.resetAllMocks()
53+
delete (global as any).window
54+
})
55+
56+
it('should throw error if window.$_TSR is not available', async () => {
57+
await expect(hydrate(mockRouter)).rejects.toThrow(
58+
'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!',
59+
)
60+
})
61+
62+
it('should throw error if window.$_TSR.router is not available', async () => {
63+
mockWindow.$_TSR = {
64+
c: vi.fn(),
65+
p: vi.fn(),
66+
buffer: [],
67+
initialized: false,
68+
// router is missing
69+
} as any
70+
71+
await expect(hydrate(mockRouter)).rejects.toThrow(
72+
'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',
73+
)
74+
})
75+
76+
it('should initialize serialization adapters when provided', async () => {
77+
const mockSerializer = {
78+
key: 'testAdapter',
79+
fromSerializable: vi.fn(),
80+
toSerializable: vi.fn(),
81+
test: vi.fn().mockReturnValue(true),
82+
'~types': {
83+
input: {},
84+
output: {},
85+
extends: {},
86+
},
87+
}
88+
89+
mockRouter.options.serializationAdapters = [mockSerializer]
90+
91+
const mockMatches = [{ id: '/', routeId: '/', index: 0, _nonReactive: {} }]
92+
mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches)
93+
mockRouter.state.matches = mockMatches
94+
95+
const mockBuffer = [vi.fn(), vi.fn()]
96+
mockWindow.$_TSR = {
97+
router: {
98+
manifest: { routes: {} },
99+
dehydratedData: {},
100+
lastMatchId: '/',
101+
matches: [],
102+
},
103+
c: vi.fn(),
104+
p: vi.fn(),
105+
buffer: mockBuffer,
106+
initialized: false,
107+
}
108+
109+
await hydrate(mockRouter)
110+
111+
expect(mockWindow.$_TSR.t).toBeInstanceOf(Map)
112+
expect(mockWindow.$_TSR.t?.get('testAdapter')).toBe(
113+
mockSerializer.fromSerializable,
114+
)
115+
expect(mockBuffer[0]).toHaveBeenCalled()
116+
expect(mockBuffer[1]).toHaveBeenCalled()
117+
expect(mockWindow.$_TSR.initialized).toBe(true)
118+
})
119+
120+
it('should handle empty serialization adapters', async () => {
121+
mockRouter.options.serializationAdapters = []
122+
123+
mockWindow.$_TSR = {
124+
router: {
125+
manifest: { routes: {} },
126+
dehydratedData: {},
127+
lastMatchId: '/',
128+
matches: [],
129+
},
130+
c: vi.fn(),
131+
p: vi.fn(),
132+
buffer: [],
133+
initialized: false,
134+
}
135+
136+
await hydrate(mockRouter)
137+
138+
expect(mockWindow.$_TSR.t).toBeUndefined()
139+
expect(mockWindow.$_TSR.initialized).toBe(true)
140+
})
141+
142+
it('should set manifest in router.ssr', async () => {
143+
const testManifest = { routes: {} }
144+
mockWindow.$_TSR = {
145+
router: {
146+
manifest: testManifest,
147+
dehydratedData: {},
148+
lastMatchId: '/',
149+
matches: [],
150+
},
151+
c: vi.fn(),
152+
p: vi.fn(),
153+
buffer: [],
154+
initialized: false,
155+
}
156+
157+
await hydrate(mockRouter)
158+
159+
expect(mockRouter.ssr).toEqual({
160+
manifest: testManifest,
161+
})
162+
})
163+
164+
it('should hydrate matches', async () => {
165+
const mockMatches = [
166+
{
167+
id: '/',
168+
routeId: '/',
169+
index: 0,
170+
ssr: undefined,
171+
_nonReactive: {},
172+
},
173+
{
174+
id: '/other',
175+
routeId: '/other',
176+
index: 1,
177+
ssr: undefined,
178+
_nonReactive: {},
179+
},
180+
]
181+
182+
const dehydratedMatches = [
183+
{
184+
i: '/',
185+
l: { indexData: 'server-data' },
186+
s: 'success' as const,
187+
ssr: true,
188+
u: Date.now(),
189+
},
190+
]
191+
192+
mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches)
193+
mockRouter.state.matches = mockMatches
194+
195+
mockWindow.$_TSR = {
196+
router: {
197+
manifest: { routes: {} },
198+
dehydratedData: {},
199+
lastMatchId: '/',
200+
matches: dehydratedMatches,
201+
},
202+
c: vi.fn(),
203+
p: vi.fn(),
204+
buffer: [],
205+
initialized: false,
206+
}
207+
208+
await hydrate(mockRouter)
209+
210+
const { id, loaderData, ssr, status } = mockMatches[0] as AnyRouteMatch
211+
expect(id).toBe('/')
212+
expect(loaderData).toEqual({ indexData: 'server-data' })
213+
expect(status).toBe('success')
214+
expect(ssr).toBe(true)
215+
})
216+
217+
it('should handle errors during route context hydration', async () => {
218+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
219+
mockHead.mockImplementation(() => {
220+
throw notFound()
221+
})
222+
223+
const mockMatches = [
224+
{ id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} },
225+
]
226+
227+
mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches)
228+
mockRouter.state.matches = mockMatches
229+
230+
mockWindow.$_TSR = {
231+
router: {
232+
manifest: { routes: {} },
233+
dehydratedData: {},
234+
lastMatchId: '/',
235+
matches: [
236+
{
237+
i: '/',
238+
l: { data: 'test' },
239+
s: 'success',
240+
ssr: true,
241+
u: Date.now(),
242+
},
243+
],
244+
},
245+
c: vi.fn(),
246+
p: vi.fn(),
247+
buffer: [],
248+
initialized: false,
249+
}
250+
251+
await hydrate(mockRouter)
252+
253+
const match = mockRouter.state.matches[0] as AnyRouteMatch
254+
expect(match.error).toEqual({ isNotFound: true })
255+
256+
expect(consoleSpy).toHaveBeenCalledWith(
257+
'NotFound error during hydration for routeId: /',
258+
expect.objectContaining({
259+
isNotFound: true,
260+
}),
261+
)
262+
263+
consoleSpy.mockRestore()
264+
})
265+
})

0 commit comments

Comments
 (0)