Files
vue-menu-layer/tests/client.test.ts
Jakub Zych d980f4cf2f feat: MenuNav layer for the SummerCMS menu API
- typed client and useMenu/useMenuApi (SSR, menu:<code>:<locale> key,
  server and public bases, x-forwarded-for forwarded also on cached renders)
- MenuNav and recursive MenuNavItem: WAI disclosure navigation, URL
  allowlist with a text fallback, rel on _blank, active link and trail,
  mobile toggle, Esc / focus-out / outside pointer / route change closing
- menu tokens and base CSS, README
- node tests (link rules, client, server-rendered NavItem) and a playground
  SSR smoke against a fixture API
2026-10-12 18:19:53 +02:00

79 lines
3.0 KiB
TypeScript

// The menu client against a fake fetcher: the URL built from base, code and
// locale, the envelope unwrapped, 404 as null. No network.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createMenuClient, isNotFound, menuPath } from '../app/utils/client.ts'
type Call = { path: string; query?: Record<string, string> }
function fakeFetcher(answer: (path: string) => unknown) {
const calls: Call[] = []
const fetcher = async (path: string, query?: Record<string, string>) => {
calls.push(query === undefined ? { path } : { path, query })
return answer(path)
}
return { fetcher, calls }
}
const MENU = {
data: {
code: 'main',
name: 'Main',
css_class: null,
items: [{ id: 1, title: 'Home', type: 'url', url: '/', target: null, css_class: null, icon: null, code: null, items: [] }],
},
}
test('menuPath encodes the code', () => {
assert.equal(menuPath('main'), '/menus/main')
assert.equal(menuPath('a b/c'), '/menus/a%20b%2Fc')
})
test('getMenu calls /menus/<code> and passes locale only when set', async () => {
const { fetcher, calls } = fakeFetcher(() => MENU)
const client = createMenuClient(fetcher)
const menu = await client.getMenu('main')
assert.equal(menu?.code, 'main')
assert.equal(menu?.items.length, 1)
await client.getMenu('main', 'en')
assert.deepEqual(calls, [{ path: '/menus/main' }, { path: '/menus/main', query: { locale: 'en' } }])
})
test('the fetcher base plus the path is the API URL', async () => {
const urls: string[] = []
const base = 'http://127.0.0.1:8096/_menu/api/v1'
const client = createMenuClient(async (path, query) => {
const u = new URL(base + path)
for (const [k, v] of Object.entries(query ?? {})) u.searchParams.set(k, v)
urls.push(u.toString())
return MENU
})
await client.getMenu('main-menu', 'pl')
assert.deepEqual(urls, ['http://127.0.0.1:8096/_menu/api/v1/menus/main-menu?locale=pl'])
})
test('a 404 resolves to null; other errors are thrown', async () => {
const notFound = createMenuClient(async () => {
throw Object.assign(new Error('Not Found'), { statusCode: 404 })
})
assert.equal(await notFound.getMenu('nope'), null)
const failing = createMenuClient(async () => {
throw Object.assign(new Error('boom'), { status: 500 })
})
await assert.rejects(failing.getMenu('main'), /boom/)
})
test('a body without data is null; missing items become an empty list', async () => {
assert.equal(await createMenuClient(async () => ({})).getMenu('x'), null)
const menu = await createMenuClient(async () => ({ data: { code: 'x', name: 'X', css_class: null } })).getMenu('x')
assert.deepEqual(menu?.items, [])
})
test('isNotFound reads status, statusCode and response.status', () => {
assert.equal(isNotFound({ status: 404 }), true)
assert.equal(isNotFound({ statusCode: 404 }), true)
assert.equal(isNotFound({ response: { status: 404 } }), true)
assert.equal(isNotFound({ status: 500 }), false)
assert.equal(isNotFound(null), false)
})