Skip to content

Commit 1554d6b

Browse files
schiller-manueltannerlinsley
authored andcommitted
add e2e test
1 parent 2f8af5f commit 1554d6b

19 files changed

+2273
-3697
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
node_modules
2+
package-lock.json
3+
yarn.lock
4+
5+
.DS_Store
6+
.cache
7+
.env
8+
.vercel
9+
.output
10+
/build/
11+
/api/
12+
/server/build
13+
/public/build# Sentry Config File
14+
.env.sentry-build-plugin
15+
/test-results/
16+
/playwright-report/
17+
/blob-report/
18+
/playwright/.cache/
19+
20+
count.txt
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
**/build
2+
**/public
3+
pnpm-lock.yaml
4+
routeTree.gen.ts
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "tanstack-react-start-e2e-serialization-adapters",
3+
"private": true,
4+
"sideEffects": false,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite dev --port 3000",
8+
"dev:e2e": "vite dev",
9+
"build": "vite build && tsc --noEmit",
10+
"start": "pnpx srvx --prod dist/server.js",
11+
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
12+
},
13+
"dependencies": {
14+
"@tanstack/react-router": "workspace:^",
15+
"@tanstack/react-router-devtools": "workspace:^",
16+
"@tanstack/react-start": "workspace:^",
17+
"react": "^19.0.0",
18+
"react-dom": "^19.0.0",
19+
"vite": "^7.1.1",
20+
"vite-tsconfig-paths": "^5.1.4",
21+
"zod": "^3.24.2"
22+
},
23+
"devDependencies": {
24+
"@tanstack/router-e2e-utils": "workspace:^",
25+
"@vitejs/plugin-react": "^4.3.4",
26+
"@types/node": "^22.10.2",
27+
"@types/react": "^19.0.8",
28+
"@types/react-dom": "^19.0.3",
29+
"postcss": "^8.5.1",
30+
"srvx": "^0.8.6",
31+
"tailwindcss": "^3.4.17",
32+
"typescript": "^5.7.2"
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { defineConfig, devices } from '@playwright/test'
2+
import { getTestServerPort } from '@tanstack/router-e2e-utils'
3+
import packageJson from './package.json' with { type: 'json' }
4+
5+
const PORT = await getTestServerPort(packageJson.name)
6+
const baseURL = `http://localhost:${PORT}`
7+
/**
8+
* See https://playwright.dev/docs/test-configuration.
9+
*/
10+
export default defineConfig({
11+
testDir: './tests',
12+
workers: 1,
13+
14+
reporter: [['line']],
15+
16+
use: {
17+
/* Base URL to use in actions like `await page.goto('/')`. */
18+
baseURL,
19+
},
20+
21+
webServer: {
22+
command: `VITE_SERVER_PORT=${PORT} pnpm build && NODE_ENV=production PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`,
23+
url: baseURL,
24+
reuseExistingServer: !process.env.CI,
25+
stdout: 'pipe',
26+
},
27+
28+
projects: [
29+
{
30+
name: 'chromium',
31+
use: { ...devices['Desktop Chrome'] },
32+
},
33+
],
34+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { createSerializationAdapter } from '@tanstack/react-router'
2+
3+
export class Foo {
4+
constructor(public value: string) {}
5+
}
6+
7+
export interface Car {
8+
__type: 'car'
9+
make: string
10+
model: string
11+
year: number
12+
honk: () => { message: string; make: string; model: string; year: number }
13+
}
14+
15+
export function makeCar(opts: {
16+
make: string
17+
model: string
18+
year: number
19+
}): Car {
20+
return {
21+
...opts,
22+
__type: 'car',
23+
honk: () => {
24+
return { message: `Honk! Honk!`, ...opts }
25+
},
26+
}
27+
}
28+
29+
export const fooAdapter = createSerializationAdapter({
30+
key: 'foo',
31+
test: (value: any) => value instanceof Foo,
32+
toSerializable: (foo) => foo.value,
33+
fromSerializable: (value) => new Foo(value),
34+
})
35+
36+
export const carAdapter = createSerializationAdapter({
37+
key: 'car',
38+
test: (value: any): value is Car =>
39+
'__type' in (value as Car) && value.__type === 'car',
40+
toSerializable: (car) => ({
41+
make: car.make,
42+
model: car.model,
43+
year: car.year,
44+
}),
45+
fromSerializable: (value: { make: string; model: string; year: number }) =>
46+
makeCar(value),
47+
})
48+
49+
export function makeData() {
50+
function makeFoo(suffix: string = '') {
51+
return new Foo(typeof window === 'undefined' ? 'server' : 'client' + suffix)
52+
}
53+
return {
54+
foo: {
55+
singleInstance: makeFoo(),
56+
array: [makeFoo('0'), makeFoo('1'), makeFoo('2')],
57+
map: new Map([
58+
[0, makeFoo('0')],
59+
[1, makeFoo('1')],
60+
[2, makeFoo('2')],
61+
]),
62+
mapOfArrays: new Map([
63+
[0, [makeFoo('0-a'), makeFoo('0-b')]],
64+
[1, [makeFoo('1-a'), makeFoo('1-b')]],
65+
[2, [makeFoo('2-a'), makeFoo('2-b')]],
66+
]),
67+
},
68+
car: {
69+
singleInstance: makeCar({
70+
make: 'Toyota',
71+
model: 'Camry',
72+
year: 2020,
73+
}),
74+
array: [
75+
makeCar({ make: 'Honda', model: 'Accord', year: 2019 }),
76+
makeCar({ make: 'Ford', model: 'Mustang', year: 2021 }),
77+
],
78+
map: new Map([
79+
[0, makeCar({ make: 'Chevrolet', model: 'Malibu', year: 2018 })],
80+
[1, makeCar({ make: 'Nissan', model: 'Altima', year: 2020 })],
81+
[2, makeCar({ make: 'Hyundai', model: 'Sonata', year: 2021 })],
82+
]),
83+
mapOfArrays: new Map([
84+
[0, [makeCar({ make: 'Kia', model: 'Optima', year: 2019 })]],
85+
[1, [makeCar({ make: 'Subaru', model: 'Legacy', year: 2020 })]],
86+
[2, [makeCar({ make: 'Volkswagen', model: 'Passat', year: 2021 })]],
87+
]),
88+
},
89+
}
90+
}
91+
92+
export function RenderData({
93+
id,
94+
data,
95+
}: {
96+
id: string
97+
data: ReturnType<typeof makeData>
98+
}) {
99+
const localData = makeData()
100+
return (
101+
<div data-testid={`${id}-container`}>
102+
<h3>Car</h3>
103+
<h4>expected</h4>
104+
<div data-testid={`${id}-car-expected`}>
105+
{JSON.stringify({
106+
make: localData.car.singleInstance.make,
107+
model: localData.car.singleInstance.model,
108+
year: localData.car.singleInstance.year,
109+
})}
110+
</div>
111+
<h4>actual</h4>
112+
<div data-testid={`${id}-car-actual`}>
113+
{JSON.stringify({
114+
make: data.car.singleInstance.make,
115+
model: data.car.singleInstance.model,
116+
year: data.car.singleInstance.year,
117+
})}
118+
</div>
119+
<b>Foo</b>
120+
<div data-testid={`${id}-foo`}>
121+
{JSON.stringify({
122+
value: data.foo.singleInstance.value,
123+
})}
124+
</div>
125+
</div>
126+
)
127+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* eslint-disable */
2+
3+
// @ts-nocheck
4+
5+
// noinspection JSUnusedGlobalSymbols
6+
7+
// This file was automatically generated by TanStack Router.
8+
// You should NOT make any changes in this file as it will be overwritten.
9+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10+
11+
import { Route as rootRouteImport } from './routes/__root'
12+
import { Route as StreamRouteImport } from './routes/stream'
13+
import { Route as DataOnlyRouteImport } from './routes/data-only'
14+
import { Route as SsrRouteImport } from './routes/_ssr'
15+
import { Route as IndexRouteImport } from './routes/index'
16+
17+
const StreamRoute = StreamRouteImport.update({
18+
id: '/stream',
19+
path: '/stream',
20+
getParentRoute: () => rootRouteImport,
21+
} as any)
22+
const DataOnlyRoute = DataOnlyRouteImport.update({
23+
id: '/data-only',
24+
path: '/data-only',
25+
getParentRoute: () => rootRouteImport,
26+
} as any)
27+
const SsrRoute = SsrRouteImport.update({
28+
id: '/_ssr',
29+
getParentRoute: () => rootRouteImport,
30+
} as any)
31+
const IndexRoute = IndexRouteImport.update({
32+
id: '/',
33+
path: '/',
34+
getParentRoute: () => rootRouteImport,
35+
} as any)
36+
37+
export interface FileRoutesByFullPath {
38+
'/': typeof IndexRoute
39+
'/data-only': typeof DataOnlyRoute
40+
'/stream': typeof StreamRoute
41+
}
42+
export interface FileRoutesByTo {
43+
'/': typeof IndexRoute
44+
'/data-only': typeof DataOnlyRoute
45+
'/stream': typeof StreamRoute
46+
}
47+
export interface FileRoutesById {
48+
__root__: typeof rootRouteImport
49+
'/': typeof IndexRoute
50+
'/_ssr': typeof SsrRoute
51+
'/data-only': typeof DataOnlyRoute
52+
'/stream': typeof StreamRoute
53+
}
54+
export interface FileRouteTypes {
55+
fileRoutesByFullPath: FileRoutesByFullPath
56+
fullPaths: '/' | '/data-only' | '/stream'
57+
fileRoutesByTo: FileRoutesByTo
58+
to: '/' | '/data-only' | '/stream'
59+
id: '__root__' | '/' | '/_ssr' | '/data-only' | '/stream'
60+
fileRoutesById: FileRoutesById
61+
}
62+
export interface RootRouteChildren {
63+
IndexRoute: typeof IndexRoute
64+
SsrRoute: typeof SsrRoute
65+
DataOnlyRoute: typeof DataOnlyRoute
66+
StreamRoute: typeof StreamRoute
67+
}
68+
69+
declare module '@tanstack/react-router' {
70+
interface FileRoutesByPath {
71+
'/stream': {
72+
id: '/stream'
73+
path: '/stream'
74+
fullPath: '/stream'
75+
preLoaderRoute: typeof StreamRouteImport
76+
parentRoute: typeof rootRouteImport
77+
}
78+
'/data-only': {
79+
id: '/data-only'
80+
path: '/data-only'
81+
fullPath: '/data-only'
82+
preLoaderRoute: typeof DataOnlyRouteImport
83+
parentRoute: typeof rootRouteImport
84+
}
85+
'/_ssr': {
86+
id: '/_ssr'
87+
path: ''
88+
fullPath: ''
89+
preLoaderRoute: typeof SsrRouteImport
90+
parentRoute: typeof rootRouteImport
91+
}
92+
'/': {
93+
id: '/'
94+
path: '/'
95+
fullPath: '/'
96+
preLoaderRoute: typeof IndexRouteImport
97+
parentRoute: typeof rootRouteImport
98+
}
99+
}
100+
}
101+
102+
const rootRouteChildren: RootRouteChildren = {
103+
IndexRoute: IndexRoute,
104+
SsrRoute: SsrRoute,
105+
DataOnlyRoute: DataOnlyRoute,
106+
StreamRoute: StreamRoute,
107+
}
108+
export const routeTree = rootRouteImport
109+
._addFileChildren(rootRouteChildren)
110+
._addFileTypes<FileRouteTypes>()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
2+
import { routeTree } from './routeTree.gen'
3+
import { carAdapter, fooAdapter } from './data'
4+
5+
export function createRouter() {
6+
const router = createTanStackRouter({
7+
routeTree,
8+
scrollRestoration: true,
9+
serializationAdapters: [fooAdapter, carAdapter],
10+
})
11+
12+
return router
13+
}
14+
15+
declare module '@tanstack/react-router' {
16+
interface Register {
17+
router: ReturnType<typeof createRouter>
18+
}
19+
}

0 commit comments

Comments
 (0)