Skip to content

Commit dd8867d

Browse files
authored
test(solid-start): serialization-adapters suite (#5557)
1 parent 818a4ee commit dd8867d

File tree

25 files changed

+1149
-22
lines changed

25 files changed

+1149
-22
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: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "tanstack-solid-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 -s ../client dist/server/server.js",
11+
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
12+
},
13+
"dependencies": {
14+
"@tanstack/solid-router": "workspace:^",
15+
"@tanstack/solid-router-devtools": "workspace:^",
16+
"@tanstack/solid-start": "workspace:^",
17+
"solid-js": "^1.9.10",
18+
"vite": "^7.1.7",
19+
"vite-tsconfig-paths": "^5.1.4",
20+
"zod": "^3.24.2"
21+
},
22+
"devDependencies": {
23+
"@tailwindcss/postcss": "^4.1.15",
24+
"@tanstack/router-e2e-utils": "workspace:^",
25+
"@types/node": "^22.10.2",
26+
"postcss": "^8.5.1",
27+
"seroval": "^1.3.2",
28+
"srvx": "^0.8.6",
29+
"tailwindcss": "^4.1.15",
30+
"typescript": "^5.7.2",
31+
"vite-plugin-solid": "^2.11.9"
32+
}
33+
}
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
plugins: {
3+
'@tailwindcss/postcss': {},
4+
},
5+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createSerializationAdapter } from '@tanstack/solid-router'
2+
3+
export class CustomError extends Error {
4+
public foo: string
5+
public bar: bigint
6+
7+
constructor(message: string, options: { foo: string; bar: bigint }) {
8+
super(message)
9+
10+
Object.setPrototypeOf(this, new.target.prototype)
11+
12+
this.name = this.constructor.name
13+
this.foo = options.foo
14+
this.bar = options.bar
15+
}
16+
}
17+
18+
export const customErrorAdapter = createSerializationAdapter({
19+
key: 'custom-error',
20+
test: (v) => v instanceof CustomError,
21+
toSerializable: ({ message, foo, bar }) => {
22+
return {
23+
message,
24+
foo,
25+
bar,
26+
}
27+
},
28+
fromSerializable: ({ message, foo, bar }) => {
29+
return new CustomError(message, { foo, bar })
30+
},
31+
})
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { createSerializationAdapter } from '@tanstack/solid-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+
export class NestedOuter {
92+
constructor(public inner: NestedInner) {}
93+
whisper() {
94+
return this.inner.value.toLowerCase()
95+
}
96+
}
97+
98+
export class NestedInner {
99+
constructor(public value: string) {}
100+
shout() {
101+
return this.value.toUpperCase()
102+
}
103+
}
104+
105+
export const nestedInnerAdapter = createSerializationAdapter({
106+
key: 'nestedInner',
107+
test: (value): value is NestedInner => value instanceof NestedInner,
108+
toSerializable: (inner) => inner.value,
109+
fromSerializable: (value) => new NestedInner(value),
110+
})
111+
112+
export const nestedOuterAdapter = createSerializationAdapter({
113+
key: 'nestedOuter',
114+
extends: [nestedInnerAdapter],
115+
test: (value) => value instanceof NestedOuter,
116+
toSerializable: (outer) => outer.inner,
117+
fromSerializable: (value) => new NestedOuter(value),
118+
})
119+
120+
export function makeNested() {
121+
return new NestedOuter(new NestedInner('Hello World'))
122+
}
123+
124+
export function RenderData({
125+
id,
126+
data,
127+
}: {
128+
id: string
129+
data: ReturnType<typeof makeData>
130+
}) {
131+
const localData = makeData()
132+
return (
133+
<div data-testid={`${id}-container`}>
134+
<h3>Car</h3>
135+
<h4>expected</h4>
136+
<div data-testid={`${id}-car-expected`}>
137+
{JSON.stringify({
138+
make: localData.car.singleInstance.make,
139+
model: localData.car.singleInstance.model,
140+
year: localData.car.singleInstance.year,
141+
})}
142+
</div>
143+
<h4>actual</h4>
144+
<div data-testid={`${id}-car-actual`}>
145+
{JSON.stringify({
146+
make: data.car.singleInstance.make,
147+
model: data.car.singleInstance.model,
148+
year: data.car.singleInstance.year,
149+
})}
150+
</div>
151+
<b>Foo</b>
152+
<div data-testid={`${id}-foo`}>
153+
{JSON.stringify({
154+
value: data.foo.singleInstance.value,
155+
})}
156+
</div>
157+
</div>
158+
)
159+
}
160+
161+
export function RenderNestedData({ nested }: { nested: NestedOuter }) {
162+
{
163+
const localData = makeNested()
164+
const expectedShoutState = localData.inner.shout()
165+
const expectedWhisperState = localData.whisper()
166+
const shoutState = nested.inner.shout()
167+
const whisperState = nested.whisper()
168+
169+
return (
170+
<div data-testid="data-only-container">
171+
<h2 data-testid="data-only-heading">data-only</h2>
172+
<div data-testid="shout-container">
173+
<h3>shout</h3>
174+
<div>
175+
expected:{' '}
176+
<div data-testid="shout-expected-state">
177+
{JSON.stringify(expectedShoutState)}
178+
</div>
179+
</div>
180+
<div>
181+
actual:{' '}
182+
<div data-testid="shout-actual-state">
183+
{JSON.stringify(shoutState)}
184+
</div>
185+
</div>
186+
</div>
187+
<div data-testid="whisper-container">
188+
<h3>whisper</h3>
189+
<div>
190+
expected:{' '}
191+
<div data-testid="whisper-expected-state">
192+
{JSON.stringify(expectedWhisperState)}
193+
</div>
194+
</div>
195+
<div>
196+
actual:{' '}
197+
<div data-testid="whisper-actual-state">
198+
{JSON.stringify(whisperState)}
199+
</div>
200+
</div>
201+
</div>
202+
</div>
203+
)
204+
}
205+
}

0 commit comments

Comments
 (0)