Skip to content
Merged
45 changes: 45 additions & 0 deletions e2e/react-start/basic/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Route as StreamRouteImport } from './routes/stream'
import { Route as ScriptsRouteImport } from './routes/scripts'
import { Route as PostsRouteImport } from './routes/posts'
import { Route as LinksRouteImport } from './routes/links'
import { Route as InlineScriptsRouteImport } from './routes/inline-scripts'
import { Route as DeferredRouteImport } from './routes/deferred'
import { Route as LayoutRouteImport } from './routes/_layout'
import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route'
Expand Down Expand Up @@ -90,6 +91,11 @@ const LinksRoute = LinksRouteImport.update({
path: '/links',
getParentRoute: () => rootRouteImport,
} as any)
const InlineScriptsRoute = InlineScriptsRouteImport.update({
id: '/inline-scripts',
path: '/inline-scripts',
getParentRoute: () => rootRouteImport,
} as any)
const DeferredRoute = DeferredRouteImport.update({
id: '/deferred',
path: '/deferred',
Expand Down Expand Up @@ -264,6 +270,7 @@ export interface FileRoutesByFullPath {
'/not-found': typeof NotFoundRouteRouteWithChildren
'/search-params': typeof SearchParamsRouteRouteWithChildren
'/deferred': typeof DeferredRoute
'/inline-scripts': typeof InlineScriptsRoute
'/links': typeof LinksRoute
'/posts': typeof PostsRouteWithChildren
'/scripts': typeof ScriptsRoute
Expand Down Expand Up @@ -298,6 +305,7 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/deferred': typeof DeferredRoute
'/inline-scripts': typeof InlineScriptsRoute
'/links': typeof LinksRoute
'/scripts': typeof ScriptsRoute
'/stream': typeof StreamRoute
Expand Down Expand Up @@ -332,6 +340,7 @@ export interface FileRoutesById {
'/search-params': typeof SearchParamsRouteRouteWithChildren
'/_layout': typeof LayoutRouteWithChildren
'/deferred': typeof DeferredRoute
'/inline-scripts': typeof InlineScriptsRoute
'/links': typeof LinksRoute
'/posts': typeof PostsRouteWithChildren
'/scripts': typeof ScriptsRoute
Expand Down Expand Up @@ -372,6 +381,7 @@ export interface FileRouteTypes {
| '/not-found'
| '/search-params'
| '/deferred'
| '/inline-scripts'
| '/links'
| '/posts'
| '/scripts'
Expand Down Expand Up @@ -406,6 +416,7 @@ export interface FileRouteTypes {
to:
| '/'
| '/deferred'
| '/inline-scripts'
| '/links'
| '/scripts'
| '/stream'
Expand Down Expand Up @@ -439,6 +450,7 @@ export interface FileRouteTypes {
| '/search-params'
| '/_layout'
| '/deferred'
| '/inline-scripts'
| '/links'
| '/posts'
| '/scripts'
Expand Down Expand Up @@ -479,6 +491,7 @@ export interface RootRouteChildren {
SearchParamsRouteRoute: typeof SearchParamsRouteRouteWithChildren
LayoutRoute: typeof LayoutRouteWithChildren
DeferredRoute: typeof DeferredRoute
InlineScriptsRoute: typeof InlineScriptsRoute
LinksRoute: typeof LinksRoute
PostsRoute: typeof PostsRouteWithChildren
ScriptsRoute: typeof ScriptsRoute
Expand Down Expand Up @@ -552,6 +565,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DeferredRouteImport
parentRoute: typeof rootRouteImport
}
'/inline-scripts': {
id: '/inline-scripts'
path: '/inline-scripts'
fullPath: '/inline-scripts'
preLoaderRoute: typeof InlineScriptsRouteImport
parentRoute: typeof rootRouteImport
}
'/links': {
id: '/links'
path: '/links'
Expand Down Expand Up @@ -829,6 +849,13 @@ declare module '@tanstack/react-start/server' {
preLoaderRoute: unknown
parentRoute: typeof rootServerRouteImport
}
'/inline-scripts': {
id: '/inline-scripts'
path: '/inline-scripts'
fullPath: '/inline-scripts'
preLoaderRoute: unknown
parentRoute: typeof rootServerRouteImport
}
'/links': {
id: '/links'
path: '/links'
Expand Down Expand Up @@ -1148,6 +1175,23 @@ declare module './routes/deferred' {
unknown
>
}
declare module './routes/inline-scripts' {
const createFileRoute: CreateFileRoute<
'/inline-scripts',
FileRoutesByPath['/inline-scripts']['parentRoute'],
FileRoutesByPath['/inline-scripts']['id'],
FileRoutesByPath['/inline-scripts']['path'],
FileRoutesByPath['/inline-scripts']['fullPath']
>

const createServerFileRoute: CreateServerFileRoute<
ServerFileRoutesByPath['/inline-scripts']['parentRoute'],
ServerFileRoutesByPath['/inline-scripts']['id'],
ServerFileRoutesByPath['/inline-scripts']['path'],
ServerFileRoutesByPath['/inline-scripts']['fullPath'],
unknown
>
}
declare module './routes/links' {
const createFileRoute: CreateFileRoute<
'/links',
Expand Down Expand Up @@ -1858,6 +1902,7 @@ const rootRouteChildren: RootRouteChildren = {
SearchParamsRouteRoute: SearchParamsRouteRouteWithChildren,
LayoutRoute: LayoutRouteWithChildren,
DeferredRoute: DeferredRoute,
InlineScriptsRoute: InlineScriptsRoute,
LinksRoute: LinksRoute,
PostsRoute: PostsRouteWithChildren,
ScriptsRoute: ScriptsRoute,
Expand Down
8 changes: 8 additions & 0 deletions e2e/react-start/basic/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ function RootDocument({ children }: { children: React.ReactNode }) {
>
Scripts
</Link>{' '}
<Link
to="/inline-scripts"
activeProps={{
className: 'font-bold',
}}
>
Inline Scripts
</Link>{' '}
<Link
to="/deferred"
activeProps={{
Expand Down
28 changes: 28 additions & 0 deletions e2e/react-start/basic/src/routes/inline-scripts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const Route = createFileRoute({
head: () => ({
scripts: [
{
children:
'window.INLINE_SCRIPT_1 = true; console.log("Inline script 1 executed");',
},
{
children:
'window.INLINE_SCRIPT_2 = "test"; console.log("Inline script 2 executed");',
type: 'text/javascript',
},
],
}),
component: InlineScriptsComponent,
})
Comment on lines +1 to +16
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

Fix createFileRoute usage: missing path argument.

React Start routes typically call createFileRoute('/inline-scripts'). The current call with an object will fail type-checking or at runtime.

-export const Route = createFileRoute({
+export const Route = createFileRoute('/inline-scripts')({
📝 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
export const Route = createFileRoute({
head: () => ({
scripts: [
{
children:
'window.INLINE_SCRIPT_1 = true; console.log("Inline script 1 executed");',
},
{
children:
'window.INLINE_SCRIPT_2 = "test"; console.log("Inline script 2 executed");',
type: 'text/javascript',
},
],
}),
component: InlineScriptsComponent,
})
export const Route = createFileRoute('/inline-scripts')({
head: () => ({
scripts: [
{
children:
'window.INLINE_SCRIPT_1 = true; console.log("Inline script 1 executed");',
},
{
children:
'window.INLINE_SCRIPT_2 = "test"; console.log("Inline script 2 executed");',
type: 'text/javascript',
},
],
}),
component: InlineScriptsComponent,
})
🤖 Prompt for AI Agents
In e2e/react-start/basic/src/routes/inline-scripts.tsx around lines 1 to 16, the
createFileRoute call is missing the required path argument; change the
invocation to pass the route path as the first parameter (e.g.
'/inline-scripts') and pass the existing config object as the second parameter
so the function signature matches createFileRoute(path, config) and
TypeScript/runtime checks succeed.


function InlineScriptsComponent() {
return (
<div className="p-2">
<h3 data-testid="inline-scripts-test-heading">Inline Scripts Test</h3>
<p>
This route tests inline script duplication prevention. Two inline
scripts should be loaded.
</p>
</div>
)
}
2 changes: 1 addition & 1 deletion e2e/react-start/basic/tests/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test('Navigating nested layouts', async ({ page }) => {

test('client side navigating to a route with scripts', async ({ page }) => {
await page.goto('/')
await page.getByRole('link', { name: 'Scripts' }).click()
await page.getByRole('link', { name: 'Scripts', exact: true }).click()
await expect(page.getByTestId('scripts-test-heading')).toBeInViewport()
expect(await page.evaluate('window.SCRIPT_1')).toBe(true)
expect(await page.evaluate('window.SCRIPT_2')).toBe(undefined)
Expand Down
143 changes: 143 additions & 0 deletions e2e/react-start/basic/tests/script-duplication.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { expect, test } from '@playwright/test'

test.describe('Script Duplication Prevention', () => {
test('should not create duplicate scripts on SSR route', async ({ page }) => {
await page.goto('/scripts')

await expect(page.getByTestId('scripts-test-heading')).toBeInViewport()

const scriptCount = await page.evaluate(() => {
return document.querySelectorAll('script[src="script.js"]').length
})

expect(scriptCount).toBe(1)

expect(await page.evaluate('window.SCRIPT_1')).toBe(true)
})

test('should not create duplicate scripts during client-side navigation', async ({
page,
}) => {
await page.goto('/')

await page.getByRole('link', { name: 'Scripts', exact: true }).click()
await expect(page.getByTestId('scripts-test-heading')).toBeInViewport()

const firstNavCount = await page.evaluate(() => {
return document.querySelectorAll('script[src="script.js"]').length
})
expect(firstNavCount).toBe(1)

await page.getByRole('link', { name: 'Home' }).click()
await expect(page.getByRole('link', { name: 'Posts' })).toBeVisible()

await page.getByRole('link', { name: 'Scripts', exact: true }).click()
await expect(page.getByTestId('scripts-test-heading')).toBeInViewport()

const secondNavCount = await page.evaluate(() => {
return document.querySelectorAll('script[src="script.js"]').length
})
expect(secondNavCount).toBe(1)

expect(await page.evaluate('window.SCRIPT_1')).toBe(true)
})

test('should not create duplicate scripts with multiple navigation cycles', async ({
page,
}) => {
await page.goto('/')

for (let i = 0; i < 3; i++) {
await page.getByRole('link', { name: 'Scripts', exact: true }).click()
await expect(page.getByTestId('scripts-test-heading')).toBeInViewport()

await page.getByRole('link', { name: 'Home' }).click()
await expect(page.getByRole('link', { name: 'Posts' })).toBeVisible()
}

await page.getByRole('link', { name: 'Scripts', exact: true }).click()
await expect(page.getByTestId('scripts-test-heading')).toBeInViewport()

const finalCount = await page.evaluate(() => {
return document.querySelectorAll('script[src="script.js"]').length
})
expect(finalCount).toBe(1)

expect(await page.evaluate('window.SCRIPT_1')).toBe(true)
})

test('should not create duplicate inline scripts', async ({ page }) => {
await page.goto('/inline-scripts')

await expect(
page.getByTestId('inline-scripts-test-heading'),
).toBeInViewport()

const script1Count = await page.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script:not([src])'))
return scripts.filter(
(script) =>
script.textContent &&
script.textContent.includes('window.INLINE_SCRIPT_1 = true'),
).length
})

const script2Count = await page.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script:not([src])'))
return scripts.filter(
(script) =>
script.textContent &&
script.textContent.includes('window.INLINE_SCRIPT_2 = "test"'),
).length
})

expect(script1Count).toBe(1)
expect(script2Count).toBe(1)

expect(await page.evaluate('window.INLINE_SCRIPT_1')).toBe(true)
expect(await page.evaluate('window.INLINE_SCRIPT_2')).toBe('test')
})

test('should not create duplicate inline scripts during client-side navigation', async ({
page,
}) => {
await page.goto('/')

await page.getByRole('link', { name: 'Inline Scripts' }).click()
await expect(
page.getByTestId('inline-scripts-test-heading'),
).toBeInViewport()

const firstNavScript1Count = await page.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script:not([src])'))
return scripts.filter(
(script) =>
script.textContent &&
script.textContent.includes('window.INLINE_SCRIPT_1 = true'),
).length
})
expect(firstNavScript1Count).toBe(1)

await page.getByRole('link', { name: 'Home' }).click()
await expect(page.getByRole('link', { name: 'Posts' })).toBeVisible()

await page.getByRole('link', { name: 'Inline Scripts' }).click()
await expect(
page.getByTestId('inline-scripts-test-heading'),
).toBeInViewport()

const secondNavScript1Count = await page.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script:not([src])'))
return scripts.filter(
(script) =>
script.textContent &&
script.textContent.includes('window.INLINE_SCRIPT_1 = true'),
).length
})
expect(secondNavScript1Count).toBe(1)

// Verify the scripts are still working
expect(await page.evaluate('window.INLINE_SCRIPT_1')).toBe(true)
expect(await page.evaluate('window.INLINE_SCRIPT_2')).toBe('test')
})
})
Loading
Loading