Files
TREK/shared/src/day/day.schema.spec.ts
T
Maurice eadb3703bc fix(shared,tests): bound the book contract and close the gaps that hid all this
Book page setup and element geometry were unbounded numbers, so a shared
book could be saved with zero, negative or astronomical dimensions and every
consumer downstream - the canvas, the PDF export - had to cope. Day-note caps
had drifted from the limits their own doc comment states, and icon had none.

On the test side: the path filter meant a PR touching only the root lockfile
ran no jobs at all, which is exactly the change class most likely to break a
native binding; the canAccessTrip mock is hand-copied into 72 files and had
already drifted from the real query; the path-traversal test asserted only
status !== 200, so a 500 from a broken guard passed it; and one unit test
shelled out to a real kitinerary binary if the host happened to have one.
2026-08-23 11:59:12 +02:00

61 lines
3.1 KiB
TypeScript

import { dayCreateRequestSchema, dayNoteCreateRequestSchema, dayNoteUpdateRequestSchema } from './day.schema';
import { describe, it, expect } from 'vitest';
describe('dayCreateRequestSchema', () => {
it('accepts an optional date + notes', () => {
expect(dayCreateRequestSchema.safeParse({}).success).toBe(true);
expect(dayCreateRequestSchema.safeParse({ date: '2026-07-01', notes: 'n' }).success).toBe(true);
});
});
describe('dayNoteCreateRequestSchema', () => {
it('requires non-empty text capped at 500, body capped at 2000', () => {
expect(dayNoteCreateRequestSchema.safeParse({ text: 'Lunch' }).success).toBe(true);
expect(dayNoteCreateRequestSchema.safeParse({ text: '' }).success).toBe(false);
expect(dayNoteCreateRequestSchema.safeParse({ text: 'x'.repeat(501) }).success).toBe(false);
// Raised from 250 with #1629: the field carries formatted Markdown now.
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', time: 'y'.repeat(2000) }).success).toBe(true);
expect(
dayNoteCreateRequestSchema.safeParse({
text: 'ok',
time: 'y'.repeat(2001),
}).success,
).toBe(false);
});
it('takes a note colour, and only as a short string', () => {
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', color: '#dc2626' }).success).toBe(true);
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', color: null }).success).toBe(true);
// The palette itself is enforced in the service — the contract only keeps
// the column from taking arbitrary length.
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', color: 'x'.repeat(10) }).success).toBe(false);
});
it('accepts null time/icon (moveDayNote re-sends the nullable entity fields)', () => {
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', time: null, icon: null }).success).toBe(true);
});
it('takes an icon as a label and not as a payload', () => {
// Both shapes the field is written in: a lucide export name from NOTE_ICONS
// and a plain emoji, plus the longest ZWJ sequence anyone types.
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', icon: 'ParkingSquare' }).success).toBe(true);
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', icon: '👩‍👩‍👧‍👦' }).success).toBe(true);
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', icon: 'x'.repeat(64) }).success).toBe(true);
expect(dayNoteCreateRequestSchema.safeParse({ text: 'ok', icon: 'x'.repeat(65) }).success).toBe(false);
});
});
describe('dayNoteUpdateRequestSchema', () => {
it('allows omitting text and caps the lengths', () => {
expect(dayNoteUpdateRequestSchema.safeParse({}).success).toBe(true);
expect(dayNoteUpdateRequestSchema.safeParse({ icon: '🍽️' }).success).toBe(true);
expect(dayNoteUpdateRequestSchema.safeParse({ text: 'x'.repeat(501) }).success).toBe(false);
expect(dayNoteUpdateRequestSchema.safeParse({ icon: 'x'.repeat(65) }).success).toBe(false);
});
it('accepts an explicit null time (clears the label)', () => {
expect(dayNoteUpdateRequestSchema.safeParse({ time: null }).success).toBe(true);
});
});