1147 lines
38 KiB
Plaintext
1147 lines
38 KiB
Plaintext
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<z.output<typeof string>>().toEqualTypeOf<string>();
|
|
expectTypeOf<z.output<typeof number>>().toEqualTypeOf<number>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<boolean>();
|
|
});
|
|
|
|
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<z.output<typeof field>>().toEqualTypeOf<string | undefined>();
|
|
expectTypeOf<z.output<typeof schema>>().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<z.output<typeof transformed>>().toEqualTypeOf<undefined>();
|
|
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<z.input<typeof tree>>().toEqualTypeOf<_Tree>();
|
|
expectTypeOf<z.input<typeof tree>>().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<typeof b>;
|
|
|
|
expectTypeOf<b>().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<typeof c>;
|
|
expectTypeOf<c>().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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<Record<string, string>>();
|
|
|
|
const b = z.record(z.union([z.string(), z.number(), z.symbol()]), z.string());
|
|
type b = z.output<typeof b>;
|
|
expectTypeOf<b>().toEqualTypeOf<Record<string | number | symbol, string>>();
|
|
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<typeof c>;
|
|
expectTypeOf<c>().toEqualTypeOf<Record<"a" | "b" | "c", string>>();
|
|
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<typeof d>;
|
|
expectTypeOf<d>().toEqualTypeOf<Record<"a" | 0, string>>();
|
|
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<typeof e>;
|
|
expectTypeOf<e>().toEqualTypeOf<Record<Enum, string>>();
|
|
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<typeof partial>;
|
|
expectTypeOf<partial>().toEqualTypeOf<Partial<Record<"__proto__" | "b", string>>>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<Map<string, number>>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<Set<string>>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<NativeEnum>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<NativeEnum>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string>();
|
|
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<typeof a>;
|
|
// expectTypeOf<a>().toEqualTypeOf<string>();
|
|
// 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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string | undefined>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string | null>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<number>();
|
|
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<typeof a>;
|
|
expectTypeOf<a_in>().toEqualTypeOf<string>();
|
|
type a_out = z.output<typeof a>;
|
|
expectTypeOf<a_out>().toEqualTypeOf<number>();
|
|
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<Readonly<string>>();
|
|
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<typeof a>;
|
|
expectTypeOf<a>().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<typeof b>;
|
|
expectTypeOf<b>().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<typeof c>;
|
|
expectTypeOf<c>().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<typeof d>;
|
|
expectTypeOf<d>().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<typeof e>;
|
|
expectTypeOf<e>().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<string>((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<string>((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<typeof a>;
|
|
expectTypeOf<a_in>().toEqualTypeOf<number>();
|
|
type a_out = z.output<typeof a>;
|
|
expectTypeOf<a_out>().toEqualTypeOf<string>();
|
|
expect(z.parse(a, 123)).toEqual("123");
|
|
});
|
|
|
|
test("z.$brand()", () => {
|
|
const a = z.string().brand<"my-brand">();
|
|
type a = z.output<typeof a>;
|
|
const branded = (_: a) => {};
|
|
// @ts-expect-error
|
|
branded("asdf");
|
|
});
|
|
|
|
test("z.lazy", () => {
|
|
const a = z.lazy(() => z.string());
|
|
type a = z.output<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<string>();
|
|
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<typeof a>;
|
|
a._zod.output;
|
|
|
|
expectTypeOf<a>().toEqualTypeOf<util.JSONType>();
|
|
|
|
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<typeof a>;
|
|
expectTypeOf<a>().toEqualTypeOf<Promise<string>>();
|
|
|
|
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<string>();
|
|
// // @ts-expect-error
|
|
// schema.assertInput<number>();
|
|
|
|
// schema.assertOutput<number>();
|
|
// // @ts-expect-error
|
|
// schema.assertOutput<string>();
|
|
// });
|
|
|
|
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<string>((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.ZodMiniString> | z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniAny>;
|
|
const arraySchema = z.array(z.string()) as ArrayOrRecord;
|
|
|
|
if (arraySchema.type === "array") {
|
|
expectTypeOf(arraySchema).toEqualTypeOf<z.ZodMiniArray<z.ZodMiniString<unknown>>>();
|
|
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<typeof a>();
|
|
});
|
|
|
|
test("z.partial on a tuple", () => {
|
|
const schema = z.partial(z.tuple([z.string(), z.number()]));
|
|
expectTypeOf<z.infer<typeof schema>>().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");
|
|
});
|