feat: entry editor at /content/:key/new and /content/:key/:id

This commit is contained in:
Florian Sylvain committed 2026-10-21 00:12:34 +02:00
1 parent 91d916fb74
commit fba20f83c5
94 files changed
+7116 -423

No files matched your search

+157
View File
@@ -0,0 +1,157 @@
import router from '@/router'
import { expect, vi } from 'vitest'
import { flushPromises, type VueWrapper } from '@vue/test-utils'
import EntryField from '@/components/EntryField.vue'
import { addCollection } from '@iconify/vue'
import lucide from 'virtual:lucide-subset'
import { mountAt, text, type EntryWrite, type SchemaMount } from './schemaTestKit'
export {
button,
calls,
customTypes,
entryWrites,
failWith,
failWithBody,
networkFailure,
pending,
text,
unmountAll,
} from './schemaTestKit'
export { MOBILE_WIDTH } from '@/components/__tests__/viewport'
export { eventsFields } from '@/lib/__tests__/schemaFixtures'
addCollection(lucide)
export const MARTA = 3
export const INES = 4
export const JONAS = 5
export const entry42 = {
id: 42,
content_type: 'events',
data: {
title: 'Lime plaster workshop',
starts_on: '2026-11-21',
ends_on: '2026-11-22',
online: false,
capacity: 18,
category: 'Workshop',
description:
'Two days of hands-on work with **lime putty** and old stone.\n\n- Bring work clothes\n- Lunch is included',
},
created_by: INES,
updated_by: MARTA,
created_at: '2026-10-12T09:14:00',
updated_at: '2026-10-15T11:02:00',
}
export const userNames = [
{ id: INES, display_name: 'Inès Duarte' },
{ id: MARTA, display_name: 'Marta Alves' },
{ id: JONAS, display_name: 'Jonas Pereira' },
]
export function lookupNames(ids: number[]) {
return userNames.filter((user) => ids.includes(user.id))
}
export function entryWith(data: Record<string, unknown>, rest: Record<string, unknown> = {}) {
return { ...entry42, ...rest, data: { ...entry42.data, ...data } }
}
export function answerWrite(write: EntryWrite) {
const data = write.body.data as Record<string, unknown>
const base = write.method === 'POST' ? { ...entry42, id: 43 } : entry42
return {
...base,
data,
updated_by: MARTA,
updated_at: '2026-10-19T11:02:00',
}
}
export function mountEditor(path: string, options: SchemaMount = {}) {
return mountAt(path, {
entry: () => entry42,
writeEntry: answerWrite,
lookup: lookupNames,
...options,
})
}
export async function settle() {
await flushPromises()
}
export function fieldWrapper(key: string) {
return document.body.querySelector<HTMLElement>(`[data-entry-field="${key}"]`)!
}
export function control(key: string) {
return fieldWrapper(key).querySelector<HTMLInputElement | HTMLTextAreaElement>(
'input, textarea',
)!
}
export async function typeInto(key: string, value: string) {
const element = control(key)
element.value = value
element.dispatchEvent(new Event('input', { bubbles: true }))
await flushPromises()
}
export function chip() {
return document.body.querySelector('[data-testid="builder-chip"]')
}
export async function save() {
document.body.querySelector<HTMLButtonElement>('button[type="submit"]')!.click()
await flushPromises()
}
export function setField(wrapper: VueWrapper, key: string, value: unknown) {
const owner = wrapper
.findAllComponents(EntryField)
.find((candidate) => candidate.props('field').key === key)!
owner.vm.$emit('update', key, value)
return flushPromises()
}
export function main() {
return document.body.querySelector('#dashboard-content')!
}
export function toast() {
return text(document.body.querySelector('[data-part="root"], [role="status"][data-state]'))
}
export function hasText(value: string) {
return text(document.body).includes(value)
}
export function messageUnder(key: string) {
return text(fieldWrapper(key).querySelector('[role="alert"]'))
}
export async function arrived(path: string) {
await vi.waitFor(() => expect(router.currentRoute.value.fullPath).toBe(path))
}
export function installSelectPolyfills() {
Element.prototype.scrollIntoView = vi.fn()
Element.prototype.hasPointerCapture = vi.fn(() => false)
Element.prototype.releasePointerCapture = vi.fn()
}
export async function openChoices(key: string) {
const trigger = fieldWrapper(key).querySelector('[role="combobox"]')!
trigger.dispatchEvent(
Object.assign(new MouseEvent('pointerdown', { bubbles: true, button: 0 }), {
pointerType: 'mouse',
}),
)
await flushPromises()
return [...document.body.querySelectorAll<HTMLElement>('[role="option"]')]
}