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
13 changes: 13 additions & 0 deletions e2e/react-start/basic/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/// <reference types="vite/client" />
import * as React from 'react'
import {
ClientOnly,
HeadContent,
Link,
Outlet,
Scripts,
createRootRoute,
useRouterState,
} from '@tanstack/react-router'

import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
Expand Down Expand Up @@ -92,6 +94,11 @@ const RouterDevtools =
)

function RootDocument({ children }: { children: React.ReactNode }) {
const { isLoading, status } = useRouterState({
select: (state) => ({ isLoading: state.isLoading, status: state.status }),
structuralSharing: true,
})

return (
<html>
<head>
Expand Down Expand Up @@ -209,6 +216,12 @@ function RootDocument({ children }: { children: React.ReactNode }) {
</Link>
</div>
<hr />
<ClientOnly>
<div hidden>
<b data-testid="router-isLoading">{isLoading ? 'true' : 'false'}</b>
<b data-testid="router-status">{status}</b>
</div>
</ClientOnly>
{children}
<div className="inline-div">This is an inline styled div</div>
<React.Suspense fallback={null}>
Expand Down
42 changes: 25 additions & 17 deletions e2e/react-start/basic/tests/redirect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import queryString from 'node:querystring'
import { expect } from '@playwright/test'
import { expect, type Page } from '@playwright/test'
import combinateImport from 'combinate'
import {
getDummyServerPort,
Expand All @@ -15,6 +15,19 @@ const e2ePortKey = getE2EPortKey()
const PORT = await getTestServerPort(e2ePortKey)

const EXTERNAL_HOST_PORT = await getDummyServerPort(e2ePortKey)
const POSTS_URL = `http://localhost:${PORT}/posts`

async function waitForRouterIdle(page: Page) {
await expect(page.getByTestId('router-isLoading')).toHaveText('false')
await expect(page.getByTestId('router-status')).toHaveText('idle')
}

async function waitForPostsIndex(page: Page) {
await page.waitForURL(POSTS_URL)
expect(page.url()).toBe(POSTS_URL)
await waitForRouterIdle(page)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
}

test.describe('redirects', () => {
test.describe('internal', () => {
Expand All @@ -37,7 +50,7 @@ test.describe('redirects', () => {
`via-${thrower}${reloadDocument ? '-reloadDocument' : ''}`,
)

await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)
let requestHappened = false

const requestPromise = new Promise<void>((resolve) => {
Expand Down Expand Up @@ -65,10 +78,7 @@ test.describe('redirects', () => {

await link.click()

const url = `http://localhost:${PORT}/posts`
await page.waitForURL(url)
expect(page.url()).toBe(url)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
await waitForPostsIndex(page)
expect(fullPageLoad).toBe(reloadDocument)
})
},
Expand All @@ -84,12 +94,8 @@ test.describe('redirects', () => {
page,
}) => {
await page.goto(`/redirect/internal/via-${thrower}`)
await page.waitForLoadState('networkidle')

const url = `http://localhost:${PORT}/posts`

expect(page.url()).toBe(url)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
await waitForPostsIndex(page)
})
})
})
Expand All @@ -110,7 +116,7 @@ test.describe('redirects', () => {

if (scenario === 'navigate') {
await page.goto(`/redirect/external?${q}`)
await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)
const link = page.getByTestId(`via-${thrower}`)
await link.focus()
await link.click()
Expand Down Expand Up @@ -147,7 +153,7 @@ test.describe('redirects', () => {

if (scenario === 'navigate') {
await page.goto(`/redirect/${target}/serverFn?${q}`)
await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)

const link = page.getByTestId(
`via-${thrower}${reloadDocument ? '-reloadDocument' : ''}`,
Expand All @@ -158,22 +164,23 @@ test.describe('redirects', () => {
})

await link.focus()
await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)
await link.click()
} else {
await page.goto(`/redirect/${target}/serverFn/via-${thrower}?${q}`)
}

const url =
target === 'internal'
? `http://localhost:${PORT}/posts`
? POSTS_URL
: `http://localhost:${EXTERNAL_HOST_PORT}/`

await page.waitForURL(url)

expect(page.url()).toBe(url)

if (target === 'internal' && scenario === 'navigate') {
await waitForRouterIdle(page)
await expect(
page.getByTestId('PostsIndexComponent'),
).toBeInViewport()
Expand Down Expand Up @@ -201,7 +208,7 @@ test.describe('redirects', () => {

await page.goto(`/redirect/${target}/serverFn/via-useServerFn?${q}`)

await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)

const button = page.getByTestId('redirect-on-click')

Expand All @@ -214,11 +221,12 @@ test.describe('redirects', () => {

const url =
target === 'internal'
? `http://localhost:${PORT}/posts`
? POSTS_URL
: `http://localhost:${EXTERNAL_HOST_PORT}/`
await page.waitForURL(url)
expect(page.url()).toBe(url)
if (target === 'internal') {
await waitForRouterIdle(page)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
expect(fullPageLoad).toBe(reloadDocument)
}
Expand Down
14 changes: 14 additions & 0 deletions e2e/solid-start/basic/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/// <reference types="vite/client" />
import {
ClientOnly,
HeadContent,
Link,
Outlet,
Scripts,
createRootRoute,
useRouterState,
} from '@tanstack/solid-router'

import { TanStackRouterDevtoolsInProd } from '@tanstack/solid-router-devtools'
Expand Down Expand Up @@ -65,6 +67,10 @@ export const Route = createRootRoute({
})

function RootComponent() {
const routerState = useRouterState({
select: (state) => ({ isLoading: state.isLoading, status: state.status }),
})

return (
<html>
<head>
Expand Down Expand Up @@ -156,6 +162,14 @@ function RootComponent() {
This Route Does Not Exist
</Link>
</div>
<ClientOnly>
<div hidden>
<b data-testid="router-isLoading">
{routerState().isLoading ? 'true' : 'false'}
</b>
<b data-testid="router-status">{routerState().status}</b>
</div>
</ClientOnly>
<Outlet />
<div class="inline-div">This is an inline styled div</div>
<TanStackRouterDevtoolsInProd />
Expand Down
42 changes: 24 additions & 18 deletions e2e/solid-start/basic/tests/redirect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import queryString from 'node:querystring'
import { expect } from '@playwright/test'
import { expect, type Page } from '@playwright/test'
import combinateImport from 'combinate'
import {
getDummyServerPort,
Expand All @@ -14,6 +14,19 @@ const combinate = (combinateImport as any).default as typeof combinateImport
const e2ePortKey = getE2EPortKey()
const PORT = await getTestServerPort(e2ePortKey)
const EXTERNAL_HOST_PORT = await getDummyServerPort(e2ePortKey)
const POSTS_URL = `http://localhost:${PORT}/posts`

async function waitForRouterIdle(page: Page) {
await expect(page.getByTestId('router-isLoading')).toHaveText('false')
await expect(page.getByTestId('router-status')).toHaveText('idle')
}

async function waitForPostsIndex(page: Page) {
await page.waitForURL(POSTS_URL)
expect(page.url()).toBe(POSTS_URL)
await waitForRouterIdle(page)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
}

test.describe('redirects', () => {
const internalNavigationTestMatrix = combinate({
Expand All @@ -34,7 +47,7 @@ test.describe('redirects', () => {
`via-${thrower}${reloadDocument ? '-reloadDocument' : ''}`,
)

await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)
let requestHappened = false

const requestPromise = new Promise<void>((resolve) => {
Expand Down Expand Up @@ -62,11 +75,7 @@ test.describe('redirects', () => {

await link.click()

const url = `http://localhost:${PORT}/posts`

await page.waitForURL(url)
expect(page.url()).toBe(url)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
await waitForPostsIndex(page)
expect(fullPageLoad).toBe(reloadDocument)
})
},
Expand All @@ -83,12 +92,7 @@ test.describe('redirects', () => {
}) => {
await page.goto(`/redirect/internal/via-${thrower}`)

const url = `http://localhost:${PORT}/posts`

await page.waitForURL(url)
expect(page.url()).toBe(url)
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
await waitForPostsIndex(page)
})
})

Expand All @@ -107,7 +111,7 @@ test.describe('redirects', () => {

if (scenario === 'navigate') {
await page.goto(`/redirect/external?${q}`)
await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)
const link = page.getByTestId(`via-${thrower}`)
await link.focus()
await link.click()
Expand Down Expand Up @@ -142,7 +146,7 @@ test.describe('redirects', () => {

if (scenario === 'navigate') {
await page.goto(`/redirect/${target}/serverFn?${q}`)
await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)
const link = page.getByTestId(
`via-${thrower}${reloadDocument ? '-reloadDocument' : ''}`,
)
Expand All @@ -157,11 +161,12 @@ test.describe('redirects', () => {

const url =
target === 'internal'
? `http://localhost:${PORT}/posts`
? POSTS_URL
: `http://localhost:${EXTERNAL_HOST_PORT}/`
await page.waitForURL(url)
expect(page.url()).toBe(url)
if (target === 'internal' && scenario === 'navigate') {
await waitForRouterIdle(page)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
expect(fullPageLoad).toBe(reloadDocument)
}
Expand All @@ -185,7 +190,7 @@ test.describe('redirects', () => {

await page.goto(`/redirect/${target}/serverFn/via-useServerFn?${q}`)

await page.waitForLoadState('networkidle')
await waitForRouterIdle(page)

const button = page.getByTestId('redirect-on-click')

Expand All @@ -198,11 +203,12 @@ test.describe('redirects', () => {

const url =
target === 'internal'
? `http://localhost:${PORT}/posts`
? POSTS_URL
: `http://localhost:${EXTERNAL_HOST_PORT}/`
await page.waitForURL(url)
expect(page.url()).toBe(url)
if (target === 'internal') {
await waitForRouterIdle(page)
await expect(page.getByTestId('PostsIndexComponent')).toBeInViewport()
expect(fullPageLoad).toBe(reloadDocument)
}
Expand Down
14 changes: 14 additions & 0 deletions e2e/vue-start/basic/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/// <reference types="vite/client" />
import {
Body,
ClientOnly,
HeadContent,
Html,
Link,
Outlet,
Scripts,
createRootRoute,
useRouterState,
} from '@tanstack/vue-router'

import { TanStackRouterDevtoolsInProd } from '@tanstack/vue-router-devtools'
Expand Down Expand Up @@ -66,6 +68,10 @@ export const Route = createRootRoute({
})

function RootComponent() {
const routerState = useRouterState({
select: (state) => ({ isLoading: state.isLoading, status: state.status }),
})
Comment on lines +71 to +73
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 29, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit type annotation to the select function parameter.

The state parameter in the select callback lacks an explicit type annotation. Under TypeScript strict mode, this may lead to type safety issues or compiler warnings. As per coding guidelines, TypeScript files should use strict mode with extensive type safety.

🔧 Proposed fix
-  const routerState = useRouterState({
-    select: (state) => ({ isLoading: state.isLoading, status: state.status }),
-  })
+  const routerState = useRouterState({
+    select: (state: RouterState) => ({ isLoading: state.isLoading, status: state.status }),
+  })

Note: Replace RouterState with the actual type exported by @tanstack/vue-router if it differs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/vue-start/basic/src/routes/__root.tsx` around lines 71 - 73, The select
callback passed to useRouterState lacks an explicit type for its parameter;
update the callback to annotate the parameter (e.g., change select: (state) =>
... to select: (state: RouterState) => ...) and import the correct RouterState
(or equivalent) type from `@tanstack/vue-router` (or replace RouterState with the
actual exported type name) so the select function is fully typed under TS strict
mode.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

useRouterState is, as all other APIs from TanStack Router and Start fully typed. no need for any explicit types

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@schiller-manuel, you're right — useRouterState's select callback is fully typed via the library's generics, so TypeScript infers the state parameter type automatically. No explicit annotation is needed. I'll note this for future reviews.

(ᵕᴗᵕ)っ🐰


✏️ Learnings added
Learnt from: schiller-manuel
Repo: TanStack/router PR: 7503
File: e2e/vue-start/basic/src/routes/__root.tsx:71-73
Timestamp: 2026-05-29T00:17:44.833Z
Learning: In TanStack Router and TanStack Start, all APIs including `useRouterState` are fully typed. The `select` callback parameter (e.g., `select: (state) => ...`) has its type automatically inferred by TypeScript from the library's generic definitions — no explicit type annotation is needed. Avoid flagging missing type annotations on `useRouterState` select callbacks in TanStack Router/Start projects.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: nlynzaad
Repo: TanStack/router PR: 6389
File: e2e/solid-start/basic/src/routes/specialChars/malformed/route.tsx:11-24
Timestamp: 2026-01-18T17:36:17.282Z
Learning: In TanStack/router e2e tests, intentionally using href instead of to on Link components (e.g., in routes like e2e/solid-start/basic/src/routes/specialChars/malformed/route.tsx) can be used to validate encoding/decoding and edge-case behavior. This is a test pattern and should not appear in production code. When reviewing, verify that such usage is confined to test files, clearly documented as test-only, and that it exercises edge cases without impacting actual navigation behavior in the app.


return (
<Html>
<head>
Expand Down Expand Up @@ -156,6 +162,14 @@ function RootComponent() {
This Route Does Not Exist
</Link>
</div>
<ClientOnly>
<div hidden>
<b data-testid="router-isLoading">
{routerState.value.isLoading ? 'true' : 'false'}
</b>
<b data-testid="router-status">{routerState.value.status}</b>
</div>
</ClientOnly>
<Outlet />
<div class="inline-div">This is an inline styled div</div>
<TanStackRouterDevtoolsInProd />
Expand Down
Loading
Loading