From a5f350bd4ea091a399e7ee3c6d30003389409962 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Wed, 19 Aug 2026 02:46:10 +0200 Subject: [PATCH 1/3] perf(router-core): skip disabled search validation --- packages/router-core/src/router.ts | 24 ++++++------- .../router-core/tests/build-location.test.ts | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index b473339c70..2d72760985 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -2777,24 +2777,22 @@ function applySearchMiddleware( } const routeValidateSearch = routeOptions.validateSearch - if (routeValidateSearch) { + if (includeValidateSearch && routeValidateSearch) { const validate: SearchMiddleware = ({ search, next, meta }) => { const result = next(search) - if (includeValidateSearch) { - try { - const validated = validateSearch(routeValidateSearch, result) as any - - if (meta && validated) { - for (const key in validated) { - if (!(key in result)) { - ;(meta.defaulted ||= new Map()).set(key, validated[key]) - } + try { + const validated = validateSearch(routeValidateSearch, result) as any + + if (meta && validated) { + for (const key in validated) { + if (!(key in result)) { + ;(meta.defaulted ||= new Map()).set(key, validated[key]) } } - return { ...result, ...validated } - } catch { - // ignore errors here because they are already handled in matchRoutes } + return { ...result, ...validated } + } catch { + // ignore errors here because they are already handled in matchRoutes } return result } diff --git a/packages/router-core/tests/build-location.test.ts b/packages/router-core/tests/build-location.test.ts index 14ab661ff6..4f075fe176 100644 --- a/packages/router-core/tests/build-location.test.ts +++ b/packages/router-core/tests/build-location.test.ts @@ -233,6 +233,41 @@ describe('buildLocation - params function receives parsed params', () => { }) describe('buildLocation - search params', () => { + test('only applies route validation when requested', async () => { + const validateSearch = vi.fn((search: Record) => ({ + ...search, + validated: true, + })) + const rootRoute = new BaseRootRoute({ validateSearch }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + await router.load() + + router.buildLocation({ to: '/' }) + validateSearch.mockClear() + + const unvalidated = router.buildLocation({ + to: '/', + search: { explicit: true }, + }) + expect(validateSearch).not.toHaveBeenCalled() + expect(unvalidated.search).toEqual({ explicit: true }) + + const validated = router.buildLocation({ + to: '/', + search: { explicit: true }, + _includeValidateSearch: true, + } as any) + expect(validateSearch).toHaveBeenCalled() + expect(validated.search).toEqual({ explicit: true, validated: true }) + }) + test('retainSearchParams should preserve current search over defaults during navigation', async () => { const rootRoute = new BaseRootRoute({ validateSearch: (search: Record) => ({ From 0a872ead1f6b4f95731d19c5e770ea757fc4ebf7 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Wed, 19 Aug 2026 03:00:33 +0200 Subject: [PATCH 2/3] test(router-core): cover validation middleware ordering --- .../router-core/tests/build-location.test.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/router-core/tests/build-location.test.ts b/packages/router-core/tests/build-location.test.ts index 4f075fe176..89f79a8a8f 100644 --- a/packages/router-core/tests/build-location.test.ts +++ b/packages/router-core/tests/build-location.test.ts @@ -238,7 +238,15 @@ describe('buildLocation - search params', () => { ...search, validated: true, })) - const rootRoute = new BaseRootRoute({ validateSearch }) + const middleware = vi.fn( + ({ search, next }: { search: any; next: (search: any) => any }) => { + return { ...next(search), middleware: true } + }, + ) + const rootRoute = new BaseRootRoute({ + validateSearch, + search: { middlewares: [middleware] }, + }) const indexRoute = new BaseRoute({ getParentRoute: () => rootRoute, path: '/', @@ -257,7 +265,8 @@ describe('buildLocation - search params', () => { search: { explicit: true }, }) expect(validateSearch).not.toHaveBeenCalled() - expect(unvalidated.search).toEqual({ explicit: true }) + expect(middleware).toHaveBeenCalled() + expect(unvalidated.search).toEqual({ explicit: true, middleware: true }) const validated = router.buildLocation({ to: '/', @@ -265,7 +274,11 @@ describe('buildLocation - search params', () => { _includeValidateSearch: true, } as any) expect(validateSearch).toHaveBeenCalled() - expect(validated.search).toEqual({ explicit: true, validated: true }) + expect(validated.search).toEqual({ + explicit: true, + middleware: true, + validated: true, + }) }) test('retainSearchParams should preserve current search over defaults during navigation', async () => { From 739ad7b3a2c07c39d2058a4b52166faec1d61fa5 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Wed, 19 Aug 2026 09:15:27 +0200 Subject: [PATCH 3/3] test(router-core): assert search validation execution --- .../router-core/tests/build-location.test.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/router-core/tests/build-location.test.ts b/packages/router-core/tests/build-location.test.ts index 89f79a8a8f..1164636429 100644 --- a/packages/router-core/tests/build-location.test.ts +++ b/packages/router-core/tests/build-location.test.ts @@ -6,6 +6,7 @@ import { retainSearchParams, stripSearchParams, } from '../src' +import type { SearchMiddleware } from '../src' import { _getUserHistoryState } from '../src/router' import { createTestRouter } from './routerTestUtils' @@ -234,13 +235,17 @@ describe('buildLocation - params function receives parsed params', () => { describe('buildLocation - search params', () => { test('only applies route validation when requested', async () => { - const validateSearch = vi.fn((search: Record) => ({ - ...search, - validated: true, - })) - const middleware = vi.fn( - ({ search, next }: { search: any; next: (search: any) => any }) => { - return { ...next(search), middleware: true } + const events: Array = [] + const validateSearch = vi.fn((search: Record) => { + events.push('validate') + return { ...search, validated: true } + }) + const middleware = vi.fn>( + ({ search, next }) => { + events.push('middleware:before') + const result = next(search) + events.push('middleware:after') + return { ...result, middleware: true } }, ) const rootRoute = new BaseRootRoute({ @@ -259,21 +264,30 @@ describe('buildLocation - search params', () => { router.buildLocation({ to: '/' }) validateSearch.mockClear() + middleware.mockClear() + events.length = 0 const unvalidated = router.buildLocation({ to: '/', search: { explicit: true }, }) expect(validateSearch).not.toHaveBeenCalled() - expect(middleware).toHaveBeenCalled() + expect(middleware).toHaveBeenCalledOnce() + expect(events).toEqual(['middleware:before', 'middleware:after']) expect(unvalidated.search).toEqual({ explicit: true, middleware: true }) + events.length = 0 const validated = router.buildLocation({ to: '/', search: { explicit: true }, _includeValidateSearch: true, - } as any) - expect(validateSearch).toHaveBeenCalled() + }) + expect(validateSearch).toHaveBeenCalledOnce() + expect(events).toEqual([ + 'middleware:before', + 'validate', + 'middleware:after', + ]) expect(validated.search).toEqual({ explicit: true, middleware: true,