import { describe, expect, it } from 'vitest'; import { DEFAULT_CATALOGUE_QUERY_STATE, buildCatalogueUrl, createCatalogueSearchParams, mergeCatalogueQueryState, parseCatalogueSearchParams, } from '../src/utils/catalogueUrlState'; describe('catalogue URL state', () => { it('uses a compact empty URL for the default catalogue state', () => { const parsed = parseCatalogueSearchParams(''); expect(parsed.state).toEqual(DEFAULT_CATALOGUE_QUERY_STATE); expect(parsed.canonicalSearch).toBe(''); expect(parsed.wasCanonical).toBe(true); expect(parsed.warnings).toEqual([]); }); it('normalises aliases into the canonical shareable query contract', () => { const parsed = parseCatalogueSearchParams( '?search=%20gear%20%20pump%20&cat=Engines&diff=Intermediate&favorites=true&sort=name&view=list&page=3', { validCategories: ['engines', 'pumps'], }, ); expect(parsed.state).toEqual({ query: 'gear pump', category: 'engines', difficulty: 'intermediate', sort: 'name-asc', favouritesOnly: true, view: 'list', page: 3, }); expect(parsed.canonicalSearch).toBe( '?q=gear+pump&category=engines&difficulty=intermediate&sort=name-asc&fav=1&view=list&page=3', ); expect(parsed.wasCanonical).toBe(false); expect(parsed.warnings).toEqual([]); }); it('detects already-canonical catalogue URLs', () => { const parsed = parseCatalogueSearchParams( '?q=gear+pump&category=engines&sort=name-asc', { validCategories: ['engines'], }, ); expect(parsed.wasCanonical).toBe(true); expect(parsed.canonicalSearch).toBe( '?q=gear+pump&category=engines&sort=name-asc', ); }); it('defensively handles malformed filters without throwing', () => { const parsed = parseCatalogueSearchParams( '?q=0123456789abcdefghijklmnopqrstuvwxyz&category=invalid*&difficulty=impossible&sort=random&fav=maybe&view=wall&page=500', { validCategories: ['engines'], maxQueryLength: 10, maxPage: 20, }, ); expect(parsed.state).toEqual({ query: '0123456789', category: 'all', difficulty: 'all', sort: 'featured', favouritesOnly: false, view: 'grid', page: 20, }); expect(parsed.canonicalSearch).toBe('?q=0123456789&page=20'); expect(parsed.wasCanonical).toBe(false); expect(parsed.warnings.map((warning) => warning.code)).toEqual( expect.arrayContaining([ 'query-truncated', 'invalid-category', 'invalid-difficulty', 'invalid-sort', 'invalid-favourites', 'invalid-view', 'invalid-page', ]), ); }); it('can preserve unrelated query parameters while replacing catalogue aliases', () => { const params = createCatalogueSearchParams( { query: 'fan clutch', category: 'pumps', favouritesOnly: true, }, { validCategories: ['pumps'], }, 'utm=backer&search=old&fav=false&foo=bar', ); expect(params.toString()).toBe( 'utm=backer&foo=bar&q=fan+clutch&category=pumps&fav=1', ); }); it('resets pagination when filter-like keys change', () => { const current = { query: 'gear', category: 'engines', page: 4, }; const next = mergeCatalogueQueryState( current, { difficulty: 'advanced', }, { validCategories: ['engines'], }, { resetPage: true, }, ); expect(next.page).toBe(1); expect(next.difficulty).toBe('advanced'); const pageOnly = mergeCatalogueQueryState( current, { page: 5, }, { validCategories: ['engines'], }, { resetPage: true, }, ); expect(pageOnly.page).toBe(5); }); it('builds canonical catalogue hrefs without leaking legacy query strings', () => { expect( buildCatalogueUrl('/catalogue?legacy=true#results', { query: 'cam follower', view: 'list', }), ).toBe('/catalogue?q=cam+follower&view=list#results'); }); });