diff --git a/src/routes/showcase/index.tsx b/src/routes/showcase/index.tsx index ebd432b65..063b52f59 100644 --- a/src/routes/showcase/index.tsx +++ b/src/routes/showcase/index.tsx @@ -1,27 +1,18 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, stripSearchParams } from '@tanstack/react-router' import * as v from 'valibot' import { seo } from '~/utils/seo' import { ShowcaseGallery } from '~/components/ShowcaseGallery' import { getApprovedShowcasesQueryOptions } from '~/queries/showcases' import { - libraryIdSchema, - showcaseUseCaseSchema, - pageNumberSchema, - pageSizeSchema, -} from '~/utils/schemas' - -const searchSchema = v.object({ - page: v.optional(pageNumberSchema, 1), - pageSize: v.optional(pageSizeSchema, 24), - libraryIds: v.optional(v.pipe(v.array(libraryIdSchema), v.maxLength(16))), - useCases: v.optional(v.pipe(v.array(showcaseUseCaseSchema), v.maxLength(12))), - hasSourceCode: v.optional(v.boolean()), - q: v.optional(v.pipe(v.string(), v.maxLength(120))), -}) + showcaseSearchSchema, + defaultShowcaseSearch, +} from '~/utils/showcase-search' export const PAGE_SIZE_OPTIONS = [24, 48, 96] as const -function hasNonCanonicalSearch(search: v.InferOutput) { +function hasNonCanonicalSearch( + search: v.InferOutput, +) { return Boolean( search.page > 1 || search.pageSize !== PAGE_SIZE_OPTIONS[0] || @@ -33,7 +24,8 @@ function hasNonCanonicalSearch(search: v.InferOutput) { } export const Route = createFileRoute('/showcase/')({ - validateSearch: searchSchema, + validateSearch: showcaseSearchSchema, + search: { middlewares: [stripSearchParams(defaultShowcaseSearch)] }, loaderDeps: ({ search }) => ({ page: search.page, pageSize: search.pageSize, diff --git a/src/utils/showcase-search.ts b/src/utils/showcase-search.ts new file mode 100644 index 000000000..d729a61fd --- /dev/null +++ b/src/utils/showcase-search.ts @@ -0,0 +1,18 @@ +import * as v from 'valibot' +import { + libraryIdSchema, + showcaseUseCaseSchema, + pageNumberSchema, + pageSizeSchema, +} from '~/utils/schemas' + +export const showcaseSearchSchema = v.object({ + page: v.optional(pageNumberSchema, 1), + pageSize: v.optional(pageSizeSchema, 24), + libraryIds: v.optional(v.pipe(v.array(libraryIdSchema), v.maxLength(16))), + useCases: v.optional(v.pipe(v.array(showcaseUseCaseSchema), v.maxLength(12))), + hasSourceCode: v.optional(v.boolean()), + q: v.optional(v.pipe(v.string(), v.maxLength(120))), +}) + +export const defaultShowcaseSearch = v.parse(showcaseSearchSchema, {}) diff --git a/tests/showcase-search.test.ts b/tests/showcase-search.test.ts new file mode 100644 index 000000000..faa7c8aac --- /dev/null +++ b/tests/showcase-search.test.ts @@ -0,0 +1,46 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import * as v from 'valibot' +import { + createRootRoute, + createRoute, + createRouter, + createMemoryHistory, + stripSearchParams, +} from '@tanstack/react-router' +import { + showcaseSearchSchema, + defaultShowcaseSearch, +} from '../src/utils/showcase-search' + +test('showcase defaults stay available to loaders without entering the URL', async () => { + const root = createRootRoute() + const showcase = createRoute({ + getParentRoute: () => root, + path: '/showcase', + validateSearch: showcaseSearchSchema, + search: { middlewares: [stripSearchParams(defaultShowcaseSearch)] }, + }) + const router = createRouter({ + routeTree: root.addChildren([showcase]), + history: createMemoryHistory({ initialEntries: ['/showcase'] }), + }) + await router.load() + assert.deepEqual(v.parse(showcaseSearchSchema, {}), { page: 1, pageSize: 24 }) + assert.equal( + router.buildLocation({ to: '/showcase', search: { page: 1, pageSize: 24 } }) + .href, + '/showcase', + ) + const filtered = { + page: 2, + pageSize: 48, + libraryIds: ['query'], + hasSourceCode: true, + q: 'dashboard', + } + assert.deepEqual( + router.buildLocation({ to: '/showcase', search: filtered }).search, + filtered, + ) +})