import { expect, expectTypeOf, test } from "vitest"; import * as z from "zod/mini"; import type { util } from "zod/v4/core"; test("factory checks", () => { const string = z.string({ checks: [z.minLength(1), z.maxLength(3)] as const }); const number = z.number({ checks: [z.minimum(1), z.maximum(3)] as const }); expectTypeOf>().toEqualTypeOf(); expectTypeOf>().toEqualTypeOf(); for (const [schema, valid, invalid] of [ [string, "ab", ""], [number, 2, 4], ] as const) { expect(z.validate(schema, valid)).toBe(true); expect(z.validate(schema, invalid)).toBe(false); expect(schema._zod.parent).toBeUndefined(); expect(z.validate(z.compile(schema), valid)).toBe(true); expect(z.validate(z.compile(schema), invalid)).toBe(false); } // @ts-expect-error numeric checks cannot validate strings z.string({ checks: [z.minimum(1)] }); // @ts-expect-error length checks cannot validate numbers z.number({ checks: [z.minLength(1)] }); }); test("factory checks snapshot caller arrays", () => { for (const factory of [z.string, z.coerce.string]) { const checks = [z.minLength(1)]; const schema = factory({ checks }); let reads = 0; factory({ get checks() { if (++reads > 1) throw new Error("checks read twice"); return checks; }, }); expect(reads).toBe(1); checks.push(z.minLength(10)); expect(z.validate(schema, "long")).toBe(true); expect(z.validate(z.compile(schema), "long")).toBe(true); expect(z.toJSONSchema(schema).minLength).toBe(1); } for (const factory of [z.number, z.coerce.number]) { const checks = [z.minimum(1)]; const schema = factory({ checks }); let reads = 0; factory({ get checks() { if (++reads > 1) throw new Error("checks read twice"); return checks; }, }); expect(reads).toBe(1); checks.push(z.minimum(10)); expect(z.validate(schema, 3)).toBe(true); expect(z.validate(z.compile(schema), 3)).toBe(true); expect(z.toJSONSchema(schema).minimum).toBe(1); } }); test("z.boolean", () => { const a = z.boolean(); expect(z.parse(a, true)).toEqual(true); expect(z.parse(a, false)).toEqual(false); expect(() => z.parse(a, 123)).toThrow(); expect(() => z.parse(a, "true")).toThrow(); type a = z.output; expectTypeOf().toEqualTypeOf(); }); test("z.bigint", () => { const a = z.bigint(); expect(z.parse(a, BigInt(123))).toEqual(BigInt(123)); expect(() => z.parse(a, 123)).toThrow(); expect(() => z.parse(a, "123")).toThrow(); }); test("z.symbol", () => { const a = z.symbol(); const sym = Symbol(); expect(z.parse(a, sym)).toEqual(sym); expect(() => z.parse(a, "symbol")).toThrow(); }); test("z.date", () => { const a = z.date(); const date = new Date(); expect(z.parse(a, date)).toEqual(date); expect(() => z.parse(a, "date")).toThrow(); }); test("z.coerce.string", () => { const a = z.coerce.string(); expect(z.parse(a, 123)).toEqual("123"); expect(z.parse(a, true)).toEqual("true"); expect(z.parse(a, null)).toEqual("null"); expect(z.parse(a, undefined)).toEqual("undefined"); }); test("z.coerce.number", () => { const a = z.coerce.number(); expect(z.parse(a, "123")).toEqual(123); expect(z.parse(a, "123.45")).toEqual(123.45); expect(z.parse(a, true)).toEqual(1); expect(z.parse(a, false)).toEqual(0); expect(() => z.parse(a, "abc")).toThrow(); }); test("z.coerce.boolean", () => { const a = z.coerce.boolean(); // test booleans expect(z.parse(a, true)).toEqual(true); expect(z.parse(a, false)).toEqual(false); expect(z.parse(a, "true")).toEqual(true); expect(z.parse(a, "false")).toEqual(true); expect(z.parse(a, 1)).toEqual(true); expect(z.parse(a, 0)).toEqual(false); expect(z.parse(a, {})).toEqual(true); expect(z.parse(a, [])).toEqual(true); expect(z.parse(a, undefined)).toEqual(false); expect(z.parse(a, null)).toEqual(false); expect(z.parse(a, "")).toEqual(false); }); test("z.coerce.bigint", () => { const a = z.coerce.bigint(); expect(z.parse(a, "123")).toEqual(BigInt(123)); expect(z.parse(a, 123)).toEqual(BigInt(123)); expect(() => z.parse(a, "abc")).toThrow(); }); test("z.coerce.date", () => { const a = z.coerce.date(); const date = new Date(); expect(z.parse(a, date.toISOString())).toEqual(date); expect(z.parse(a, date.getTime())).toEqual(date); expect(() => z.parse(a, "invalid date")).toThrow(); }); test("z.iso.datetime", () => { const d1 = "2021-01-01T00:00:00Z"; const d2 = "2021-01-01T00:00:00.123Z"; const d3 = "2021-01-01T00:00:00"; const d4 = "2021-01-01T00:00:00+07:00"; const d5 = "bad data"; // local: false, offset: false, precision: null const a = z.iso.datetime(); expect(z.safeParse(a, d1).success).toEqual(true); expect(z.safeParse(a, d2).success).toEqual(true); expect(z.safeParse(a, d3).success).toEqual(false); expect(z.safeParse(a, d4).success).toEqual(false); expect(z.safeParse(a, d5).success).toEqual(false); const b = z.iso.datetime({ local: true }); expect(z.safeParse(b, d1).success).toEqual(true); expect(z.safeParse(b, d2).success).toEqual(true); expect(z.safeParse(b, d3).success).toEqual(true); expect(z.safeParse(b, d4).success).toEqual(false); expect(z.safeParse(b, d5).success).toEqual(false); const c = z.iso.datetime({ offset: true }); expect(z.safeParse(c, d1).success).toEqual(true); expect(z.safeParse(c, d2).success).toEqual(true); expect(z.safeParse(c, d3).success).toEqual(false); expect(z.safeParse(c, d4).success).toEqual(true); expect(z.safeParse(c, d5).success).toEqual(false); const d = z.iso.datetime({ precision: 3 }); expect(z.safeParse(d, d1).success).toEqual(false); expect(z.safeParse(d, d2).success).toEqual(true); expect(z.safeParse(d, d3).success).toEqual(false); expect(z.safeParse(d, d4).success).toEqual(false); expect(z.safeParse(d, d5).success).toEqual(false); }); test("z.iso.date", () => { const d1 = "2021-01-01"; const d2 = "bad data"; const a = z.iso.date(); expect(z.safeParse(a, d1).success).toEqual(true); expect(z.safeParse(a, d2).success).toEqual(false); const b = z.string().check(z.iso.date()); expect(z.safeParse(b, d1).success).toEqual(true); expect(z.safeParse(b, d2).success).toEqual(false); }); test("z.iso.time", () => { const d1 = "00:00:00"; const d2 = "00:00:00.123"; const d3 = "bad data"; const a = z.iso.time(); expect(z.safeParse(a, d1).success).toEqual(true); expect(z.safeParse(a, d2).success).toEqual(true); expect(z.safeParse(a, d3).success).toEqual(false); const b = z.iso.time({ precision: 3 }); expect(z.safeParse(b, d1).success).toEqual(false); expect(z.safeParse(b, d2).success).toEqual(true); expect(z.safeParse(b, d3).success).toEqual(false); const c = z.string().check(z.iso.time()); expect(z.safeParse(c, d1).success).toEqual(true); expect(z.safeParse(c, d2).success).toEqual(true); expect(z.safeParse(c, d3).success).toEqual(false); }); test("z.iso.duration", () => { const d1 = "P3Y6M4DT12H30M5S"; const d2 = "bad data"; const a = z.iso.duration(); expect(z.safeParse(a, d1).success).toEqual(true); expect(z.safeParse(a, d2).success).toEqual(false); const b = z.string().check(z.iso.duration()); expect(z.safeParse(b, d1).success).toEqual(true); expect(z.safeParse(b, d2).success).toEqual(false); }); test("z.prefault preserves undefined output", async () => { const field = z.prefault(z.union([z.string(), z.undefined()]), () => undefined); const schema = z.object({ a: field }); expectTypeOf>().toEqualTypeOf(); expectTypeOf>().toEqualTypeOf<{ a: string | undefined }>(); expect(z.parse(schema, {})).toStrictEqual({ a: undefined }); expect(z.parse(schema, { a: undefined })).toStrictEqual({ a: undefined }); expect(z.parse(schema, { a: "value" })).toStrictEqual({ a: "value" }); expect(z.safeParse(schema, { a: 123 }).success).toBe(false); expect(await z.parseAsync(schema, {})).toStrictEqual({ a: undefined }); expect(z.parse(z.object({ a: z.prefault(z.optional(z.string()), undefined) }), {})).toStrictEqual({ a: undefined }); const transformed = z.prefault( z.pipe( z.string(), z.transform(() => undefined) ), "fallback" ); expectTypeOf>().toEqualTypeOf(); expect(z.parse(z.object({ a: transformed }), {})).toStrictEqual({ a: undefined }); }); test("z.undefined", () => { const a = z.undefined(); expect(z.parse(a, undefined)).toEqual(undefined); expect(() => z.parse(a, "undefined")).toThrow(); }); test("z.null", () => { const a = z.null(); expect(z.parse(a, null)).toEqual(null); expect(() => z.parse(a, "null")).toThrow(); }); test("z.any", () => { const a = z.any(); expect(z.parse(a, "hello")).toEqual("hello"); expect(z.parse(a, 123)).toEqual(123); expect(z.parse(a, true)).toEqual(true); expect(z.parse(a, null)).toEqual(null); expect(z.parse(a, undefined)).toEqual(undefined); z.parse(a, {}); z.parse(a, []); z.parse(a, Symbol()); z.parse(a, new Date()); }); test("z.unknown", () => { const a = z.unknown(); expect(z.parse(a, "hello")).toEqual("hello"); expect(z.parse(a, 123)).toEqual(123); expect(z.parse(a, true)).toEqual(true); expect(z.parse(a, null)).toEqual(null); expect(z.parse(a, undefined)).toEqual(undefined); z.parse(a, {}); z.parse(a, []); z.parse(a, Symbol()); z.parse(a, new Date()); }); test("z.never", () => { const a = z.never(); expect(() => z.parse(a, "hello")).toThrow(); }); test("z.void", () => { const a = z.void(); expect(z.parse(a, undefined)).toEqual(undefined); expect(() => z.parse(a, null)).toThrow(); }); test("z.array", () => { const a = z.array(z.string()); expect(z.parse(a, ["hello", "world"])).toEqual(["hello", "world"]); expect(() => z.parse(a, [123])).toThrow(); expect(() => z.parse(a, "hello")).toThrow(); }); test("z.union", () => { const a = z.union([z.string(), z.number()]); expect(z.parse(a, "hello")).toEqual("hello"); expect(z.parse(a, 123)).toEqual(123); expect(() => z.parse(a, true)).toThrow(); }); test("z.union([]) / z.xor([]) / z.discriminatedUnion(_, []) construct and reject all input", () => { for (const schema of [z.union([]), z.xor([])]) { const r = schema.safeParse("anything"); expect(r.success).toEqual(false); if (!r.success) { expect(r.error.issues[0].code).toBe("invalid_union"); expect((r.error.issues[0] as any).errors).toEqual([]); } } const disc = z.discriminatedUnion("type", [] as any); const r = disc.safeParse({ type: "x" }); expect(r.success).toEqual(false); if (!r.success) { expect(r.error.issues[0].code).toBe("invalid_union"); expect((r.error.issues[0] as any).errors).toEqual([]); expect((r.error.issues[0] as any).options).toEqual([]); } }); test("z.discriminatedUnion rejects object options missing the discriminator", () => { expect(() => z.discriminatedUnion("type", [z.object({ value: z.string() })])).toThrow( /Invalid discriminated union option at index "0"/ ); // An option whose shape cannot be listed without resolving it is left to the lookup map, on the first object parsed. const viaPipe = z.discriminatedUnion("type", [ z.pipe(z.object({ value: z.literal("x") }), z.object({ value: z.literal("x") })), ]); expect(() => z.safeParse(viaPipe, { value: "x" })).toThrow(/Invalid discriminated union option at index "0"/); }); test("z.discriminatedUnion infers mutually-recursive getter options", () => { const variantA = z.object({ kind: z.literal("a"), get child() { return z.optional(tree); }, }); const variantB = z.object({ kind: z.literal("b"), get sibling() { return z.optional(tree); }, }); const tree = z.discriminatedUnion("kind", [variantA, variantB]); type _Tree = { kind: "a"; child?: _Tree | undefined } | { kind: "b"; sibling?: _Tree | undefined }; expectTypeOf>().toEqualTypeOf<_Tree>(); expectTypeOf>().not.toBeAny(); expect(z.parse(tree, { kind: "a", child: { kind: "b" } })).toEqual({ kind: "a", child: { kind: "b" } }); expect(() => z.parse(tree, { kind: "c" })).toThrow(); }); test("z.intersection", () => { const a = z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() })); expect(z.parse(a, { a: "hello", b: 123 })).toEqual({ a: "hello", b: 123 }); expect(() => z.parse(a, { a: "hello" })).toThrow(); expect(() => z.parse(a, { b: 123 })).toThrow(); expect(() => z.parse(a, "hello")).toThrow(); }); test("z.tuple", () => { const a = z.tuple([z.string(), z.number()]); expect(z.parse(a, ["hello", 123])).toEqual(["hello", 123]); expect(() => z.parse(a, ["hello", "world"])).toThrow(); expect(() => z.parse(a, [123, 456])).toThrow(); expect(() => z.parse(a, "hello")).toThrow(); // tuple with rest const b = z.tuple([z.string(), z.number(), z.optional(z.string())], z.boolean()); type b = z.output; expectTypeOf().toEqualTypeOf<[string, number, (string | undefined)?, ...boolean[]]>(); const datas = [ ["hello", 123], ["hello", 123, "world"], ["hello", 123, "world", true], ["hello", 123, "world", true, false, true], ]; for (const data of datas) { expect(z.parse(b, data)).toEqual(data); } expect(() => z.parse(b, ["hello", 123, 123])).toThrow(); expect(() => z.parse(b, ["hello", 123, "world", 123])).toThrow(); // tuple with readonly args const cArgs = [z.string(), z.number(), z.optional(z.string())] as const; const c = z.tuple(cArgs, z.boolean()); type c = z.output; expectTypeOf().toEqualTypeOf<[string, number, (string | undefined)?, ...boolean[]]>(); }); test("z.record", () => { // record schema with enum keys const a = z.record(z.string(), z.string()); type a = z.output; expectTypeOf().toEqualTypeOf>(); const b = z.record(z.union([z.string(), z.number(), z.symbol()]), z.string()); type b = z.output; expectTypeOf().toEqualTypeOf>(); expect(z.parse(b, { a: "hello", 1: "world", [Symbol.for("asdf")]: "symbol" })).toEqual({ a: "hello", 1: "world", [Symbol.for("asdf")]: "symbol", }); // enum keys const c = z.record(z.enum(["a", "b", "c"]), z.string()); type c = z.output; expectTypeOf().toEqualTypeOf>(); expect(z.parse(c, { a: "hello", b: "world", c: "world" })).toEqual({ a: "hello", b: "world", c: "world", }); // missing keys expect(() => z.parse(c, { a: "hello", b: "world" })).toThrow(); // extra keys expect(() => z.parse(c, { a: "hello", b: "world", c: "world", d: "world" })).toThrow(); // literal union keys const d = z.record(z.union([z.literal("a"), z.literal(0)]), z.string()); type d = z.output; expectTypeOf().toEqualTypeOf>(); expect(z.parse(d, { a: "hello", 0: "world" })).toEqual({ a: "hello", 0: "world", }); // TypeScript enum keys enum Enum { A = 0, B = "hi", } const e = z.record(z.enum(Enum), z.string()); type e = z.output; expectTypeOf().toEqualTypeOf>(); expect(z.parse(e, { [Enum.A]: "hello", [Enum.B]: "world" })).toEqual({ [Enum.A]: "hello", [Enum.B]: "world", }); const partial = z.partialRecord(z.enum(["__proto__", "b"]), z.string()); type partial = z.output; expectTypeOf().toEqualTypeOf>>(); const parsed: any = z.parse(partial, Object.fromEntries([["__proto__", "declared"]])); expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype); expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(false); expect(parsed).toEqual({}); expect(z.parse(partial, {})).toEqual({}); // v3-compat single-arg form: z.record(valueType) defaults keyType to z.string() const f = (z.record as any)(z.number()); expect(f._zod.def.keyType._zod.def.type).toEqual("string"); expect(f._zod.def.valueType._zod.def.type).toEqual("number"); expect(z.parse(f, { a: 1, b: 2 })).toEqual({ a: 1, b: 2 }); }); test("z.map", () => { const a = z.map(z.string(), z.number()); type a = z.output; expectTypeOf().toEqualTypeOf>(); expect(z.parse(a, new Map([["hello", 123]]))).toEqual(new Map([["hello", 123]])); expect(() => z.parse(a, new Map([["hello", "world"]]))).toThrow(); expect(() => z.parse(a, new Map([[1243, "world"]]))).toThrow(); expect(() => z.parse(a, "hello")).toThrow(); const r1 = z.safeParse(a, new Map([[123, 123]])); expect(r1.error?.issues[0].code).toEqual("invalid_type"); expect(r1.error?.issues[0].path).toEqual([123]); const r2: any = z.safeParse(a, new Map([[BigInt(123), 123]])); expect(r2.error!.issues[0].code).toEqual("invalid_key"); expect(r2.error!.issues[0].path).toEqual([]); const r3: any = z.safeParse(a, new Map([["hello", "world"]])); expect(r3.error!.issues[0].code).toEqual("invalid_type"); expect(r3.error!.issues[0].path).toEqual(["hello"]); }); test("z.map invalid_element", () => { const a = z.map(z.bigint(), z.number()); const r1 = z.safeParse(a, new Map([[BigInt(123), BigInt(123)]])); expect(r1.error!.issues[0].code).toEqual("invalid_element"); expect(r1.error!.issues[0].path).toEqual([]); }); test("z.map async", async () => { const a = z.map(z.string().check(z.refine(async () => true)), z.number().check(z.refine(async () => true))); const d1 = new Map([["hello", 123]]); expect(await z.parseAsync(a, d1)).toEqual(d1); await expect(z.parseAsync(a, new Map([[123, 123]]))).rejects.toThrow(); await expect(z.parseAsync(a, new Map([["hi", "world"]]))).rejects.toThrow(); await expect(z.parseAsync(a, new Map([[1243, "world"]]))).rejects.toThrow(); await expect(z.parseAsync(a, "hello")).rejects.toThrow(); const r = await z.safeParseAsync(a, new Map([[123, 123]])); expect(r.success).toEqual(false); expect(r.error!.issues[0].code).toEqual("invalid_type"); expect(r.error!.issues[0].path).toEqual([123]); }); test("z.set", () => { const a = z.set(z.string()); type a = z.output; expectTypeOf().toEqualTypeOf>(); expect(z.parse(a, new Set(["hello", "world"]))).toEqual(new Set(["hello", "world"])); expect(() => z.parse(a, new Set([123]))).toThrow(); expect(() => z.parse(a, ["hello", "world"])).toThrow(); expect(() => z.parse(a, "hello")).toThrow(); const b = z.set(z.number()); expect(z.parse(b, new Set([1, 2, 3]))).toEqual(new Set([1, 2, 3])); expect(() => z.parse(b, new Set(["hello"]))).toThrow(); expect(() => z.parse(b, [1, 2, 3])).toThrow(); expect(() => z.parse(b, 123)).toThrow(); }); test("z.enum", () => { const a = z.enum(["A", "B", "C"]); type a = z.output; expectTypeOf().toEqualTypeOf<"A" | "B" | "C">(); expect(z.parse(a, "A")).toEqual("A"); expect(z.parse(a, "B")).toEqual("B"); expect(z.parse(a, "C")).toEqual("C"); expect(() => z.parse(a, "D")).toThrow(); expect(() => z.parse(a, 123)).toThrow(); // expect(a.enum.A).toEqual("A"); // expect(a.enum.B).toEqual("B"); // expect(a.enum.C).toEqual("C"); // expect((a.enum as any).D).toEqual(undefined); }); test("z.enum - native", () => { enum NativeEnum { A = "A", B = "B", C = "C", } const a = z.enum(NativeEnum); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, NativeEnum.A)).toEqual(NativeEnum.A); expect(z.parse(a, NativeEnum.B)).toEqual(NativeEnum.B); expect(z.parse(a, NativeEnum.C)).toEqual(NativeEnum.C); expect(() => z.parse(a, "D")).toThrow(); expect(() => z.parse(a, 123)).toThrow(); // test a.enum a; // expect(a.enum.A).toEqual(NativeEnum.A); // expect(a.enum.B).toEqual(NativeEnum.B); // expect(a.enum.C).toEqual(NativeEnum.C); enum NumericEnum { A = 0, B = 1, } // numeric enums carry reverse-mapping keys; options lists only what parse accepts expect(z.enum(NumericEnum).options).toEqual([NumericEnum.A, NumericEnum.B]); }); test("z.nativeEnum", () => { enum NativeEnum { A = "A", B = "B", C = "C", } const a = z.nativeEnum(NativeEnum); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, NativeEnum.A)).toEqual(NativeEnum.A); expect(z.parse(a, NativeEnum.B)).toEqual(NativeEnum.B); expect(z.parse(a, NativeEnum.C)).toEqual(NativeEnum.C); expect(() => z.parse(a, "D")).toThrow(); expect(() => z.parse(a, 123)).toThrow(); // test a.enum a; // expect(a.enum.A).toEqual(NativeEnum.A); // expect(a.enum.B).toEqual(NativeEnum.B); // expect(a.enum.C).toEqual(NativeEnum.C); }); test("z.literal", () => { const a = z.literal("hello"); type a = z.output; expectTypeOf().toEqualTypeOf<"hello">(); expect(z.parse(a, "hello")).toEqual("hello"); expect(() => z.parse(a, "world")).toThrow(); expect(() => z.parse(a, 123)).toThrow(); z.literal(["adf"] as const); }); test("z.file", () => { const a = z.file(); const file = new File(["content"], "filename.txt", { type: "text/plain" }); expect(z.parse(a, file)).toEqual(file); expect(() => z.parse(a, "file")).toThrow(); expect(() => z.parse(a, 123)).toThrow(); }); test("z.transform", () => { const a = z.pipe( z.string(), z.transform((val) => val.toUpperCase()) ); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, "hello")).toEqual("HELLO"); expect(() => z.parse(a, 123)).toThrow(); }); test("z.transform async", async () => { const a = z.pipe( z.string(), z.transform(async (val) => val.toUpperCase()) ); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(await z.parseAsync(a, "hello")).toEqual("HELLO"); await expect(() => z.parseAsync(a, 123)).rejects.toThrow(); }); test("z.preprocess", () => { const a = z.pipe( z.transform((val) => String(val).toUpperCase()), z.string() ); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, 123)).toEqual("123"); expect(z.parse(a, true)).toEqual("TRUE"); expect(z.parse(a, BigInt(1234))).toEqual("1234"); // expect(() => z.parse(a, Symbol("asdf"))).toThrow(); }); // test("z.preprocess async", () => { // const a = z.preprocess(async (val) => String(val), z.string()); // type a = z.output; // expectTypeOf().toEqualTypeOf(); // expect(z.parse(a, 123)).toEqual("123"); // expect(z.parse(a, true)).toEqual("true"); // expect(() => z.parse(a, {})).toThrow(); // }); test("z.optional", () => { const a = z.optional(z.string()); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, "hello")).toEqual("hello"); expect(z.parse(a, undefined)).toEqual(undefined); expect(() => z.parse(a, 123)).toThrow(); }); test("z.nullable", () => { const a = z.nullable(z.string()); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, "hello")).toEqual("hello"); expect(z.parse(a, null)).toEqual(null); expect(() => z.parse(a, 123)).toThrow(); }); test("z.default", () => { const a = z._default(z.string(), "default"); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, undefined)).toEqual("default"); expect(z.parse(a, "hello")).toEqual("hello"); expect(() => z.parse(a, 123)).toThrow(); const b = z._default(z.string(), () => "default"); expect(z.parse(b, undefined)).toEqual("default"); expect(z.parse(b, "hello")).toEqual("hello"); expect(() => z.parse(b, 123)).toThrow(); }); test("z.catch", () => { const a = z.catch(z.string(), "default"); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, "hello")).toEqual("hello"); expect(z.parse(a, 123)).toEqual("default"); const b = z.catch(z.string(), () => "default"); expect(z.parse(b, "hello")).toEqual("hello"); expect(z.parse(b, 123)).toEqual("default"); const c = z.catch(z.string(), (ctx) => { return `${ctx.error.issues.length}issues`; }); expect(z.parse(c, 1234)).toEqual("1issues"); }); test("z.nan", () => { const a = z.nan(); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, Number.NaN)).toEqual(Number.NaN); expect(() => z.parse(a, 123)).toThrow(); expect(() => z.parse(a, "NaN")).toThrow(); }); test("z.pipe", () => { const a = z.pipe( z.pipe( z.string(), z.transform((val) => val.length) ), z.number() ); type a_in = z.input; expectTypeOf().toEqualTypeOf(); type a_out = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, "123")).toEqual(3); expect(z.parse(a, "hello")).toEqual(5); expect(() => z.parse(a, 123)).toThrow(); }); test("z.readonly", () => { const a = z.readonly(z.string()); type a = z.output; expectTypeOf().toEqualTypeOf>(); expect(z.parse(a, "hello")).toEqual("hello"); expect(() => z.parse(a, 123)).toThrow(); }); test("z.templateLiteral", () => { const a = z.templateLiteral([z.string(), z.number()]); type a = z.output; expectTypeOf().toEqualTypeOf<`${string}${number}`>(); expect(z.parse(a, "hello123")).toEqual("hello123"); expect(() => z.parse(a, "hello")).toThrow(); expect(() => z.parse(a, 123)).toThrow(); // multipart const b = z.templateLiteral([z.string(), z.number(), z.string()]); type b = z.output; expectTypeOf().toEqualTypeOf<`${string}${number}${string}`>(); expect(z.parse(b, "hello123world")).toEqual("hello123world"); expect(z.parse(b, "123")).toEqual("123"); expect(() => z.parse(b, "hello")).toThrow(); expect(() => z.parse(b, 123)).toThrow(); // include boolean const c = z.templateLiteral([z.string(), z.boolean()]); type c = z.output; expectTypeOf().toEqualTypeOf<`${string}${boolean}`>(); expect(z.parse(c, "hellotrue")).toEqual("hellotrue"); expect(z.parse(c, "hellofalse")).toEqual("hellofalse"); expect(() => z.parse(c, "hello")).toThrow(); expect(() => z.parse(c, 123)).toThrow(); // include literal prefix const d = z.templateLiteral([z.literal("hello"), z.number()]); type d = z.output; expectTypeOf().toEqualTypeOf<`hello${number}`>(); expect(z.parse(d, "hello123")).toEqual("hello123"); expect(() => z.parse(d, 123)).toThrow(); expect(() => z.parse(d, "world123")).toThrow(); // include literal union const e = z.templateLiteral([z.literal(["aa", "bb"]), z.number()]); type e = z.output; expectTypeOf().toEqualTypeOf<`aa${number}` | `bb${number}`>(); expect(z.parse(e, "aa123")).toEqual("aa123"); expect(z.parse(e, "bb123")).toEqual("bb123"); expect(() => z.parse(e, "cc123")).toThrow(); expect(() => z.parse(e, 123)).toThrow(); }); // this returns both a schema and a check test("z.custom", () => { const a = z.custom((val) => { return typeof val === "string"; }); expect(z.parse(a, "hello")).toEqual("hello"); expect(() => z.parse(a, 123)).toThrow(); const b = z.string().check(z.custom((val) => val.length > 3)); expect(z.parse(b, "hello")).toEqual("hello"); expect(() => z.parse(b, "hi")).toThrow(); }); test("z.check", () => { // this is a more flexible version of z.custom that accepts an arbitrary _parse logic the function should return core.$ZodResult const a = z.any().check( z.check((ctx) => { if (typeof ctx.value === "string") return; ctx.issues.push({ code: "custom", origin: "custom", message: "Expected a string", input: ctx.value, }); }) ); expect(z.safeParse(a, "hello")).toMatchObject({ success: true, data: "hello", }); expect(z.safeParse(a, 123)).toMatchObject({ success: false, error: { issues: [{ code: "custom", message: "Expected a string" }] }, }); }); test("z.with (alias for z.check)", () => { // .with() should work exactly the same as .check() const a = z.any().with( z.check((ctx) => { if (typeof ctx.value === "string") return; ctx.issues.push({ code: "custom", origin: "custom", message: "Expected a string", input: ctx.value, }); }) ); expect(z.safeParse(a, "hello")).toMatchObject({ success: true, data: "hello", }); expect(z.safeParse(a, 123)).toMatchObject({ success: false, error: { issues: [{ code: "custom", message: "Expected a string" }] }, }); // Test with refine const b = z.string().with(z.refine((val) => val.length > 3, "Must be longer than 3")); expect(z.safeParse(b, "hello").success).toBe(true); expect(z.safeParse(b, "hi").success).toBe(false); // Test with function const c = z.string().with(({ value, issues }) => { if (value.length <= 3) { issues.push({ code: "custom", input: value, message: "Must be longer than 3", }); } }); expect(z.safeParse(c, "hello").success).toBe(true); expect(z.safeParse(c, "hi").success).toBe(false); }); test("z.instanceof", () => { class A {} const a = z.instanceof(A); expect(z.parse(a, new A())).toBeInstanceOf(A); expect(() => z.parse(a, {})).toThrow(); }); test("z.refine", () => { const a = z.number().check( z.refine((val) => val > 3), z.refine((val) => val < 10) ); expect(z.parse(a, 5)).toEqual(5); expect(() => z.parse(a, 2)).toThrow(); expect(() => z.parse(a, 11)).toThrow(); expect(() => z.parse(a, "hi")).toThrow(); }); // test("z.superRefine", () => { // const a = z.number([ // z.superRefine((val, ctx) => { // if (val < 3) { // return ctx.addIssue({ // code: "custom", // origin: "custom", // message: "Too small", // input: val, // }); // } // if (val > 10) { // return ctx.addIssue("Too big"); // } // }), // ]); // expect(z.parse(a, 5)).toEqual(5); // expect(() => z.parse(a, 2)).toThrow(); // expect(() => z.parse(a, 11)).toThrow(); // expect(() => z.parse(a, "hi")).toThrow(); // }); test("z.transform", () => { const a = z.transform((val: number) => { return `${val}`; }); type a_in = z.input; expectTypeOf().toEqualTypeOf(); type a_out = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, 123)).toEqual("123"); }); test("z.$brand()", () => { const a = z.string().brand<"my-brand">(); type a = z.output; const branded = (_: a) => {}; // @ts-expect-error branded("asdf"); }); test("z.lazy", () => { const a = z.lazy(() => z.string()); type a = z.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, "hello")).toEqual("hello"); expect(() => z.parse(a, 123)).toThrow(); }); // schema that validates JSON-like data test("z.json", () => { const a = z.json(); type a = z.output; a._zod.output; expectTypeOf().toEqualTypeOf(); expect(z.parse(a, "hello")).toEqual("hello"); expect(z.parse(a, 123)).toEqual(123); expect(z.parse(a, true)).toEqual(true); expect(z.parse(a, null)).toEqual(null); expect(z.parse(a, {})).toEqual({}); expect(z.parse(a, { a: "hello" })).toEqual({ a: "hello" }); expect(z.parse(a, [1, 2, 3])).toEqual([1, 2, 3]); expect(z.parse(a, [{ a: "hello" }])).toEqual([{ a: "hello" }]); // fail cases expect(() => z.parse(a, new Date())).toThrow(); expect(() => z.parse(a, Symbol())).toThrow(); expect(() => z.parse(a, { a: new Date() })).toThrow(); expect(() => z.parse(a, undefined)).toThrow(); expect(() => z.parse(a, { a: undefined })).toThrow(); }); test("z.stringbool", () => { const a = z.stringbool(); expect(z.parse(a, "true")).toEqual(true); expect(z.parse(a, "yes")).toEqual(true); expect(z.parse(a, "1")).toEqual(true); expect(z.parse(a, "on")).toEqual(true); expect(z.parse(a, "y")).toEqual(true); expect(z.parse(a, "enabled")).toEqual(true); expect(z.parse(a, "TRUE")).toEqual(true); expect(z.parse(a, "false")).toEqual(false); expect(z.parse(a, "no")).toEqual(false); expect(z.parse(a, "0")).toEqual(false); expect(z.parse(a, "off")).toEqual(false); expect(z.parse(a, "n")).toEqual(false); expect(z.parse(a, "disabled")).toEqual(false); expect(z.parse(a, "FALSE")).toEqual(false); expect(z.safeParse(a, "other")).toMatchObject({ success: false }); expect(z.safeParse(a, "")).toMatchObject({ success: false }); expect(z.safeParse(a, undefined)).toMatchObject({ success: false }); expect(z.safeParse(a, {})).toMatchObject({ success: false }); expect(z.safeParse(a, true)).toMatchObject({ success: false }); expect(z.safeParse(a, false)).toMatchObject({ success: false }); const b = z.stringbool({ truthy: ["y"], falsy: ["n"], }); expect(z.parse(b, "y")).toEqual(true); expect(z.parse(b, "n")).toEqual(false); expect(z.safeParse(b, "true")).toMatchObject({ success: false }); expect(z.safeParse(b, "false")).toMatchObject({ success: false }); expect(b._zod.bag.truthy).toEqual(["y"]); expect(b._zod.bag.falsy).toEqual(["n"]); const c = z.stringbool({ case: "sensitive", }); expect(z.parse(c, "true")).toEqual(true); expect(z.safeParse(c, "TRUE")).toMatchObject({ success: false }); expect(c._zod.bag.case).toEqual("sensitive"); }); // promise test("z.promise", async () => { const a = z.promise(z.string()); type a = z.output; expectTypeOf().toEqualTypeOf>(); expect(await z.safeParseAsync(a, Promise.resolve("hello"))).toMatchObject({ success: true, data: "hello", }); expect(await z.safeParseAsync(a, Promise.resolve(123))).toMatchObject({ success: false, }); const b = z.string(); expect(() => z.parse(b, Promise.resolve("hello"))).toThrow(); }); // test("type assertions", () => { // const schema = z.pipe( // z.string(), // z.transform((val) => val.length) // ); // schema.assertInput(); // // @ts-expect-error // schema.assertInput(); // schema.assertOutput(); // // @ts-expect-error // schema.assertOutput(); // }); test("z.pipe type enforcement", () => { z.pipe( z.pipe( z.string().check(z.regex(/asdf/)), z.transform((v) => new Date(v)) ), z.date().check(z.maximum(new Date())) ); }); test("def typing", () => { z.string().def.type satisfies "string"; z.email().def.format satisfies "email"; z.number().def.type satisfies "number"; z.float64().def.format satisfies z.core.$ZodNumberFormats; z.bigint().def.type satisfies "bigint"; z.boolean().def.type satisfies "boolean"; z.date().def.type satisfies "date"; z.symbol().def.type satisfies "symbol"; z.undefined().def.type satisfies "undefined"; z.nullable(z.string()).def.type satisfies "nullable"; z.null().def.type satisfies "null"; z.any().def.type satisfies "any"; z.unknown().def.type satisfies "unknown"; z.never().def.type satisfies "never"; z.void().def.type satisfies "void"; z.array(z.string()).def.type satisfies "array"; z.object({ key: z.string() }).def.type satisfies "object"; z.union([z.string(), z.number()]).def.type satisfies "union"; z.intersection(z.string(), z.number()).def.type satisfies "intersection"; z.tuple([z.string(), z.number()]).def.type satisfies "tuple"; z.record(z.string(), z.number()).def.type satisfies "record"; z.map(z.string(), z.number()).def.type satisfies "map"; z.set(z.string()).def.type satisfies "set"; z.literal("example").def.type satisfies "literal"; expectTypeOf(z.literal("example").def.values).toEqualTypeOf<"example"[]>(); z.enum(["a", "b", "c"]).def.type satisfies "enum"; z.promise(z.string()).def.type satisfies "promise"; z.lazy(() => z.string()).def.type satisfies "lazy"; z.optional(z.string()).def.type satisfies "optional"; z._default(z.string(), "default").def.type satisfies "default"; z.templateLiteral([z.literal("a"), z.literal("b")]).def.type satisfies "template_literal"; z.custom((val) => typeof val === "string").def.type satisfies "custom"; z.transform((val) => val as string).def.type satisfies "transform"; z.nonoptional(z.string()).def.type satisfies "nonoptional"; z.readonly(z.unknown()).def.type satisfies "readonly"; z.nan().def.type satisfies "nan"; z.pipe(z.unknown(), z.number()).def.type satisfies "pipe"; z.success(z.string()).def.type satisfies "success"; z.catch(z.string(), "fallback").def.type satisfies "catch"; z.file().def.type satisfies "file"; }); test("defaulted object schema returns shallow clone", () => { const schema = z._default( z.object({ a: z.string(), }), { a: "x" } ); const result1 = schema.parse(undefined); const result2 = schema.parse(undefined); expect(result1).not.toBe(result2); expect(result1).toEqual(result2); }); test("runtime type property exists and returns correct values", () => { const stringSchema = z.string(); expect(stringSchema.type).toBe("string"); }); test("type narrowing works with type property", () => { type ArrayOrRecord = z.ZodMiniArray | z.ZodMiniRecord, z.ZodMiniAny>; const arraySchema = z.array(z.string()) as ArrayOrRecord; if (arraySchema.type === "array") { expectTypeOf(arraySchema).toEqualTypeOf>>(); expect(arraySchema.def.element).toBeDefined(); } }); test("getDiscriminatedOption", () => { const a = z.object({ type: z.literal("a"), x: z.string() }); const b = z.object({ type: z.literal("b"), y: z.number() }); const schema = z.discriminatedUnion("type", [a, b]); expect(z.getDiscriminatedOption(schema, "a")).toBe(a); expect(z.getDiscriminatedOption(schema, "b")).toBe(b); expectTypeOf(z.getDiscriminatedOption(schema, "a")).toEqualTypeOf(); }); test("z.partial on a tuple", () => { const schema = z.partial(z.tuple([z.string(), z.number()])); expectTypeOf>().toEqualTypeOf<[(string | undefined)?, (number | undefined)?]>(); expect(z.safeParse(schema, []).success).toEqual(true); expect(z.safeParse(schema, ["a"]).success).toEqual(true); expect(z.safeParse(schema, ["a", 1]).success).toEqual(true); expect(z.safeParse(schema, ["a", "b"]).success).toEqual(false); const refined = z.tuple([z.string(), z.number()]).check(z.refine(([a]) => a.length > 0)); expect(() => z.partial(refined)).toThrow("cannot be used on tuple schemas containing refinements"); });