import { expect, expectTypeOf, test } from "vitest"; import { z } from "zod/v4"; /* ------------------------------------------------------------------ * * TYPE-LEVEL — the dangerous half. Defaults must stay OPTIONAL in * z.input and REQUIRED in z.output, exactly as before the ladder. * ------------------------------------------------------------------ */ test("ladder: z.input / z.output unchanged by the extra rung", () => { const objDefault = z.object({ a: z.string().default("D"), b: z.number() }); expectTypeOf>().toEqualTypeOf<{ a?: string | undefined; b: number }>(); expectTypeOf>().toEqualTypeOf<{ a: string; b: number }>(); const objPrefault = z.object({ a: z.string().prefault("P"), b: z.number() }); expectTypeOf>().toEqualTypeOf<{ a?: string | undefined; b: number }>(); expectTypeOf>().toEqualTypeOf<{ a: string; b: number }>(); const objOptional = z.object({ a: z.string().optional() }); expectTypeOf>().toEqualTypeOf<{ a?: string | undefined }>(); const objTransform = z.object({ a: z .string() .default("") .transform((v) => v.length), }); expectTypeOf>().toEqualTypeOf<{ a?: string | undefined }>(); expectTypeOf>().toEqualTypeOf<{ a: number }>(); const standalone = z.string().default("D"); expectTypeOf>().toEqualTypeOf(); expectTypeOf>().toEqualTypeOf(); // a trailing defaulted tuple slot stays optional in the input tuple const tup = z.tuple([z.string(), z.number().default(1)]); expectTypeOf>().toEqualTypeOf<[string, (number | undefined)?]>(); const partialed = objDefault.partial(); expectTypeOf>().toEqualTypeOf<{ a?: string | undefined; b?: number | undefined }>(); }); /* ------------------------------------------------------------------ * * RUNTIME * ------------------------------------------------------------------ */ test("ladder: the #6321 regression is fixed", () => { const arr = z .string() .default("") .transform((v) => (v ? v.split(",") : [])); expect(z.object({ a: arr }).partial().parse({})).toEqual({ a: [] }); expect(z.object({ a: arr }).partial().parse({ a: undefined })).toEqual({ a: [] }); expect(z.object({ a: arr }).partial().parse({ a: "x,y" })).toEqual({ a: ["x", "y"] }); expect(arr.optional().parse(undefined)).toEqual([]); expect( z .string() .prefault("hi") .transform((v) => v.length) .optional() .parse(undefined) ).toBe(2); }); test("ladder: key-level admissibility across every rung", () => { expect(z.object({ a: z.string().default("D") }).parse({})).toEqual({ a: "D" }); expect(z.object({ a: z.string().prefault("P") }).parse({})).toEqual({ a: "P" }); expect(z.object({ a: z.string().optional() }).parse({})).toEqual({}); expect(z.object({ a: z.transform(() => "T") }).parse({})).toEqual({ a: "T" }); expect(z.object({ a: z.string().catch("C") }).parse({})).toEqual({ a: "C" }); expect(z.object({ a: z.string() }).safeParse({}).success).toBe(false); }); test("ladder: exactOptional keeps its distinct meaning", () => { expect(z.object({ a: z.string().exactOptional() }).parse({})).toEqual({}); expect(z.object({ a: z.string().exactOptional() }).safeParse({ a: undefined }).success).toBe(false); }); // An absent key never materializes a value from a schema that only claims the middle rung, however that schema answers `undefined`. The top rung still substitutes, and an explicitly present `undefined` still runs the inner. test.each([true, false])("ladder: an absent key stays absent on the middle rung (jitless=%s)", (jitless) => { const opts = { jitless }; expect(z.object({ a: z.coerce.string().exactOptional() }).parse({}, opts)).toStrictEqual({}); expect(z.object({ a: z.string().catch("C").exactOptional() }).parse({}, opts)).toStrictEqual({}); expect(z.object({ a: z.preprocess((v) => v ?? "X", z.string()).exactOptional() }).parse({}, opts)).toStrictEqual({}); expect(z.object({ a: z.union([z.coerce.string(), z.string().optional()]) }).parse({}, opts)).toStrictEqual({}); expect(z.object({ a: z.string().default("D").exactOptional() }).parse({}, opts)).toStrictEqual({ a: "D" }); expect(z.object({ a: z.string().prefault("P").exactOptional() }).parse({}, opts)).toStrictEqual({ a: "P" }); expect(z.object({ a: z.coerce.string().exactOptional() }).parse({ a: undefined }, opts)).toStrictEqual({ a: "undefined", }); expect(z.object({ a: z.coerce.string() }).parse({ a: undefined }, opts)).toStrictEqual({ a: "undefined" }); }); test("ladder: an absent tuple slot on the middle rung truncates the tail", () => { expect(z.tuple([z.string(), z.coerce.string().exactOptional()]).parse(["x"])).toStrictEqual(["x"]); expect(z.tuple([z.string(), z.string().catch("C").exactOptional()]).parse(["x"])).toStrictEqual(["x"]); expect(z.tuple([z.string(), z.string().default("D").exactOptional()]).parse(["x"])).toStrictEqual(["x", "D"]); }); // z.compile() assembles its own output, so the gate has to be mirrored there or compile mode silently keeps the invented value. test("ladder: compile mode agrees with the interpreted and JIT paths on absent middle-rung slots", () => { const objects = [ [z.object({ a: z.coerce.string().exactOptional() }), {}], [z.object({ a: z.string().catch("C").exactOptional() }), {}], [z.object({ a: z.string().default("D").exactOptional() }), { a: "D" }], [z.object({ a: z.string().catch("C") }), { a: "C" }], ] as const; for (const [schema, expected] of objects) { expect(z.compile(schema).parse({})).toStrictEqual(expected); expect(schema.parse({}, { jitless: true })).toStrictEqual(expected); expect(schema.parse({})).toStrictEqual(expected); } const tuples = [ [z.tuple([z.string(), z.coerce.string().exactOptional()]), ["x"]], [z.tuple([z.string(), z.string().default("D").exactOptional()]), ["x", "D"]], ] as const; for (const [schema, expected] of tuples) { expect(z.compile(schema).parse(["x"])).toStrictEqual(expected); expect(schema.parse(["x"], { jitless: true })).toStrictEqual(expected); } }); test("ladder: tuple minimum length respects every rung", () => { expect(z.tuple([z.string().default("D")]).parse([])).toEqual(["D"]); expect(z.tuple([z.string().prefault("P")]).parse([])).toEqual(["P"]); expect(z.tuple([z.string().optional()]).parse([])).toEqual([]); expect(z.tuple([z.string()]).safeParse([]).success).toBe(false); expect(z.tuple([z.string(), z.number().default(1)]).parse(["x"])).toEqual(["x", 1]); }); test("ladder: the #5941 / #5939 clobber invariants still hold", () => { expect( z .preprocess((v) => v ?? "X", z.string()) .optional() .parse(undefined) ).toBeUndefined(); expect(z.string().catch("X").optional().parse(undefined)).toBeUndefined(); expect( z .string() .catch("X") .transform((s) => `${s}!`) .optional() .parse(undefined) ).toBeUndefined(); expect( z .transform(() => "T") .optional() .parse(undefined) ).toBeUndefined(); expect( z .union([z.preprocess((v) => v ?? "X", z.string()), z.number()]) .optional() .parse(undefined) ).toBeUndefined(); expect(z.object({ a: z.preprocess((v) => v ?? "X", z.string()) }).parse({})).toEqual({ a: "X" }); }); test("ladder: the top rung survives every wrapper order", () => { expect(z.string().default("D").optional().parse(undefined)).toBe("D"); expect(z.string().default("D").optional().optional().parse(undefined)).toBe("D"); expect(z.string().default("D").nullable().optional().parse(undefined)).toBe("D"); expect(z.string().default("D").readonly().optional().parse(undefined)).toBe("D"); expect(z.string().default("D").catch("C").optional().parse(undefined)).toBe("D"); expect(z.string().default("D").pipe(z.string()).optional().parse(undefined)).toBe("D"); expect( z .union([z.string().default("D"), z.number()]) .optional() .parse(undefined) ).toBe("D"); expect( z .lazy(() => z.string().default("D")) .optional() .parse(undefined) ).toBe("D"); }); test("ladder: record and catchall paths that read optin", () => { expect(z.record(z.string(), z.string().default("D")).parse({ k: undefined })).toEqual({ k: "D" }); expect(z.object({}).catchall(z.string().default("D")).parse({ x: undefined })).toEqual({ x: "D" }); }); test("ladder: async parity", async () => { const a = z .string() .default("") .transform(async (v) => (v ? v.split(",") : [])); expect((await a.optional().safeParseAsync(undefined)).data).toEqual([]); expect((await z.object({ a }).partial().safeParseAsync({})).data).toEqual({ a: [] }); expect( ( await z .preprocess(async (v) => v ?? "X", z.string()) .optional() .safeParseAsync(undefined) ).data ).toBeUndefined(); }); test("ladder: the rung values themselves", () => { expect(z.string()._zod.optin).toBeUndefined(); expect(z.string().optional()._zod.optin).toBe("optional"); expect(z.transform(() => 1)._zod.optin).toBe("optional"); expect(z.string().catch("C")._zod.optin).toBe("optional"); expect(z.string().default("D")._zod.optin).toBe("defaulted"); expect(z.string().prefault("P")._zod.optin).toBe("defaulted"); expect( z .string() .default("D") .transform((v) => v)._zod.optin ).toBe("defaulted"); expect(z.string().default("D").optional()._zod.optin).toBe("defaulted"); expect(z.union([z.string().default("D"), z.number()])._zod.optin).toBe("defaulted"); expect(z.union([z.string().optional(), z.number()])._zod.optin).toBe("optional"); }); test("exactOptional overrides the values and pattern optional installs", () => { expect(z.exactOptional(z.enum(["a", "b"]))._zod.values).toEqual(new Set(["a", "b"])); // a format carries its pattern on its def; a chained `.regex()` no longer folds into the inner `_zod.pattern`, the template literal derives that itself expect(z.exactOptional(z.stringFormat("abc", /^abc$/))._zod.pattern).toEqual(/^abc$/); expect(z.templateLiteral(["a", z.exactOptional(z.literal("b"))]).safeParse("a").success).toEqual(false); }); // The override is installed once, on a prototype every instance of the type shares, so it has to hold for instances built after the first. test("exactOptional's override holds for later instances", () => { z.exactOptional(z.enum(["a", "b"])); expect(z.exactOptional(z.enum(["c", "d"]))._zod.values).toEqual(new Set(["c", "d"])); expect(z.exactOptional(z.stringFormat("xyz", /^xyz$/))._zod.pattern).toEqual(/^xyz$/); });