feat: entry list at /content/:key

This commit is contained in:
Florian Sylvain committed 2026-10-20 21:05:03 +02:00
1 parent b4cbcb0395
commit 91d916fb74
44 files changed
+4984 -32

No files matched your search

+250
View File
@@ -0,0 +1,250 @@
import { vi } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { addCollection } from '@iconify/vue'
import lucide from 'virtual:lucide-subset'
import { mountAt, type SchemaMount } from './schemaTestKit'
export {
button,
calls,
customTypes,
failWith,
failWithBody,
manyTypes,
networkFailure,
pending,
text,
unmountAll,
} from './schemaTestKit'
addCollection(lucide)
export const SKELETON_DELAY_MS = 150
export const DEFAULT_SORT = 'updated_at:desc'
const MARTA = 3
const INES = 4
const JONAS = 5
type Row = [
number,
string,
string,
string,
boolean,
number | null,
string,
string,
string,
string,
number,
]
const ROWS: Row[] = [
[
42,
'Lime plaster workshop',
'2026-11-21',
'2026-11-22',
false,
18,
'Workshop',
'Two days of hands-on work with lime putty and old stone.',
'2026-10-12',
'2026-10-15',
MARTA,
],
[
41,
'Heat pumps: what the numbers say',
'2026-11-03',
'',
true,
500,
'Talk',
'A look at running costs after two winters.',
'2026-10-08',
'2026-10-14',
JONAS,
],
[
40,
'Open day at the Rue Verte site',
'2026-11-12',
'2026-11-12',
false,
120,
'Meetup',
'',
'2026-10-02',
'2026-10-12',
INES,
],
[
39,
'Winter windows clinic',
'2026-12-05',
'',
false,
null,
'Workshop',
'Bring a window, leave with a plan.',
'2026-09-30',
'2026-10-09',
MARTA,
],
[
38,
'Timber joinery for beginners',
'2027-01-14',
'2027-01-15',
false,
12,
'Workshop',
'Mortise, tenon and a lot of patience.',
'2026-09-28',
'2026-10-07',
INES,
],
[
37,
'Q&A with the site managers',
'2026-10-28',
'',
true,
null,
'Meetup',
'',
'2026-09-21',
'2026-10-02',
JONAS,
],
[
36,
'Reclaimed floorboards talk',
'2026-10-17',
'',
false,
60,
'Talk',
'Where to find them and how to lay them.',
'2026-09-14',
'2026-09-29',
MARTA,
],
]
export function entry(row: Row) {
const [
id,
title,
startsOn,
endsOn,
online,
capacity,
category,
description,
created,
updated,
by,
] = row
return {
id,
content_type_id: 1,
data: {
title,
starts_on: startsOn,
ends_on: endsOn,
online,
capacity,
category,
description,
},
created_by: by,
updated_by: by,
created_at: `${created}T09:14:00Z`,
updated_at: `${updated}T11:02:00Z`,
}
}
export const eventEntries = ROWS.map(entry)
export const userNames = [
{ id: MARTA, display_name: 'Marta Alves' },
{ id: INES, display_name: 'Inès Duarte' },
{ id: JONAS, display_name: 'Jonas Pereira' },
]
export function listPage(items: unknown[], total: number, query: URLSearchParams) {
return {
items,
total,
page: Number(query.get('page')),
size: Number(query.get('size')),
sort: query.get('sort'),
}
}
export function populated(key: string, query: URLSearchParams) {
return key === 'events' ? listPage(eventEntries, 7, query) : listPage([], 0, query)
}
export function lookupNames(ids: number[]) {
return userNames.filter((user) => ids.includes(user.id))
}
export function mountList(path: string, options: SchemaMount = {}) {
return mountAt(path, { list: populated, lookup: lookupNames, ...options })
}
export async function settle() {
await flushPromises()
}
export function listRequests(calls: string[]) {
return calls.filter((url) => /\/entry\?/.test(url) && !/size=5$/.test(url))
}
export function lookupRequests(calls: string[]) {
return calls.filter((url) => url.startsWith('/v1/user/lookup'))
}
export function installSelectPolyfills() {
Element.prototype.scrollIntoView = vi.fn()
Element.prototype.hasPointerCapture = vi.fn(() => false)
Element.prototype.releasePointerCapture = vi.fn()
}
export function openSelect(trigger: Element | null) {
trigger?.dispatchEvent(
Object.assign(new MouseEvent('pointerdown', { bubbles: true, button: 0 }), {
pointerType: 'mouse',
}),
)
}
export async function chooseSort(label: string) {
openSelect(document.body.querySelector('[aria-label="Sort by"]'))
await flushPromises()
const option = [...document.body.querySelectorAll<HTMLElement>('[role="option"]')].find(
(candidate) => candidate.textContent?.trim() === label,
)!
option.dispatchEvent(new MouseEvent('pointerup', { bubbles: true }))
option.click()
await flushPromises()
}
export async function chooseSize(label: string) {
document.body.querySelector<HTMLElement>('[aria-label="Rows per page"]')!.click()
await flushPromises()
const option = [...document.body.querySelectorAll<HTMLElement>('[role="option"]')].find(
(candidate) => candidate.textContent?.trim() === label,
)!
option.click()
await flushPromises()
}
export async function press(label: string) {
document.body.querySelector<HTMLElement>(`[aria-label="${label}"]`)!.click()
await flushPromises()
}