Skip to content

Commit 2ec0e55

Browse files
committed
test(router): cover head asset stability in react and vue
1 parent f12ac8d commit 2ec0e55

2 files changed

Lines changed: 388 additions & 4 deletions

File tree

packages/react-router/tests/Scripts.test.tsx

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1-
import { describe, expect, test } from 'vitest'
2-
import { act, render, screen } from '@testing-library/react'
1+
import { afterEach, describe, expect, test } from 'vitest'
2+
import {
3+
act,
4+
cleanup,
5+
fireEvent,
6+
render,
7+
screen,
8+
waitFor,
9+
} from '@testing-library/react'
10+
import { createPortal } from 'react-dom'
311
import ReactDOMServer from 'react-dom/server'
412

513
import {
614
HeadContent,
15+
Link,
716
Outlet,
817
RouterProvider,
18+
createBrowserHistory,
919
createMemoryHistory,
1020
createRootRoute,
1121
createRoute,
1222
createRouter,
1323
} from '../src'
1424
import { Scripts } from '../src/Scripts'
1525

26+
afterEach(() => {
27+
window.history.replaceState(null, 'root', '/')
28+
cleanup()
29+
})
30+
1631
describe('ssr scripts', () => {
1732
test('it works', async () => {
1833
const rootRoute = createRootRoute({
@@ -327,6 +342,179 @@ describe('ssr HeadContent', () => {
327342
`<title>Index</title><meta name="image" content="image.jpg"/><meta property="og:description" content="Root description"/><meta name="description" content="Index"/><meta name="last-modified" content="2021-10-10"/><meta property="og:image" content="index-image.jpg"/>`,
328343
)
329344
})
345+
346+
test('keeps manifest stylesheet links mounted when history state changes', async () => {
347+
const history = createBrowserHistory()
348+
349+
try {
350+
const rootRoute = createRootRoute({
351+
component: () => {
352+
return (
353+
<>
354+
{createPortal(<HeadContent />, document.head)}
355+
<button
356+
onClick={() => {
357+
window.history.replaceState(
358+
{ slideId: 'slide-2' },
359+
'',
360+
window.location.href,
361+
)
362+
}}
363+
>
364+
Replace state
365+
</button>
366+
<Outlet />
367+
</>
368+
)
369+
},
370+
})
371+
372+
const indexRoute = createRoute({
373+
path: '/',
374+
getParentRoute: () => rootRoute,
375+
component: () => <div>Index</div>,
376+
})
377+
378+
const router = createRouter({
379+
history,
380+
routeTree: rootRoute.addChildren([indexRoute]),
381+
})
382+
383+
router.ssr = {
384+
manifest: {
385+
routes: {
386+
[rootRoute.id]: {
387+
assets: [
388+
{
389+
tag: 'link',
390+
attrs: {
391+
rel: 'stylesheet',
392+
href: '/main.css',
393+
},
394+
},
395+
],
396+
},
397+
},
398+
},
399+
} as any
400+
401+
await router.load()
402+
403+
await act(() => render(<RouterProvider router={router} />))
404+
405+
const getStylesheetLink = () =>
406+
Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find(
407+
(link) => link.getAttribute('href') === '/main.css',
408+
)
409+
410+
await waitFor(() => {
411+
expect(getStylesheetLink()).toBeInstanceOf(HTMLLinkElement)
412+
})
413+
414+
const initialLink = getStylesheetLink()
415+
expect(initialLink).toBeInstanceOf(HTMLLinkElement)
416+
417+
fireEvent.click(screen.getByRole('button', { name: 'Replace state' }))
418+
419+
await waitFor(() => {
420+
expect(
421+
(router.state.location.state as { slideId?: string }).slideId,
422+
).toBe('slide-2')
423+
})
424+
425+
expect(getStylesheetLink()).toBe(initialLink)
426+
expect(
427+
Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(
428+
(link) => link.getAttribute('href') === '/main.css',
429+
),
430+
).toHaveLength(1)
431+
} finally {
432+
history.destroy()
433+
}
434+
})
435+
436+
test('keeps manifest stylesheet links mounted when navigating with Link', async () => {
437+
const history = createBrowserHistory()
438+
439+
try {
440+
const rootRoute = createRootRoute({
441+
component: () => {
442+
return (
443+
<>
444+
{createPortal(<HeadContent />, document.head)}
445+
<Outlet />
446+
</>
447+
)
448+
},
449+
})
450+
451+
const indexRoute = createRoute({
452+
path: '/',
453+
getParentRoute: () => rootRoute,
454+
component: () => <Link to="/about">Go to about page</Link>,
455+
})
456+
457+
const aboutRoute = createRoute({
458+
path: '/about',
459+
getParentRoute: () => rootRoute,
460+
component: () => <div>About</div>,
461+
})
462+
463+
const router = createRouter({
464+
history,
465+
routeTree: rootRoute.addChildren([indexRoute, aboutRoute]),
466+
})
467+
468+
router.ssr = {
469+
manifest: {
470+
routes: {
471+
[rootRoute.id]: {
472+
assets: [
473+
{
474+
tag: 'link',
475+
attrs: {
476+
rel: 'stylesheet',
477+
href: '/main.css',
478+
},
479+
},
480+
],
481+
},
482+
},
483+
},
484+
} as any
485+
486+
await router.load()
487+
488+
await act(() => render(<RouterProvider router={router} />))
489+
490+
const getStylesheetLink = () =>
491+
Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find(
492+
(link) => link.getAttribute('href') === '/main.css',
493+
)
494+
495+
await waitFor(() => {
496+
expect(getStylesheetLink()).toBeInstanceOf(HTMLLinkElement)
497+
})
498+
499+
const initialLink = getStylesheetLink()
500+
expect(initialLink).toBeInstanceOf(HTMLLinkElement)
501+
502+
fireEvent.click(screen.getByRole('link', { name: 'Go to about page' }))
503+
504+
await waitFor(() => {
505+
expect(router.state.location.pathname).toBe('/about')
506+
})
507+
508+
expect(getStylesheetLink()).toBe(initialLink)
509+
expect(
510+
Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(
511+
(link) => link.getAttribute('href') === '/main.css',
512+
),
513+
).toHaveLength(1)
514+
} finally {
515+
history.destroy()
516+
}
517+
})
330518
})
331519

332520
describe('data script rendering', () => {

0 commit comments

Comments
 (0)