Files
TREK/client/tests/unit/studio/autoLayout.contract.test.ts
T
Maurice 4570ace78c feat(journey): the shared book engine lays out, edits and designs whole books
Every element kind and Studio tool gets an op, the auto layout and its
templates move from the client to shared with a parity fixture, and a pure
design engine turns directives into a finished book with design findings.
2026-10-22 21:34:15 +00:00

95 lines
4.4 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import type { BookPageSetup, JourneyStats } from '@trek/shared'
import { bookDocumentSchema, bookPageSetupSchema, buildBook, emptyBook, type AutoEntry, type AutoInput } from '@trek/shared'
/**
* The auto layout writes journey data straight into a book document, and the
* save route parses that document against the contract before storing it — so
* anything the layout produces that the contract will not take is a book that
* cannot be saved at all, for the rest of the session (#2085).
*
* These cases feed it journeys past every cap the contract sets and assert the
* one thing that matters: whatever comes out, the server would take it.
*/
const page: BookPageSetup = bookPageSetupSchema.parse({
preset: 'square-210', pageWidth: 210, pageHeight: 210, bleed: 3, safe: 5,
})
const long = (n: number) => 'x'.repeat(n)
const entry = (over: Partial<AutoEntry> = {}): AutoEntry => ({
id: 1, title: 'A day', story: 'Something happened.', location: 'Reykjavík',
date: '2026-06-02', photos: [], ...over,
})
const stats = (over: Partial<JourneyStats> = {}): JourneyStats => ({
journeyId: 6, distance: 1_189_000, days: 14, steps: 14, photos: 57, places: 21, furthest: 408_000,
countries: [{ code: 'IS', name: 'Iceland', places: 14, firstVisit: '2026-06-02' }],
points: [
{ lat: 64.14, lng: -21.94, label: 'Reykjavík', date: '2026-06-02', country: 'IS', tripId: null, photoId: null, entryId: null },
{ lat: 65.68, lng: -18.12, label: 'Akureyri', date: '2026-06-06', country: 'IS', tripId: null, photoId: null, entryId: null },
],
trips: [], excluded: [], start: '2026-06-02', end: '2026-06-15',
...over,
})
const input = (over: Partial<AutoInput> = {}): AutoInput => ({
locale: 'en', title: 'Iceland', subtitle: null, coverPhotoId: null,
stationsLabel: 'Stations', dayLabel: 'DAY', summaryLabel: 'Trip summary', countriesLabel: 'Countries',
entries: [entry()], page, stats: stats(), ...over,
})
/** The contract's own verdict, with the offending fields named when it refuses. */
const savable = (doc: unknown) => {
const parsed = bookDocumentSchema.safeParse(doc)
return parsed.success
? { ok: true as const }
: { ok: false as const, at: parsed.error.issues.map(i => i.path.join('.')) }
}
describe('a laid-out book is a book the server will take', () => {
it('when an entry runs longer than a text element may hold', () => {
expect(savable(buildBook(input({ entries: [entry({ story: long(20_000) })] })))).toEqual({ ok: true })
})
it('when a stop carries a place name longer than a map label may print', () => {
const points = [
{ lat: 64.14, lng: -21.94, label: long(300), date: '2026-06-02', country: 'IS', tripId: null, photoId: null, entryId: null },
{ lat: 65.68, lng: -18.12, label: 'Akureyri', date: '2026-06-06', country: 'IS', tripId: null, photoId: null, entryId: null },
]
expect(savable(buildBook(input({ stats: stats({ points }) })))).toEqual({ ok: true })
})
it('when the journey visited more countries than a page may name', () => {
const countries = Array.from({ length: 150 }, () => ({
code: 'IS', name: long(150), places: 1, firstVisit: '2026-06-02',
}))
expect(savable(buildBook(input({ stats: stats({ countries }) })))).toEqual({ ok: true })
})
it('when the journey is called something longer than a title may be', () => {
expect(savable(buildBook(input({ title: long(400) })))).toEqual({ ok: true })
expect(savable(emptyBook(input({ title: long(400) })))).toEqual({ ok: true })
})
/*
* The one that is not about a runaway string: a run of stops with nothing on
* them becomes a two-column list, and the rows cost three elements each. At
* thirteen rows a page that is eighty elements on a spread the contract caps
* at sixty — an ordinary journey of dated stops, not a pathological one.
*/
it('when a long run of bare stops becomes an itinerary list', () => {
const bare = Array.from({ length: 60 }, (_, i) => entry({
id: i + 1, title: 'Stop ' + i, story: '', location: 'Place ' + i, date: '2026-06-01',
}))
expect(savable(buildBook(input({ entries: bare })))).toEqual({ ok: true })
})
it('when a recorded track is longer than the map may draw', () => {
const path = Array.from({ length: 60 }, () =>
Array.from({ length: 2000 }, () => [64, -21] as [number, number]))
expect(savable(buildBook(input({ path })))).toEqual({ ok: true })
})
})