test: usePagination hrefs, head links and page count; drift pattern forms

- the query and path forms from the resolve answer, the site answer and
  the layer option, page 1 unnumbered
- prev and next links at the ends, %pages% from the page count
- routeServes reads {param} and :param alike and ignores the query part
This commit is contained in:
Jakub Zych
2026-11-04 19:47:18 +01:00
parent 7af91fc5e4
commit 75cc100336
2 changed files with 115 additions and 0 deletions

View File

@@ -115,3 +115,12 @@ test('driftWarnings: segments and param, a switched-off layer sitemap, a switche
assert.deepEqual(driftWarnings({ site: { html_sitemap: { enabled: false, paths: { pl: '/x' } } }, routes, ...layer }), [])
assert.deepEqual(driftWarnings({ site: {}, routes, ...layer }), [])
})
test('routeServes normalises CMS patterns: {param} and :param alike, the query part ignored', () => {
const route = { pattern: '/blog/:slug', catchAll: false }
assert.equal(routeServes(route, '/blog/{slug}'), true)
assert.equal(routeServes(route, '/blog/:slug'), true)
assert.equal(routeServes(route, '/blog/{slug}?page={page}'), true)
assert.equal(routeServes(route, '/blog/{category}/{slug}'), false)
assert.equal(routeServes({ pattern: '/docs/**', catchAll: true }, '/docs/{a}/{b}'), true)
})

106
tests/usePagination.test.ts Normal file
View File

@@ -0,0 +1,106 @@
// usePagination (D-40 to D-42, UI-SPEC L2) with the Nuxt composables it
// calls stubbed on globalThis: the page hrefs in the form the resolve
// answer, the site answer or the layer option names; the prev and next
// head links at the ends; the number of pages handed to the CMS head,
// whose %pages% seoFillPages replaces.
import assert from 'node:assert/strict'
import test from 'node:test'
import { computed, ref, toValue, watch } from 'vue'
import { seoFillPages } from '../app/utils/seo.ts'
interface Stubs {
fullPath: string
answer: unknown
site: unknown
option?: unknown
}
const g = globalThis as Record<string, unknown>
let heads: Array<() => { link: Array<{ rel: string; href: string }> }> = []
const states = new Map<string, ReturnType<typeof ref>>()
function stub(s: Stubs) {
heads = []
states.clear()
Object.assign(g, {
computed,
toValue,
watch,
useRoute: () => ({ fullPath: s.fullPath }),
useSeoAnswer: () => ref(s.answer),
useRuntimeConfig: () => ({ public: { seo: s.option === undefined ? {} : { pagination: s.option } } }),
useNuxtData: () => ({ data: ref(s.site) }),
useState: (key: string, init: () => unknown) => {
if (!states.has(key)) states.set(key, ref(init()))
return states.get(key)
},
useHead: (input: { value: { link: Array<{ rel: string; href: string }> } }) => heads.push(() => input.value),
})
}
const { usePagination, useSeoPages } = await import('../app/composables/usePagination.ts')
test('the query form from the resolve answer: page 1 unnumbered, other parameters kept', () => {
stub({ fullPath: '/blog?sort=new&page=2', answer: { locale: 'pl', pagination: { form: 'query', param: 'page', segment: '' } }, site: null })
const p = usePagination({ current: 2, last: 4 })
assert.equal(p.href(1), '/blog?sort=new')
assert.equal(p.href(3), '/blog?sort=new&page=3')
assert.deepEqual(p.form.value, { form: 'query', param: 'page', segment: '' })
})
test('the path form from the site answer with the locale segment', () => {
stub({
fullPath: '/en/blog/page/2',
answer: { locale: 'en' },
site: { default_locale: 'pl', pagination: { form: 'path', param: 'page', segments: { pl: 'strona', en: 'page' } } },
})
const p = usePagination({ current: 2, last: 3 })
assert.equal(p.href(1), '/en/blog')
assert.equal(p.href(3), '/en/blog/page/3')
stub({
fullPath: '/blog',
answer: null,
site: { default_locale: 'pl', pagination: { form: 'path', segments: { pl: 'strona' } } },
})
assert.equal(usePagination({ current: 1, last: 3 }).href(2), '/blog/strona/2')
})
test('the layer option without a CMS answer, with the given locale', () => {
stub({ fullPath: '/de/blog', answer: null, site: null, option: { form: 'path', segments: { de: 'seite' } } })
const p = usePagination({ current: 1, last: 2, locale: 'de' })
assert.equal(p.href(2), '/de/blog/seite/2')
})
test('prev and next head links: none on a single page, only next on the first, only prev on the last', () => {
const links = (current: number, last: number) => {
stub({ fullPath: '/blog', answer: { pagination: { form: 'query', param: 'page' } }, site: null })
usePagination({ current, last })
return heads[0]!().link.map((l) => `${l.rel} ${l.href}`)
}
assert.deepEqual(links(1, 1), [])
assert.deepEqual(links(1, 3), ['next /blog?page=2'])
assert.deepEqual(links(2, 3), ['prev /blog', 'next /blog?page=3'])
assert.deepEqual(links(3, 3), ['prev /blog?page=2'])
assert.deepEqual(links(5, 3), [])
})
test('the number of pages for %pages%: set from last, null without pages', () => {
stub({ fullPath: '/blog', answer: null, site: null })
usePagination({ current: 1, last: 7 })
assert.equal(useSeoPages().value, 7)
const head = seoFillPages({ title: 'Blog – strona 1 z %pages%', meta: [{ content: 'Wpisy (%pages% stron)' }, { content: 'x' }] }, useSeoPages().value as number)
assert.equal(head.title, 'Blog – strona 1 z 7')
assert.equal(head.meta[0]!.content, 'Wpisy (7 stron)')
stub({ fullPath: '/blog', answer: null, site: null })
usePagination({ current: 1, last: 0 })
assert.equal(useSeoPages().value, null)
assert.equal(seoFillPages({ title: '%pages%', meta: [] }, null).title, '%pages%')
})
test('current and last are whole numbers: current at least 1, last at least 0', () => {
stub({ fullPath: '/blog', answer: null, site: null })
const p = usePagination({ current: 0, last: -2.5 })
assert.equal(p.current.value, 1)
assert.equal(p.last.value, 0)
assert.equal(usePagination({ current: 2.9, last: '4' as unknown as number }).current.value, 2)
})