250 lines
4.7 KiB
TypeScript
250 lines
4.7 KiB
TypeScript
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: 'events',
|
|
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')),
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|