/** * Regression tests for issue #1368: a `datetime` field could not round-trip * through its own admin editor. The validator was * `z.string().datetime().or(z.string().date())`, which only accepts ISO with a * `Z` suffix or a bare `YYYY-MM-DD` date. But `` * (and many seeds) produce a *naive* datetime (`YYYY-MM-DDTHH:mm[:ss]`, no * offset). Since the admin re-sends every loaded field on autosave, a stored * naive datetime failed validation and the entry became unsavable. Same class * of autosave-round-trip bug as #867. * * The fix validates with `z.iso.datetime({ offset: true, local: true })` (which * accepts `Z`, timezone offsets, and naive datetimes) unioned with * `z.iso.date()` for date-only values — while still rejecting garbage and * impossible dates. */ import { describe, it, expect, beforeEach } from "vitest"; import type { Field } from "../../../src/schema/types.js"; import { generateFieldSchema, clearSchemaCache } from "../../../src/schema/zod-generator.js"; function datetimeField(overrides: Partial = {}): Field { return { id: "f1", collectionId: "c1", slug: "event_at", label: "Event at", type: "datetime", columnType: "TEXT", required: true, unique: false, sortOrder: 0, createdAt: "2024-01-01T00:00:00.000Z", ...overrides, }; } describe("zod-generator datetime validation (issue #1368)", () => { beforeEach(() => { clearSchemaCache(); }); const accepted = [ ["ISO with milliseconds and Z (admin-edited)", "2024-01-15T14:30:00.000Z"], ["ISO with Z, no milliseconds", "2024-01-15T14:30:00Z"], ["ISO with timezone offset", "2024-01-15T14:30:00+00:00"], ["naive datetime with seconds (seeded value -- the bug)", "2026-06-04T18:30:00"], ["naive datetime without seconds (datetime-local default)", "2024-01-15T14:30"], ["date only", "2024-01-15"], ] as const; for (const [label, value] of accepted) { it(`accepts ${label}`, () => { const schema = generateFieldSchema(datetimeField()); expect(schema.safeParse(value).success).toBe(true); }); } const rejected = [ ["non-date text", "not-a-date"], ["slash-separated date", "2024/01/15"], ["an impossible date (semantic validation retained)", "2024-13-45T99:99:99"], ] as const; for (const [label, value] of rejected) { it(`rejects ${label}`, () => { const schema = generateFieldSchema(datetimeField()); expect(schema.safeParse(value).success).toBe(false); }); } it("round-trips null/undefined for an optional datetime field", () => { const schema = generateFieldSchema(datetimeField({ required: false })); expect(schema.parse(undefined)).toBe(undefined); expect(schema.parse(null)).toBe(null); expect(schema.safeParse("2026-06-04T18:30:00").success).toBe(true); }); });