-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
test(solid-start): serialization-adapters suite #5557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
39d1efa
6df6f56
230b4ff
12ad4aa
62807c4
8143d15
a6d4655
e9719e8
291e2d4
ec5dd8b
4050d97
860ab37
9fc9745
333ed61
706f2de
44f513b
349dedb
61be233
1835e59
a954ccc
77310bb
ec14ecb
eb19e59
3e7d446
333ae5a
20d7aaa
6357bab
265ff5e
7479380
14dd994
fdb188c
6f0b1d1
199c360
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| node_modules | ||
| package-lock.json | ||
| yarn.lock | ||
|
|
||
| .DS_Store | ||
| .cache | ||
| .env | ||
| .vercel | ||
| .output | ||
| /build/ | ||
| /api/ | ||
| /server/build | ||
| /public/build# Sentry Config File | ||
| .env.sentry-build-plugin | ||
| /test-results/ | ||
| /playwright-report/ | ||
| /blob-report/ | ||
| /playwright/.cache/ | ||
|
|
||
| count.txt | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| **/build | ||
| **/public | ||
| pnpm-lock.yaml | ||
| routeTree.gen.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| { | ||
| "name": "tanstack-solid-start-e2e-serialization-adapters", | ||
| "private": true, | ||
| "sideEffects": false, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite dev --port 3000", | ||
| "dev:e2e": "vite dev", | ||
| "build": "vite build && tsc --noEmit", | ||
| "start": "pnpx srvx --prod -s ../client dist/server/server.js", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify the client directory path in the start script. The path Run the following script to check if the referenced path exists and verify the intended directory structure: 🏁 Script executed: #!/bin/bash
# Description: Verify client directory structure and start script paths
# Check if ../client exists relative to this package
if [ -d "e2e/solid-start/serialization-adapters/../client" ]; then
echo "Found: e2e/solid-start/serialization-adapters/../client"
ls -la e2e/solid-start/serialization-adapters/../client
else
echo "Not found: e2e/solid-start/serialization-adapters/../client"
fi
# Look for client build directories within this package
fd -t d "client" e2e/solid-start/serialization-adapters/
# Check for dist or build directories that might contain client artifacts
fd -t d "^(dist|build)$" e2e/solid-start/serialization-adapters/ --max-depth 2Length of output: 59 🏁 Script executed: #!/bin/bash
# Description: Understand e2e/solid-start project structure and client reference
# List all directories in e2e/solid-start
echo "=== Directory structure of e2e/solid-start ==="
find e2e/solid-start -type d -not -path '*/node_modules/*' | head -20
# Find all package.json files to understand packages in e2e/solid-start
echo -e "\n=== Package.json files in e2e/solid-start ==="
fd -e "package.json" e2e/solid-start/ --max-depth 3
# Look for any 'client' directories anywhere in e2e or root
echo -e "\n=== Any 'client' directories in e2e/solid-start ==="
fd -t d "client" e2e/solid-start/ --max-depth 5
# Check if there's a client at root level
echo -e "\n=== Check root level directories ==="
ls -d */ 2>/dev/null | grep -E "(client|packages|apps)" || echo "No obvious client/packages/apps at root"
# Look at what the start script reference file contains and its context
echo -e "\n=== Full content of the package.json under review ==="
cat e2e/solid-start/serialization-adapters/package.jsonLength of output: 2402 🏁 Script executed: #!/bin/bash
# Description: Find all package.json, check build outputs, and understand project structure
# Find all package.json files properly
echo "=== All package.json files in e2e/solid-start ==="
fd --full-path "package.json" e2e/solid-start/
# Check if there's a build/dist directory with client artifacts
echo -e "\n=== Check for dist directories in serialization-adapters ==="
ls -la e2e/solid-start/serialization-adapters/ 2>/dev/null | grep -E "^d" || echo "No directories listed"
# Look for what exists at the parent level (e2e/solid-start)
echo -e "\n=== Contents of e2e/solid-start parent level ==="
ls -la e2e/solid-start/ | grep -E "^d" | head -20
# Check if there's a client directory elsewhere (e.g., in packages)
echo -e "\n=== Look for 'client' in packages/ ==="
find packages/ -type d -name "client" 2>/dev/null || echo "No client directories in packages/"
# Check other e2e packages' start scripts for comparison
echo -e "\n=== Start scripts in other e2e packages ==="
rg -A 1 '"start"' e2e/solid-start/*/package.json
# Check the build output structure by looking at vite.config or tsconfig
echo -e "\n=== Check vite.config in serialization-adapters ==="
ls -la e2e/solid-start/serialization-adapters/ | grep -i vite || echo "No vite.config found in listing"
fd "vite.config" e2e/solid-start/serialization-adapters/Length of output: 5693 🏁 Script executed: #!/bin/bash
# Description: Understand vite build output and srvx -s flag behavior
# Check vite.config.ts in serialization-adapters
echo "=== vite.config.ts in serialization-adapters ==="
cat e2e/solid-start/serialization-adapters/vite.config.ts
# Check if there's any build setup or shared client directory mentioned
echo -e "\n=== Check root vite.config or build config ==="
fd "vite.config" . --max-depth 1
fd "build" . --max-depth 1 -t f
# Look at package.json root for any build/setup scripts
echo -e "\n=== Root package.json scripts ==="
rg '"scripts"' -A 30 package.json | head -40
# Search for documentation or comments about srvx or ../client pattern
echo -e "\n=== Search for srvx or client path references ==="
rg "srvx" --type json --type ts --type js -B 2 -A 2 e2e/solid-start/basic/
# Check the vite build output by looking at what other e2e packages have
echo -e "\n=== Check dist structure in e2e/solid-start/basic after potential build ==="
ls -la e2e/solid-start/basic/dist 2>/dev/null || echo "No dist directory in basic"
# Look for srvx documentation or usage in the repo
echo -e "\n=== Search for srvx in entire repo ==="
rg "srvx" -t json | head -10Length of output: 4497 🌐 Web query: 💡 Result: The -s shorthand is the CLI's "silent" option (same as --silent) — it suppresses the server listening message/console output. See the Server Options docs (silent option). [1][2] Sources:
|
||
| "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" | ||
| }, | ||
| "dependencies": { | ||
| "@tanstack/solid-router": "workspace:^", | ||
| "@tanstack/solid-router-devtools": "workspace:^", | ||
| "@tanstack/solid-start": "workspace:^", | ||
| "solid-js": "^1.9.10", | ||
| "vite": "^7.1.7", | ||
| "vite-tsconfig-paths": "^5.1.4", | ||
| "zod": "^3.24.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@tailwindcss/postcss": "^4.1.15", | ||
| "@tanstack/router-e2e-utils": "workspace:^", | ||
| "@types/node": "^22.10.2", | ||
| "postcss": "^8.5.1", | ||
| "seroval": "^1.3.2", | ||
| "srvx": "^0.8.6", | ||
| "tailwindcss": "^4.1.15", | ||
| "typescript": "^5.7.2", | ||
| "vite-plugin-solid": "^2.11.9" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { defineConfig, devices } from '@playwright/test' | ||
| import { getTestServerPort } from '@tanstack/router-e2e-utils' | ||
| import packageJson from './package.json' with { type: 'json' } | ||
|
|
||
| const PORT = await getTestServerPort(packageJson.name) | ||
| const baseURL = `http://localhost:${PORT}` | ||
| /** | ||
| * See https://playwright.dev/docs/test-configuration. | ||
| */ | ||
| export default defineConfig({ | ||
| testDir: './tests', | ||
| workers: 1, | ||
|
|
||
| reporter: [['line']], | ||
|
|
||
| use: { | ||
| /* Base URL to use in actions like `await page.goto('/')`. */ | ||
| baseURL, | ||
| }, | ||
|
|
||
| webServer: { | ||
| command: `VITE_SERVER_PORT=${PORT} pnpm build && NODE_ENV=production PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`, | ||
| url: baseURL, | ||
| reuseExistingServer: !process.env.CI, | ||
| stdout: 'pipe', | ||
| }, | ||
|
|
||
| projects: [ | ||
| { | ||
| name: 'chromium', | ||
| use: { ...devices['Desktop Chrome'] }, | ||
| }, | ||
| ], | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export default { | ||
| plugins: { | ||
| '@tailwindcss/postcss': {}, | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { createSerializationAdapter } from '@tanstack/solid-router' | ||
|
|
||
| export class CustomError extends Error { | ||
| public foo: string | ||
| public bar: bigint | ||
|
|
||
| constructor(message: string, options: { foo: string; bar: bigint }) { | ||
| super(message) | ||
|
|
||
| Object.setPrototypeOf(this, new.target.prototype) | ||
|
|
||
| this.name = this.constructor.name | ||
| this.foo = options.foo | ||
| this.bar = options.bar | ||
| } | ||
| } | ||
|
|
||
| export const customErrorAdapter = createSerializationAdapter({ | ||
| key: 'custom-error', | ||
| test: (v) => v instanceof CustomError, | ||
| toSerializable: ({ message, foo, bar }) => { | ||
| return { | ||
| message, | ||
| foo, | ||
| bar, | ||
| } | ||
| }, | ||
| fromSerializable: ({ message, foo, bar }) => { | ||
| return new CustomError(message, { foo, bar }) | ||
| }, | ||
| }) | ||
|
Comment on lines
+21
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BigInt is not JSON-serializable; stringify in toSerializable and rehydrate in fromSerializable. Avoid runtime TypeError during transport by serializing -export const customErrorAdapter = createSerializationAdapter({
+export const customErrorAdapter = createSerializationAdapter<
+ CustomError,
+ { message: string; foo: string; bar: string }
+>({
key: 'custom-error',
- test: (v) => v instanceof CustomError,
- toSerializable: ({ message, foo, bar }) => {
- return {
- message,
- foo,
- bar,
- }
- },
- fromSerializable: ({ message, foo, bar }) => {
- return new CustomError(message, { foo, bar })
- },
+ test: (v: unknown): v is CustomError => v instanceof CustomError,
+ toSerializable: ({ message, foo, bar }) => ({
+ message,
+ foo,
+ bar: bar.toString(),
+ }),
+ fromSerializable: ({ message, foo, bar }) =>
+ new CustomError(message, { foo, bar: BigInt(bar) }),
})
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| import { createSerializationAdapter } from '@tanstack/solid-router' | ||
|
|
||
| export class Foo { | ||
| constructor(public value: string) {} | ||
| } | ||
|
|
||
| export interface Car { | ||
| __type: 'car' | ||
| make: string | ||
| model: string | ||
| year: number | ||
| honk: () => { message: string; make: string; model: string; year: number } | ||
| } | ||
|
|
||
| export function makeCar(opts: { | ||
| make: string | ||
| model: string | ||
| year: number | ||
| }): Car { | ||
| return { | ||
| ...opts, | ||
| __type: 'car', | ||
| honk: () => { | ||
| return { message: `Honk! Honk!`, ...opts } | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| export const fooAdapter = createSerializationAdapter({ | ||
| key: 'foo', | ||
| test: (value: any) => value instanceof Foo, | ||
| toSerializable: (foo) => foo.value, | ||
| fromSerializable: (value) => new Foo(value), | ||
| }) | ||
|
|
||
| export const carAdapter = createSerializationAdapter({ | ||
| key: 'car', | ||
| test: (value: any): value is Car => | ||
| '__type' in (value as Car) && value.__type === 'car', | ||
|
Comment on lines
+37
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against null/primitive in adapter test to avoid runtime TypeError.
export const carAdapter = createSerializationAdapter({
key: 'car',
- test: (value: any): value is Car =>
- '__type' in (value as Car) && value.__type === 'car',
+ test: (value: unknown): value is Car =>
+ typeof value === 'object' &&
+ value !== null &&
+ '__type' in (value as Record<string, unknown>) &&
+ (value as any).__type === 'car',🤖 Prompt for AI Agents |
||
| toSerializable: (car) => ({ | ||
| make: car.make, | ||
| model: car.model, | ||
| year: car.year, | ||
| }), | ||
| fromSerializable: (value: { make: string; model: string; year: number }) => | ||
| makeCar(value), | ||
| }) | ||
|
|
||
| export function makeData() { | ||
| function makeFoo(suffix: string = '') { | ||
| return new Foo(typeof window === 'undefined' ? 'server' : 'client' + suffix) | ||
| } | ||
| return { | ||
| foo: { | ||
| singleInstance: makeFoo(), | ||
| array: [makeFoo('0'), makeFoo('1'), makeFoo('2')], | ||
| map: new Map([ | ||
| [0, makeFoo('0')], | ||
| [1, makeFoo('1')], | ||
| [2, makeFoo('2')], | ||
| ]), | ||
| mapOfArrays: new Map([ | ||
| [0, [makeFoo('0-a'), makeFoo('0-b')]], | ||
| [1, [makeFoo('1-a'), makeFoo('1-b')]], | ||
| [2, [makeFoo('2-a'), makeFoo('2-b')]], | ||
| ]), | ||
| }, | ||
| car: { | ||
| singleInstance: makeCar({ | ||
| make: 'Toyota', | ||
| model: 'Camry', | ||
| year: 2020, | ||
| }), | ||
| array: [ | ||
| makeCar({ make: 'Honda', model: 'Accord', year: 2019 }), | ||
| makeCar({ make: 'Ford', model: 'Mustang', year: 2021 }), | ||
| ], | ||
| map: new Map([ | ||
| [0, makeCar({ make: 'Chevrolet', model: 'Malibu', year: 2018 })], | ||
| [1, makeCar({ make: 'Nissan', model: 'Altima', year: 2020 })], | ||
| [2, makeCar({ make: 'Hyundai', model: 'Sonata', year: 2021 })], | ||
| ]), | ||
| mapOfArrays: new Map([ | ||
| [0, [makeCar({ make: 'Kia', model: 'Optima', year: 2019 })]], | ||
| [1, [makeCar({ make: 'Subaru', model: 'Legacy', year: 2020 })]], | ||
| [2, [makeCar({ make: 'Volkswagen', model: 'Passat', year: 2021 })]], | ||
| ]), | ||
| }, | ||
| } | ||
| } | ||
| export class NestedOuter { | ||
| constructor(public inner: NestedInner) {} | ||
| whisper() { | ||
| return this.inner.value.toLowerCase() | ||
| } | ||
| } | ||
|
|
||
| export class NestedInner { | ||
| constructor(public value: string) {} | ||
| shout() { | ||
| return this.value.toUpperCase() | ||
| } | ||
| } | ||
|
|
||
| export const nestedInnerAdapter = createSerializationAdapter({ | ||
| key: 'nestedInner', | ||
| test: (value): value is NestedInner => value instanceof NestedInner, | ||
| toSerializable: (inner) => inner.value, | ||
| fromSerializable: (value) => new NestedInner(value), | ||
| }) | ||
|
|
||
| export const nestedOuterAdapter = createSerializationAdapter({ | ||
| key: 'nestedOuter', | ||
| extends: [nestedInnerAdapter], | ||
| test: (value) => value instanceof NestedOuter, | ||
| toSerializable: (outer) => outer.inner, | ||
| fromSerializable: (value) => new NestedOuter(value), | ||
| }) | ||
|
|
||
| export function makeNested() { | ||
| return new NestedOuter(new NestedInner('Hello World')) | ||
| } | ||
|
|
||
| export function RenderData({ | ||
| id, | ||
| data, | ||
| }: { | ||
| id: string | ||
| data: ReturnType<typeof makeData> | ||
| }) { | ||
| const localData = makeData() | ||
| return ( | ||
| <div data-testid={`${id}-container`}> | ||
| <h3>Car</h3> | ||
| <h4>expected</h4> | ||
| <div data-testid={`${id}-car-expected`}> | ||
| {JSON.stringify({ | ||
| make: localData.car.singleInstance.make, | ||
| model: localData.car.singleInstance.model, | ||
| year: localData.car.singleInstance.year, | ||
| })} | ||
| </div> | ||
| <h4>actual</h4> | ||
| <div data-testid={`${id}-car-actual`}> | ||
| {JSON.stringify({ | ||
| make: data.car.singleInstance.make, | ||
| model: data.car.singleInstance.model, | ||
| year: data.car.singleInstance.year, | ||
| })} | ||
| </div> | ||
| <b>Foo</b> | ||
| <div data-testid={`${id}-foo`}> | ||
| {JSON.stringify({ | ||
| value: data.foo.singleInstance.value, | ||
| })} | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export function RenderNestedData({ nested }: { nested: NestedOuter }) { | ||
| { | ||
| const localData = makeNested() | ||
| const expectedShoutState = localData.inner.shout() | ||
| const expectedWhisperState = localData.whisper() | ||
| const shoutState = nested.inner.shout() | ||
| const whisperState = nested.whisper() | ||
|
|
||
| return ( | ||
| <div data-testid="data-only-container"> | ||
| <h2 data-testid="data-only-heading">data-only</h2> | ||
| <div data-testid="shout-container"> | ||
| <h3>shout</h3> | ||
| <div> | ||
| expected:{' '} | ||
| <div data-testid="shout-expected-state"> | ||
| {JSON.stringify(expectedShoutState)} | ||
| </div> | ||
| </div> | ||
| <div> | ||
| actual:{' '} | ||
| <div data-testid="shout-actual-state"> | ||
| {JSON.stringify(shoutState)} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <div data-testid="whisper-container"> | ||
| <h3>whisper</h3> | ||
| <div> | ||
| expected:{' '} | ||
| <div data-testid="whisper-expected-state"> | ||
| {JSON.stringify(expectedWhisperState)} | ||
| </div> | ||
| </div> | ||
| <div> | ||
| actual:{' '} | ||
| <div data-testid="whisper-actual-state"> | ||
| {JSON.stringify(whisperState)} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Fix formatting: separate pattern and comment.
The pattern
/public/buildand the comment# Sentry Config Fileare on the same line. This makes the comment ineffective and could be confusing.📝 Committable suggestion
🤖 Prompt for AI Agents