Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion e2e/solid-start/selective-ssr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"build": "vite build && tsc --noEmit",
"preview": "vite preview",
"start": "pnpx srvx --prod -s ../client dist/server/server.js",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium",
"test:e2e:dev": "rm -rf port*.txt; playwright test --config=playwright.dev.config.ts --project=chromium"
},
"dependencies": {
"@tanstack/solid-router": "workspace:^",
Expand Down
27 changes: 27 additions & 0 deletions e2e/solid-start/selective-ssr/playwright.dev.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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}_dev`)
const baseURL = `http://localhost:${PORT}`

export default defineConfig({
testDir: './tests',
workers: 1,
reporter: [['line']],
use: {
baseURL,
},
webServer: {
command: `VITE_SERVER_PORT=${PORT} pnpm exec vite dev --host 127.0.0.1 --port ${PORT}`,
url: baseURL,
reuseExistingServer: false,
stdout: 'pipe',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
})
24 changes: 21 additions & 3 deletions e2e/solid-start/selective-ssr/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { Route as rootRouteImport } from './routes/__root'
import { Route as PostsRouteImport } from './routes/posts'
import { Route as MreDataOnlyRouteImport } from './routes/mre-data-only'
import { Route as IndexRouteImport } from './routes/index'
import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'

Expand All @@ -18,6 +19,11 @@ const PostsRoute = PostsRouteImport.update({
path: '/posts',
getParentRoute: () => rootRouteImport,
} as any)
const MreDataOnlyRoute = MreDataOnlyRouteImport.update({
id: '/mre-data-only',
path: '/mre-data-only',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
Expand All @@ -31,30 +37,34 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/mre-data-only': typeof MreDataOnlyRoute
'/posts': typeof PostsRouteWithChildren
'/posts/$postId': typeof PostsPostIdRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/mre-data-only': typeof MreDataOnlyRoute
'/posts': typeof PostsRouteWithChildren
'/posts/$postId': typeof PostsPostIdRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/mre-data-only': typeof MreDataOnlyRoute
'/posts': typeof PostsRouteWithChildren
'/posts/$postId': typeof PostsPostIdRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/posts' | '/posts/$postId'
fullPaths: '/' | '/mre-data-only' | '/posts' | '/posts/$postId'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/posts' | '/posts/$postId'
id: '__root__' | '/' | '/posts' | '/posts/$postId'
to: '/' | '/mre-data-only' | '/posts' | '/posts/$postId'
id: '__root__' | '/' | '/mre-data-only' | '/posts' | '/posts/$postId'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
MreDataOnlyRoute: typeof MreDataOnlyRoute
PostsRoute: typeof PostsRouteWithChildren
}

Expand All @@ -67,6 +77,13 @@ declare module '@tanstack/solid-router' {
preLoaderRoute: typeof PostsRouteImport
parentRoute: typeof rootRouteImport
}
'/mre-data-only': {
id: '/mre-data-only'
path: '/mre-data-only'
fullPath: '/mre-data-only'
preLoaderRoute: typeof MreDataOnlyRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
Expand Down Expand Up @@ -96,6 +113,7 @@ const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren)

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
MreDataOnlyRoute: MreDataOnlyRoute,
PostsRoute: PostsRouteWithChildren,
}
export const routeTree = rootRouteImport
Expand Down
6 changes: 6 additions & 0 deletions e2e/solid-start/selective-ssr/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ function Home() {
<div>
test count: <b data-testid="test-count">{links.length}</b>
</div>
<div>
<Link data-testid="mre-data-only-link" to="/mre-data-only">
MRE data-only + pendingComponent
</Link>
</div>
<br />
<div>{links}</div>
</>
)
Expand Down
24 changes: 24 additions & 0 deletions e2e/solid-start/selective-ssr/src/routes/mre-data-only.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/mre-data-only')({
ssr: 'data-only',
loader: async () => {
await new Promise((resolve) => setTimeout(resolve, 900))
return { loadedAt: new Date().toISOString() }
},
pendingComponent: () => <div>PENDING</div>,
component: MreDataOnlyRoute,
})

function MreDataOnlyRoute() {
const data = Route.useLoaderData()

return (
<div data-testid="mre-data-only-ready">
<p data-testid="mre-data-only-ready-label">OK — loader finished</p>
<pre data-testid="mre-data-only-ready-data">
{JSON.stringify(data(), null, 2)}
</pre>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { expect } from '@playwright/test'
import { test } from '@tanstack/router-e2e-utils'

test.describe('pending component hydration', () => {
test('data-only route hydrates from pending element to loaded state on first load', async ({
page,
}) => {
const browserErrors: string[] = []

page.on('pageerror', (error) => {
browserErrors.push(error.message)
})

page.on('console', (message) => {
if (message.type() === 'error' || message.type() === 'warning') {
browserErrors.push(message.text())
}
})

await page.goto('/mre-data-only')

await expect(page.getByTestId('mre-data-only-ready-label')).toHaveText(
'OK — loader finished',
{ timeout: 5_000 },
)

expect(browserErrors).not.toContainEqual(
expect.stringContaining('template is not a function'),
)
expect(browserErrors).not.toContainEqual(
expect.stringContaining('Hydration Mismatch'),
)
})
})