Skip to content
Merged
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
11 changes: 11 additions & 0 deletions e2e/solid-router/generator-cli-only/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.DS_Store
dist
dist-hash
dist-ssr
*.local

/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
12 changes: 12 additions & 0 deletions e2e/solid-router/generator-cli-only/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
29 changes: 29 additions & 0 deletions e2e/solid-router/generator-cli-only/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "tanstack-solid-router-e2e-react-generator-cli-only",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix incorrect package name reference.

The package name contains "react" but this is a Solid Router e2e test suite. The name should reflect that it's for Solid, not React.

Apply this diff to correct the package name:

-  "name": "tanstack-solid-router-e2e-react-generator-cli-only",
+  "name": "tanstack-solid-router-e2e-solid-generator-cli-only",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"name": "tanstack-solid-router-e2e-react-generator-cli-only",
"name": "tanstack-solid-router-e2e-solid-generator-cli-only",
🤖 Prompt for AI Agents
In e2e/solid-router/generator-cli-only/package.json around line 2, the package
name incorrectly contains "react"; update the "name" field to reference "solid"
instead of "react" so it reflects the Solid Router e2e test suite (e.g., replace
"tanstack-solid-router-e2e-react-generator-cli-only" with a name that uses
"solid" such as "tanstack-solid-router-e2e-solid-generator-cli-only" or similar
project naming consistent with other Solid e2e packages).

"private": true,
"type": "module",
"scripts": {
"dev": "tsr generate && vite --port 3000",
"dev:e2e": "tsr generate && vite",
"build": "tsr generate && vite build && tsc --noEmit",
"serve": "vite preview",
"start": "tsr generate && vite",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
},
"dependencies": {
"@tailwindcss/postcss": "^4.1.15",
"@tanstack/solid-router": "workspace:^",
"@tanstack/solid-router-devtools": "workspace:^",
"@tanstack/router-cli": "workspace:^",
"postcss": "^8.5.1",
"solid-js": "^1.9.9",
"redaxios": "^0.5.1",
"tailwindcss": "^4.1.15"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tanstack/router-e2e-utils": "workspace:^",
"vite-plugin-solid": "^2.11.10",
"vite": "^7.1.7"
}
}
41 changes: 41 additions & 0 deletions e2e/solid-router/generator-cli-only/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
import {
getDummyServerPort,
getTestServerPort,
} from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }

const PORT = await getTestServerPort(packageJson.name)
const EXTERNAL_PORT = await getDummyServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}`
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
workers: 1,

reporter: [['line']],

globalSetup: './tests/setup/global.setup.ts',
globalTeardown: './tests/setup/global.teardown.ts',

use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL,
},

webServer: {
command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm build && VITE_SERVER_PORT=${PORT} pnpm serve --port ${PORT}`,
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
},

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
})
5 changes: 5 additions & 0 deletions e2e/solid-router/generator-cli-only/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
25 changes: 25 additions & 0 deletions e2e/solid-router/generator-cli-only/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { RouterProvider, createRouter } from '@tanstack/solid-router'
import { render } from 'solid-js/web'
import { routeTree } from './routeTree.gen'
import './styles.css'

// Set up a Router instance
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultStaleTime: 5000,
scrollRestoration: true,
})

// Register things for typesafety
declare module '@tanstack/solid-router' {
interface Register {
router: typeof router
}
}

const rootElement = document.getElementById('app')!

if (!rootElement.innerHTML) {
render(() => <RouterProvider router={router} />, rootElement)
}
36 changes: 36 additions & 0 deletions e2e/solid-router/generator-cli-only/src/posts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axios from 'redaxios'

export class NotFoundError extends Error {}

type PostType = {
id: string
title: string
body: string
}

let queryURL = 'https://jsonplaceholder.typicode.com'

if (import.meta.env.VITE_NODE_ENV === 'test') {
queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}`
}

export const fetchPosts = async () => {
console.info('Fetching posts...')
return axios
.get<Array<PostType>>(`${queryURL}/posts`)
.then((r) => r.data.slice(0, 10))
}

export const fetchPost = async (postId: string) => {
console.info(`Fetching post with id ${postId}...`)
const post = await axios
.get<PostType>(`${queryURL}/posts/${postId}`)
.then((r) => r.data)

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!post) {
throw new NotFoundError(`Post with id "${postId}" not found!`)
}

return post
}
Comment on lines +24 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Map 404s (and empty payloads) to NotFoundError so route notFoundComponent renders.

As written, axios 404 rejects before if (!post) runs, so the not-found UI won’t show. Catch 404s and throw NotFoundError; also validate empty bodies and encode the param.

Apply this diff:

-export const fetchPost = async (postId: string) => {
-  console.info(`Fetching post with id ${postId}...`)
-  const post = await axios
-    .get<PostType>(`${queryURL}/posts/${postId}`)
-    .then((r) => r.data)
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-  if (!post) {
-    throw new NotFoundError(`Post with id "${postId}" not found!`)
-  }
-
-  return post
-}
+export const fetchPost = async (postId: string) => {
+  console.info(`Fetching post with id ${postId}...`)
+  try {
+    const post = await axios
+      .get<PostType | null>(`${queryURL}/posts/${encodeURIComponent(postId)}`)
+      .then((r) => r.data)
+
+    // Treat null/empty payloads as not found
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (!post || !(post as any).id) {
+      throw new NotFoundError(`Post with id "${postId}" not found!`)
+    }
+    return post
+  } catch (err: any) {
+    const status = err?.response?.status ?? err?.status
+    if (status === 404) {
+      throw new NotFoundError(`Post with id "${postId}" not found!`)
+    }
+    throw err
+  }
+}
🤖 Prompt for AI Agents
In e2e/solid-router/generator-cli-only/src/posts.ts around lines 24 to 36, the
axios call will reject on 404 so the existing null-check never runs and the
notFoundComponent won’t render; wrap the request in a try/catch, use
encodeURIComponent(postId) in the URL, and in the catch map a 404 response to
throw new NotFoundError(postId) while rethrowing other errors; after the request
also verify the response body is present and throw NotFoundError if it’s empty.

230 changes: 230 additions & 0 deletions e2e/solid-router/generator-cli-only/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout'
import { Route as PostsRouteRouteImport } from './routes/posts.route'
import { Route as IndexRouteImport } from './routes/index'
import { Route as PostsIndexRouteImport } from './routes/posts.index'
import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'
import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout'
import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b'
import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a'

const PathlessLayoutRoute = PathlessLayoutRouteImport.update({
id: '/_pathlessLayout',
getParentRoute: () => rootRouteImport,
} as any)
const PostsRouteRoute = PostsRouteRouteImport.update({
id: '/posts',
path: '/posts',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const PostsIndexRoute = PostsIndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => PostsRouteRoute,
} as any)
const PostsPostIdRoute = PostsPostIdRouteImport.update({
id: '/$postId',
path: '/$postId',
getParentRoute: () => PostsRouteRoute,
} as any)
const PathlessLayoutNestedLayoutRoute =
PathlessLayoutNestedLayoutRouteImport.update({
id: '/_nested-layout',
getParentRoute: () => PathlessLayoutRoute,
} as any)
const PathlessLayoutNestedLayoutRouteBRoute =
PathlessLayoutNestedLayoutRouteBRouteImport.update({
id: '/route-b',
path: '/route-b',
getParentRoute: () => PathlessLayoutNestedLayoutRoute,
} as any)
const PathlessLayoutNestedLayoutRouteARoute =
PathlessLayoutNestedLayoutRouteARouteImport.update({
id: '/route-a',
path: '/route-a',
getParentRoute: () => PathlessLayoutNestedLayoutRoute,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/posts': typeof PostsRouteRouteWithChildren
'/posts/$postId': typeof PostsPostIdRoute
'/posts/': typeof PostsIndexRoute
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/posts/$postId': typeof PostsPostIdRoute
'/posts': typeof PostsIndexRoute
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/posts': typeof PostsRouteRouteWithChildren
'/_pathlessLayout': typeof PathlessLayoutRouteWithChildren
'/_pathlessLayout/_nested-layout': typeof PathlessLayoutNestedLayoutRouteWithChildren
'/posts/$postId': typeof PostsPostIdRoute
'/posts/': typeof PostsIndexRoute
'/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
'/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/posts'
| '/posts/$postId'
| '/posts/'
| '/route-a'
| '/route-b'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/posts/$postId' | '/posts' | '/route-a' | '/route-b'
id:
| '__root__'
| '/'
| '/posts'
| '/_pathlessLayout'
| '/_pathlessLayout/_nested-layout'
| '/posts/$postId'
| '/posts/'
| '/_pathlessLayout/_nested-layout/route-a'
| '/_pathlessLayout/_nested-layout/route-b'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
PostsRouteRoute: typeof PostsRouteRouteWithChildren
PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren
}

declare module '@tanstack/solid-router' {
interface FileRoutesByPath {
'/_pathlessLayout': {
id: '/_pathlessLayout'
path: ''
fullPath: ''
preLoaderRoute: typeof PathlessLayoutRouteImport
parentRoute: typeof rootRouteImport
}
'/posts': {
id: '/posts'
path: '/posts'
fullPath: '/posts'
preLoaderRoute: typeof PostsRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/posts/': {
id: '/posts/'
path: '/'
fullPath: '/posts/'
preLoaderRoute: typeof PostsIndexRouteImport
parentRoute: typeof PostsRouteRoute
}
'/posts/$postId': {
id: '/posts/$postId'
path: '/$postId'
fullPath: '/posts/$postId'
preLoaderRoute: typeof PostsPostIdRouteImport
parentRoute: typeof PostsRouteRoute
}
'/_pathlessLayout/_nested-layout': {
id: '/_pathlessLayout/_nested-layout'
path: ''
fullPath: ''
preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport
parentRoute: typeof PathlessLayoutRoute
}
'/_pathlessLayout/_nested-layout/route-b': {
id: '/_pathlessLayout/_nested-layout/route-b'
path: '/route-b'
fullPath: '/route-b'
preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport
parentRoute: typeof PathlessLayoutNestedLayoutRoute
}
'/_pathlessLayout/_nested-layout/route-a': {
id: '/_pathlessLayout/_nested-layout/route-a'
path: '/route-a'
fullPath: '/route-a'
preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport
parentRoute: typeof PathlessLayoutNestedLayoutRoute
}
}
}

interface PostsRouteRouteChildren {
PostsPostIdRoute: typeof PostsPostIdRoute
PostsIndexRoute: typeof PostsIndexRoute
}

const PostsRouteRouteChildren: PostsRouteRouteChildren = {
PostsPostIdRoute: PostsPostIdRoute,
PostsIndexRoute: PostsIndexRoute,
}

const PostsRouteRouteWithChildren = PostsRouteRoute._addFileChildren(
PostsRouteRouteChildren,
)

interface PathlessLayoutNestedLayoutRouteChildren {
PathlessLayoutNestedLayoutRouteARoute: typeof PathlessLayoutNestedLayoutRouteARoute
PathlessLayoutNestedLayoutRouteBRoute: typeof PathlessLayoutNestedLayoutRouteBRoute
}

const PathlessLayoutNestedLayoutRouteChildren: PathlessLayoutNestedLayoutRouteChildren =
{
PathlessLayoutNestedLayoutRouteARoute:
PathlessLayoutNestedLayoutRouteARoute,
PathlessLayoutNestedLayoutRouteBRoute:
PathlessLayoutNestedLayoutRouteBRoute,
}

const PathlessLayoutNestedLayoutRouteWithChildren =
PathlessLayoutNestedLayoutRoute._addFileChildren(
PathlessLayoutNestedLayoutRouteChildren,
)

interface PathlessLayoutRouteChildren {
PathlessLayoutNestedLayoutRoute: typeof PathlessLayoutNestedLayoutRouteWithChildren
}

const PathlessLayoutRouteChildren: PathlessLayoutRouteChildren = {
PathlessLayoutNestedLayoutRoute: PathlessLayoutNestedLayoutRouteWithChildren,
}

const PathlessLayoutRouteWithChildren = PathlessLayoutRoute._addFileChildren(
PathlessLayoutRouteChildren,
)

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
PostsRouteRoute: PostsRouteRouteWithChildren,
PathlessLayoutRoute: PathlessLayoutRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
Loading
Loading