mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
test(studio): add real-router integration guard for URL-shape regressions
Drives an actual TanStack router configured with the app's custom search codec through the navigation shapes the compat shims and the custom nuqs adapter produce, asserting Next-shaped hrefs: no JSON coercion or quoting, repeated keys preserved, no trailing slash injected on ?-only navigations, single '#' on hash links. Claude-Session: https://claude.ai/code/session_01MLH8WVg8zBJnmZTYTV3ooo
This commit is contained in:
1 parent
c59bcd851e
commit
bcdf1b745e
1 file changed
+75
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
createMemoryHistory,
|
||||
createRootRoute,
|
||||
createRoute,
|
||||
createRouter,
|
||||
} from '@tanstack/react-router'
|
||||
import { renderQueryString } from 'nuqs/adapters/custom'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { resolveSearchOrHashOnlyTarget } from '../compat/next/router'
|
||||
import { buildSearchUpdateTarget } from './nuqs-tanstack-adapter'
|
||||
import { parseSearch, stringifySearch } from './router-search-params'
|
||||
|
||||
// Integration guard for the family of URL-shape regressions from the
|
||||
// TanStack migration: drives a real router (with the app's custom search
|
||||
// codec) through the navigation shapes the compat shims and the nuqs
|
||||
// adapter produce, and asserts the built hrefs are Next-shaped — no JSON
|
||||
// coercion/quoting, no dropped repeated keys, no injected trailing slash,
|
||||
// no doubled `#`.
|
||||
describe('URL shape end-to-end against a real router', () => {
|
||||
it('produces Next-shaped URLs for every fixed producer', async () => {
|
||||
const rootRoute = createRootRoute({})
|
||||
const routes = ['/project/$ref/advisors/security', '/project/$ref/auth/providers', '/org'].map(
|
||||
(p) => createRoute({ getParentRoute: () => rootRoute, path: p })
|
||||
)
|
||||
rootRoute.addChildren(routes)
|
||||
const router = createRouter({
|
||||
routeTree: rootRoute,
|
||||
history: createMemoryHistory({
|
||||
initialEntries: [
|
||||
'/project/abc/advisors/security?preset=security&filter=a:eq:1&filter=b:eq:2',
|
||||
],
|
||||
}),
|
||||
parseSearch,
|
||||
stringifySearch,
|
||||
})
|
||||
await router.load()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const build = (opts: any) => router.buildLocation(opts).href
|
||||
|
||||
// Custom codec: strings and repeated-key arrays, no JSON coercion.
|
||||
expect(router.state.location.search).toEqual({
|
||||
preset: 'security',
|
||||
filter: ['a:eq:1', 'b:eq:2'],
|
||||
})
|
||||
|
||||
// Compat router push({ query }) resolves to a ?-only target; must stay
|
||||
// on the exact current pathname (no `/advisors/security/?...`).
|
||||
const target = resolveSearchOrHashOnlyTarget('?preset=WARN', router.state.location.pathname)
|
||||
expect(build({ to: target })).toBe('/project/abc/advisors/security?preset=WARN')
|
||||
|
||||
// nuqs write over the whole page query — same guarantee.
|
||||
const params = new URLSearchParams(router.state.location.searchStr)
|
||||
params.set('preset', 'perf')
|
||||
expect(
|
||||
build({
|
||||
to: buildSearchUpdateTarget(router.state.location.pathname, renderQueryString(params)),
|
||||
hash: (h: string | undefined) => h ?? '',
|
||||
})
|
||||
).toBe('/project/abc/advisors/security?preset=perf&filter=a:eq:1&filter=b:eq:2')
|
||||
|
||||
// nuqs clear-all keeps the exact pathname.
|
||||
expect(
|
||||
build({
|
||||
to: buildSearchUpdateTarget(
|
||||
router.state.location.pathname,
|
||||
renderQueryString(new URLSearchParams())
|
||||
),
|
||||
})
|
||||
).toBe('/project/abc/advisors/security')
|
||||
|
||||
// A bare fragment (the Link shim's hash shape) produces a single '#'.
|
||||
expect(build({ to: '/org', hash: 'invoices' })).toBe('/org#invoices')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user