import { describe, expect, it } from 'vitest' import { nextSort, parseArticleQuery, sameArticleQuery, toRouteQuery } from '@/lib/articleListQuery' describe('parseArticleQuery', () => { it('falls back to page 1, size 25, no search and the All tab', () => { expect(parseArticleQuery({})).toEqual({ page: 1, size: 25, q: '', status: '', sort: '', site: '', seo: '', lang: '', tab: '', author: 0, }) }) it('reads valid values', () => { expect( parseArticleQuery({ page: '7', size: '50', q: 'plaster', status: 'draft', sort: 'title:asc', }), ).toEqual({ page: 7, size: 50, q: 'plaster', status: 'draft', sort: 'title:asc', site: '', seo: '', lang: '', tab: '', author: 0, }) }) it.each(['abc', '0', '-2', '2.5', '', '1e2', '99999999999999999999999'])( 'treats page "%s" as 1', (page) => { expect(parseArticleQuery({ page }).page).toBe(1) }, ) it.each(['10', 'abc', '0', '26', ''])('treats size "%s" as 25', (size) => { expect(parseArticleQuery({ size }).size).toBe(25) }) it('ignores an unknown status', () => { expect(parseArticleQuery({ status: 'archived' }).status).toBe('') }) it('reads the first value of a repeated parameter and caps the search at 200 characters', () => { expect(parseArticleQuery({ page: ['3', '4'] }).page).toBe(3) expect(parseArticleQuery({ q: 'a'.repeat(250) }).q).toHaveLength(200) }) }) describe('localized list parameters', () => { it('reads lang, tab and author', () => { expect(parseArticleQuery({ lang: 'fr', tab: 'missing', author: '12' })).toMatchObject({ lang: 'fr', tab: 'missing', author: 12, }) }) it.each(['english', 'x', '', 'fr_FR', '12'])('falls back from the lang "%s"', (lang) => { expect(parseArticleQuery({ lang }).lang).toBe('') }) it.each(['review', 'all', 'published', ''])('falls back from the tab "%s"', (tab) => { expect(parseArticleQuery({ tab }).tab).toBe('') }) it.each(['abc', '0', '-1', '', '1.5'])('falls back from the author "%s"', (author) => { expect(parseArticleQuery({ author }).author).toBe(0) }) it('drops the status when the tab is Not in language', () => { expect(parseArticleQuery({ tab: 'missing', status: 'draft' }).status).toBe('') }) it('writes lang, tab and author and omits their defaults', () => { expect( toRouteQuery({ ...parseArticleQuery({}), lang: 'fr', tab: 'missing', author: 4, }), ).toEqual({ lang: 'fr', tab: 'missing', author: '4' }) expect(toRouteQuery(parseArticleQuery({}))).toEqual({}) }) it('tells queries apart by lang, tab and author', () => { const base = parseArticleQuery({}) expect(sameArticleQuery(base, { ...base, lang: 'fr' })).toBe(false) expect(sameArticleQuery(base, { ...base, tab: 'missing' })).toBe(false) expect(sameArticleQuery(base, { ...base, author: 3 })).toBe(false) }) }) describe('sort parameter', () => { it.each(['title:asc', 'status:desc', 'created_at:asc', 'created_at:desc'])( 'keeps %s', (sort) => { expect(parseArticleQuery({ sort }).sort).toBe(sort) }, ) it.each(['body:asc', 'title', 'title:up', 'TITLE:ASC', ''])('drops invalid "%s"', (sort) => { expect(parseArticleQuery({ sort }).sort).toBe('') }) it('treats the default as no sort', () => { expect(parseArticleQuery({ sort: 'updated_at:desc' }).sort).toBe('') }) it('omits the default from the route and treats it as equal to no sort', () => { const base = { page: 1, size: 25, q: '', status: '' as const, site: '', seo: '' as const } expect(toRouteQuery({ ...base, sort: 'updated_at:desc' })).toEqual({}) expect(toRouteQuery({ ...base, sort: 'title:asc' })).toEqual({ sort: 'title:asc', }) expect(sameArticleQuery({ ...base, sort: '' }, { ...base, sort: 'updated_at:desc' })).toBe( true, ) expect(sameArticleQuery({ ...base, sort: 'title:asc' }, { ...base, sort: '' })).toBe(false) }) it('toggles: first click ascending, second descending, other column restarts', () => { expect(nextSort('', 'title')).toBe('title:asc') expect(nextSort('title:asc', 'title')).toBe('title:desc') expect(nextSort('title:desc', 'title')).toBe('title:asc') expect(nextSort('title:asc', 'status')).toBe('status:asc') expect(nextSort('', 'updated_at')).toBe('updated_at:asc') expect(nextSort('updated_at:asc', 'updated_at')).toBe('updated_at:desc') }) }) describe('toRouteQuery', () => { it('omits every default', () => { expect( toRouteQuery({ page: 1, size: 25, q: '', status: '', sort: '', site: '', seo: '', }), ).toEqual({}) }) it('keeps what differs from the defaults', () => { expect( toRouteQuery({ page: 2, size: 5, q: 'plaster', status: 'online', sort: 'title:desc', site: '', seo: '', }), ).toEqual({ page: '2', size: '5', q: 'plaster', status: 'online', sort: 'title:desc', }) }) }) describe('sameArticleQuery', () => { it('compares every field', () => { const base = { page: 1, size: 25, q: '', status: '' as const, sort: '', site: '', seo: '', } expect(sameArticleQuery(base, { ...base })).toBe(true) expect(sameArticleQuery(base, { ...base, page: 2 })).toBe(false) expect(sameArticleQuery(base, { ...base, size: 5 })).toBe(false) expect(sameArticleQuery(base, { ...base, q: 'a' })).toBe(false) expect(sameArticleQuery(base, { ...base, status: 'draft' })).toBe(false) }) }) describe('site parameter', () => { it('reads the site key and writes it back to the route', () => { const parsed = parseArticleQuery({ site: 'journal' }) expect(parsed.site).toBe('journal') expect(toRouteQuery(parsed)).toEqual({ site: 'journal' }) }) it('omits an empty site and tells two queries apart by their site', () => { const base = { page: 1, size: 25, q: '', status: '' as const, sort: '', seo: '' as const } expect(toRouteQuery({ ...base, site: '' })).toEqual({}) expect(sameArticleQuery({ ...base, site: 'journal' }, { ...base, site: '' })).toBe(false) expect(sameArticleQuery({ ...base, site: 'journal' }, { ...base, site: 'journal' })).toBe( true, ) }) }) describe('seo parameter', () => { it('reads only the value suggestions and writes it after the site', () => { const parsed = parseArticleQuery({ site: 'journal', seo: 'suggestions' }) expect(parsed.seo).toBe('suggestions') expect(Object.keys(toRouteQuery(parsed))).toEqual(['site', 'seo']) }) it.each(['maybe', '', 'READY', 'ready', 'hidden', 'suggestions,x'])( 'drops the unknown value "%s"', (seo) => { expect(parseArticleQuery({ seo }).seo).toBe('') }, ) it('tells two queries apart by their seo filter', () => { const base = parseArticleQuery({}) expect(sameArticleQuery(base, { ...base, seo: 'suggestions' })).toBe(false) expect(sameArticleQuery(base, { ...base })).toBe(true) }) })