v1.0 with SW PWA enabled

This commit is contained in:
Blomios
2026-01-01 17:40:53 +01:00
parent 1c0e22aac1
commit 3c8bebb2ad
29775 changed files with 2197201 additions and 119080 deletions

View File

@ -0,0 +1,26 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("check any inference", () => {
const t1 = z.any();
t1.optional();
t1.nullable();
type t1 = z.infer<typeof t1>;
expectTypeOf<t1>().toEqualTypeOf<any>();
});
test("check unknown inference", () => {
const t1 = z.unknown();
t1.optional();
t1.nullable();
type t1 = z.infer<typeof t1>;
expectTypeOf<t1>().toEqualTypeOf<unknown>();
});
test("check never inference", () => {
const t1 = z.never();
expect(() => t1.parse(undefined)).toThrow();
expect(() => t1.parse("asdf")).toThrow();
expect(() => t1.parse(null)).toThrow();
});

View File

@ -0,0 +1,264 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("type inference", () => {
const schema = z.string().array();
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<string[]>();
});
test("array min/max", async () => {
const schema = z.array(z.string()).min(2).max(2);
const r1 = await schema.safeParse(["asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected array to have >=2 items",
"minimum": 2,
"origin": "array",
"path": [],
},
]
`);
const r2 = await schema.safeParse(["asdf", "asdf", "asdf"]);
expect(r2.success).toEqual(false);
expect(r2.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 2,
"message": "Too big: expected array to have <=2 items",
"origin": "array",
"path": [],
},
]
`);
});
test("array length", async () => {
const schema = z.array(z.string()).length(2);
schema.parse(["asdf", "asdf"]);
const r1 = await schema.safeParse(["asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"exact": true,
"inclusive": true,
"message": "Too small: expected array to have >=2 items",
"minimum": 2,
"origin": "array",
"path": [],
},
]
`);
const r2 = await schema.safeParse(["asdf", "asdf", "asdf"]);
expect(r2.success).toEqual(false);
expect(r2.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"exact": true,
"inclusive": true,
"maximum": 2,
"message": "Too big: expected array to have <=2 items",
"origin": "array",
"path": [],
},
]
`);
});
test("array.nonempty()", () => {
const schema = z.string().array().nonempty();
schema.parse(["a"]);
expect(() => schema.parse([])).toThrow();
});
test("array.nonempty().max()", () => {
const schema = z.string().array().nonempty().max(2);
schema.parse(["a"]);
expect(() => schema.parse([])).toThrow();
expect(() => schema.parse(["a", "a", "a"])).toThrow();
});
test("parse empty array in nonempty", () => {
expect(() =>
z
.array(z.string())
.nonempty()
.parse([] as any)
).toThrow();
});
test("get element", () => {
const schema = z.string().array();
schema.element.parse("asdf");
expect(() => schema.element.parse(12)).toThrow();
});
test("continue parsing despite array size error", () => {
const schema = z.object({
people: z.string().array().min(2),
});
const result = schema.safeParse({
people: [123],
});
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"people",
0
],
"message": "Invalid input: expected string, received number"
},
{
"origin": "array",
"code": "too_small",
"minimum": 2,
"inclusive": true,
"path": [
"people"
],
"message": "Too small: expected array to have >=2 items"
}
]],
"success": false,
}
`);
});
test("parse should fail given sparse array", () => {
const schema = z.array(z.string()).nonempty().min(1).max(3);
const result = schema.safeParse(new Array(3));
expect(result.success).toEqual(false);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
0
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
1
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
2
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
// const unique = z.string().array().unique();
// const uniqueArrayOfObjects = z.array(z.object({ name: z.string() })).unique({ identifier: (item) => item.name });
// test("passing unique validation", () => {
// unique.parse(["a", "b", "c"]);
// uniqueArrayOfObjects.parse([{ name: "Leo" }, { name: "Joe" }]);
// });
// test("failing unique validation", () => {
// expect(() => unique.parse(["a", "a", "b"])).toThrow();
// expect(() => uniqueArrayOfObjects.parse([{ name: "Leo" }, { name: "Leo" }])).toThrow();
// });
// test("continue parsing despite array of primitives uniqueness error", () => {
// const schema = z.number().array().unique();
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Values must be unique");
// }
// });
// test("continue parsing despite array of objects not_unique error", () => {
// const schema = z.array(z.object({ name: z.string() })).unique({
// identifier: (item) => item.name,
// showDuplicates: true,
// });
// const result = schema.safeParse([
// { name: "Leo" },
// { name: "Joe" },
// { name: "Leo" },
// ]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Element(s): 'Leo' not unique");
// }
// });
// test("returns custom error message without duplicate elements", () => {
// const schema = z.number().array().unique({ message: "Custom message" });
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Custom message");
// }
// });
// test("returns error message with duplicate elements", () => {
// const schema = z.number().array().unique({ showDuplicates: true });
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Element(s): '1,2' not unique");
// }
// });
// test("returns custom error message with duplicate elements", () => {
// const schema = z
// .number()
// .array()
// .unique({
// message: (item) => `Custom message: '${item}' are not unique`,
// showDuplicates: true,
// });
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Custom message: '1,2' are not unique");
// }
// });

View File

@ -0,0 +1,210 @@
import { expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("assignability", () => {
// $ZodString
z.string() satisfies z.core.$ZodString;
z.string() satisfies z.ZodString;
// $ZodNumber
z.number() satisfies z.core.$ZodNumber;
z.number() satisfies z.ZodNumber;
// $ZodBigInt
z.bigint() satisfies z.core.$ZodBigInt;
z.bigint() satisfies z.ZodBigInt;
// $ZodBoolean
z.boolean() satisfies z.core.$ZodBoolean;
z.boolean() satisfies z.ZodBoolean;
// $ZodDate
z.date() satisfies z.core.$ZodDate;
z.date() satisfies z.ZodDate;
// $ZodSymbol
z.symbol() satisfies z.core.$ZodSymbol;
z.symbol() satisfies z.ZodSymbol;
// $ZodUndefined
z.undefined() satisfies z.core.$ZodUndefined;
z.undefined() satisfies z.ZodUndefined;
// $ZodNullable
z.string().nullable() satisfies z.core.$ZodNullable;
z.string().nullable() satisfies z.ZodNullable;
// $ZodNull
z.null() satisfies z.core.$ZodNull;
z.null() satisfies z.ZodNull;
// $ZodAny
z.any() satisfies z.core.$ZodAny;
z.any() satisfies z.ZodAny;
// $ZodUnknown
z.unknown() satisfies z.core.$ZodUnknown;
z.unknown() satisfies z.ZodUnknown;
// $ZodNever
z.never() satisfies z.core.$ZodNever;
z.never() satisfies z.ZodNever;
// $ZodVoid
z.void() satisfies z.core.$ZodVoid;
z.void() satisfies z.ZodVoid;
// $ZodArray
z.array(z.string()) satisfies z.core.$ZodArray;
z.array(z.string()) satisfies z.ZodArray;
z.array(z.string()) satisfies z.ZodType<Array<unknown>>;
// $ZodObject
z.object({ key: z.string() }) satisfies z.core.$ZodObject;
z.object({ key: z.string() }) satisfies z.ZodObject<{ key: z.ZodType }>;
z.object({ key: z.string() }) satisfies z.ZodType<{ key: string }>;
// $ZodUnion
z.union([z.string(), z.number()]) satisfies z.core.$ZodUnion;
z.union([z.string(), z.number()]) satisfies z.ZodUnion;
z.union([z.string(), z.number()]) satisfies z.ZodType<string | number>;
// $ZodIntersection
z.intersection(z.string(), z.number()) satisfies z.core.$ZodIntersection;
z.intersection(z.string(), z.number()) satisfies z.ZodIntersection;
// $ZodTuple
z.tuple([z.string(), z.number()]) satisfies z.core.$ZodTuple;
z.tuple([z.string(), z.number()]) satisfies z.ZodTuple;
// $ZodRecord
z.record(z.string(), z.number()) satisfies z.core.$ZodRecord;
z.record(z.string(), z.number()) satisfies z.ZodRecord;
// $ZodMap
z.map(z.string(), z.number()) satisfies z.core.$ZodMap;
z.map(z.string(), z.number()) satisfies z.ZodMap;
// $ZodSet
z.set(z.string()) satisfies z.core.$ZodSet;
z.set(z.string()) satisfies z.ZodSet;
// $ZodLiteral
z.literal("example") satisfies z.core.$ZodLiteral;
z.literal("example") satisfies z.ZodLiteral;
// $ZodEnum
z.enum(["a", "b", "c"]) satisfies z.core.$ZodEnum;
z.enum(["a", "b", "c"]) satisfies z.ZodEnum;
// $ZodPromise
z.promise(z.string()) satisfies z.core.$ZodPromise;
z.promise(z.string()) satisfies z.ZodPromise;
// $ZodLazy
const lazySchema = z.lazy(() => z.string());
lazySchema satisfies z.core.$ZodLazy;
lazySchema satisfies z.ZodLazy;
// $ZodOptional
z.string().optional() satisfies z.core.$ZodOptional;
z.string().optional() satisfies z.ZodOptional;
// $ZodDefault
z.string().default("default") satisfies z.core.$ZodDefault;
z.string().default("default") satisfies z.ZodDefault;
// $ZodTemplateLiteral
z.templateLiteral([z.literal("a"), z.literal("b")]) satisfies z.core.$ZodTemplateLiteral;
z.templateLiteral([z.literal("a"), z.literal("b")]) satisfies z.ZodTemplateLiteral;
// $ZodCustom
z.custom<string>((val) => typeof val === "string") satisfies z.core.$ZodCustom;
z.custom<string>((val) => typeof val === "string") satisfies z.ZodCustom;
// $ZodTransform
z.transform((val) => val as string) satisfies z.core.$ZodTransform;
z.transform((val) => val as string) satisfies z.ZodTransform;
// $ZodNonOptional
z.string().optional().nonoptional() satisfies z.core.$ZodNonOptional;
z.string().optional().nonoptional() satisfies z.ZodNonOptional;
// $ZodReadonly
z.object({ key: z.string() }).readonly() satisfies z.core.$ZodReadonly;
z.object({ key: z.string() }).readonly() satisfies z.ZodReadonly;
// $ZodNaN
z.nan() satisfies z.core.$ZodNaN;
z.nan() satisfies z.ZodNaN;
// $ZodPipe
z.unknown().pipe(z.number()) satisfies z.core.$ZodPipe;
z.unknown().pipe(z.number()) satisfies z.ZodPipe;
// $ZodSuccess
z.success(z.string()) satisfies z.core.$ZodSuccess;
z.success(z.string()) satisfies z.ZodSuccess;
// $ZodCatch
z.string().catch("fallback") satisfies z.core.$ZodCatch;
z.string().catch("fallback") satisfies z.ZodCatch;
// $ZodFile
z.file() satisfies z.core.$ZodFile;
z.file() satisfies z.ZodFile;
});
test("checks", () => {
const _a: z.core.$ZodCheck = {} as any as z.core.$ZodChecks;
const _b: z.core.$ZodCheck = {} as any as z.core.$ZodStringFormatChecks;
const _c: z.core.$ZodType = {} as any as z.core.$ZodTypes;
const _d: z.core.$ZodType = {} as any as z.core.$ZodStringFormatTypes;
});
test("assignability to $ZodType", () => {
z.string() satisfies z.ZodType;
z.number() satisfies z.ZodType;
z.boolean() satisfies z.ZodType;
z.object({ key: z.string() }) satisfies z.ZodType;
z.object({ key: z.string() }) satisfies z.ZodType<{ key: string }>;
z.array(z.string()) satisfies z.ZodType;
z.union([z.string(), z.number()]) satisfies z.ZodType;
z.intersection(z.string(), z.number()) satisfies z.ZodType;
z.tuple([z.string(), z.number()]) satisfies z.ZodType;
z.record(z.string(), z.number()) satisfies z.ZodType;
z.map(z.string(), z.number()) satisfies z.ZodType;
z.set(z.string()) satisfies z.ZodType;
z.literal("example") satisfies z.ZodType;
expectTypeOf<z.ZodType extends z.core.$ZodType ? true : false>().toEqualTypeOf<true>();
});
test("assignability with narrowing", () => {
type _RefinedSchema<T extends z.ZodType<object> | z.ZodUnion> = T extends z.ZodUnion
? RefinedUnionSchema<T> // <-- Type instantiation is excessively deep and possibly infinite.
: T extends z.ZodType<object>
? RefinedTypeSchema<z.output<T>> // <-- Type instantiation is excessively deep and possibly infinite.
: never;
type RefinedTypeSchema<T extends object> = T;
type RefinedUnionSchema<T extends z.ZodUnion> = T;
});
test("generic assignability in objects", () => {
interface SortItem<T extends string> {
key: T;
order: string;
}
const createSortItemSchema = <T extends z.ZodType<string>>(sortKeySchema: T) =>
z.object({
key: sortKeySchema,
order: z.string(),
});
<T extends z.ZodType<string>>(sortKeySchema: T, defaultSortBy: SortItem<z.output<T>>[] = []) =>
createSortItemSchema(sortKeySchema).array().default(defaultSortBy);
});

View File

@ -0,0 +1,381 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
/// string
const stringSchema = z.string();
test("string async parse", async () => {
const goodData = "XXX";
const badData = 12;
const goodResult = await stringSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await stringSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// number
const numberSchema = z.number();
test("number async parse", async () => {
const goodData = 1234.2353;
const badData = "1234";
const goodResult = await numberSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await numberSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// bigInt
const bigIntSchema = z.bigint();
test("bigInt async parse", async () => {
const goodData = BigInt(145);
const badData = 134;
const goodResult = await bigIntSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await bigIntSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// boolean
const booleanSchema = z.boolean();
test("boolean async parse", async () => {
const goodData = true;
const badData = 1;
const goodResult = await booleanSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await booleanSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// date
const dateSchema = z.date();
test("date async parse", async () => {
const goodData = new Date();
const badData = new Date().toISOString();
const goodResult = await dateSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await dateSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// undefined
const undefinedSchema = z.undefined();
test("undefined async parse", async () => {
const goodData = undefined;
const badData = "XXX";
const goodResult = await undefinedSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(undefined);
const badResult = await undefinedSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// null
const nullSchema = z.null();
test("null async parse", async () => {
const goodData = null;
const badData = undefined;
const goodResult = await nullSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await nullSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// any
const anySchema = z.any();
test("any async parse", async () => {
const goodData = [{}];
// const badData = 'XXX';
const goodResult = await anySchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
// const badResult = await anySchema.safeParseAsync(badData);
// expect(badResult.success).toBe(false);
// if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// unknown
const unknownSchema = z.unknown();
test("unknown async parse", async () => {
const goodData = ["asdf", 124, () => {}];
// const badData = 'XXX';
const goodResult = await unknownSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
// const badResult = await unknownSchema.safeParseAsync(badData);
// expect(badResult.success).toBe(false);
// if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// void
const voidSchema = z.void();
test("void async parse", async () => {
const goodData = undefined;
const badData = 0;
const goodResult = await voidSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await voidSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// array
const arraySchema = z.array(z.string());
test("array async parse", async () => {
const goodData = ["XXX"];
const badData = "XXX";
const goodResult = await arraySchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await arraySchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// object
const objectSchema = z.object({ string: z.string() });
test("object async parse", async () => {
const goodData = { string: "XXX" };
const badData = { string: 12 };
const goodResult = await objectSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await objectSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// union
const unionSchema = z.union([z.string(), z.undefined()]);
test("union async parse", async () => {
const goodData = undefined;
const badData = null;
const goodResult = await unionSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await unionSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// record
const recordSchema = z.record(z.string(), z.object({}));
test("record async parse", async () => {
const goodData = { adsf: {}, asdf: {} };
const badData = [{}];
const goodResult = await recordSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await recordSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// function
// const functionSchema = z.function();
// test("function async parse", async () => {
// const goodData = () => {};
// const badData = "XXX";
// const goodResult = await functionSchema.safeParseAsync(goodData);
// expect(goodResult.success).toBe(true);
// if (goodResult.success) expect(typeof goodResult.data).toEqual("function");
// const badResult = await functionSchema.safeParseAsync(badData);
// expect(badResult.success).toBe(false);
// if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
// });
/// literal
const literalSchema = z.literal("asdf");
test("literal async parse", async () => {
const goodData = "asdf";
const badData = "asdff";
const goodResult = await literalSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await literalSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// enum
const enumSchema = z.enum(["fish", "whale"]);
test("enum async parse", async () => {
const goodData = "whale";
const badData = "leopard";
const goodResult = await enumSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await enumSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// nativeEnum
enum nativeEnumTest {
asdf = "qwer",
}
// @ts-ignore
const nativeEnumSchema = z.nativeEnum(nativeEnumTest);
test("nativeEnum async parse", async () => {
const goodData = nativeEnumTest.asdf;
const badData = "asdf";
const goodResult = await nativeEnumSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
const badResult = await nativeEnumSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
});
/// promise
const promiseSchema = z.promise(z.number());
test("promise async parse good", async () => {
const goodData = Promise.resolve(123);
const goodResult = await promiseSchema.safeParseAsync(goodData);
expect(goodResult.success).toBe(true);
expect(typeof goodResult.data).toEqual("number");
expect(goodResult.data).toEqual(123);
});
test("promise async parse bad", async () => {
const badData = Promise.resolve("XXX");
const badResult = await promiseSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
expect(badResult.error).toBeInstanceOf(z.ZodError);
});
test("async validation non-empty strings", async () => {
const base = z.object({
hello: z.string().refine((x) => x && x.length > 0),
foo: z.string().refine((x) => x && x.length > 0),
});
const testval = { hello: "", foo: "" };
const result1 = base.safeParse(testval);
const result2 = base.safeParseAsync(testval);
const r1 = result1;
await result2.then((r2) => {
expect(r1.error!.issues.length).toBe(r2.error!.issues.length);
});
});
test("async validation multiple errors 1", async () => {
const base = z.object({
hello: z.string(),
foo: z.number(),
});
const testval = { hello: 3, foo: "hello" };
const result1 = base.safeParse(testval);
const result2 = base.safeParseAsync(testval);
await result2.then((result2) => {
expect(result2.error!.issues.length).toBe(result1.error!.issues.length);
});
});
test("async validation multiple errors 2", async () => {
const base = (is_async?: boolean) =>
z.object({
hello: z.string(),
foo: z.object({
bar: z.number().refine(
is_async
? async () =>
new Promise((resolve) => {
setTimeout(() => resolve(false), 500);
})
: () => false
),
}),
});
const testval = { hello: 3, foo: { bar: 4 } };
const result1 = base().safeParse(testval);
const result2 = base(true).safeParseAsync(testval);
await result2.then((result2) => {
expect(result1.error!.issues.length).toBe(result2.error!.issues.length);
});
});
test("ensure early async failure prevents follow-up refinement checks", async () => {
let count = 0;
const base = z.object({
hello: z.string(),
foo: z
.number()
.refine(async () => {
count++;
return true;
})
.refine(async () => {
count++;
return true;
}, "Good"),
});
const testval = { hello: "bye", foo: 3 };
const result = await base.safeParseAsync(testval);
if (result.success === false) {
expect(result.error.issues.length).toBe(1);
expect(count).toBe(1);
}
// await result.then((r) => {
// if (r.success === false) expect(r.error.issues.length).toBe(1);
// expect(count).toBe(2);
// });
});

View File

@ -0,0 +1,68 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("async refine .parse()", async () => {
// throws ZodAsyncError
const s1 = z.string().refine(async (_val) => true);
expect(() => s1.safeParse("asdf")).toThrow();
});
test("async refine", async () => {
const s1 = z.string().refine(async (_val) => true);
const r1 = await s1.parseAsync("asdf");
expect(r1).toEqual("asdf");
const s2 = z.string().refine(async (_val) => false);
const r2 = await s2.safeParseAsync("asdf");
expect(r2.success).toBe(false);
expect(r2).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});
test("async refine with Promises", async () => {
// expect.assertions(2);
const schema1 = z.string().refine((_val) => Promise.resolve(true));
const v1 = await schema1.parseAsync("asdf");
expect(v1).toEqual("asdf");
const schema2 = z.string().refine((_val) => Promise.resolve(false));
await expect(schema2.parseAsync("asdf")).rejects.toBeDefined();
const schema3 = z.string().refine((_val) => Promise.resolve(true));
await expect(schema3.parseAsync("asdf")).resolves.toEqual("asdf");
return await expect(schema3.parseAsync("qwer")).resolves.toEqual("qwer");
});
test("async refine that uses value", async () => {
const schema1 = z.string().refine(async (val) => {
return val.length > 5;
});
const r1 = await schema1.safeParseAsync("asdf");
expect(r1.success).toBe(false);
expect(r1.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
const r2 = await schema1.safeParseAsync("asdf123");
expect(r2.success).toBe(true);
expect(r2.data).toEqual("asdf123");
});

View File

@ -0,0 +1,7 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("test this binding", () => {
const parse = z.string().parse;
expect(parse("asdf")).toBe("asdf");
});

View File

@ -0,0 +1,54 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
const gtFive = z.bigint().gt(BigInt(5));
const gteFive = z.bigint().gte(BigInt(5));
const ltFive = z.bigint().lt(BigInt(5));
const lteFive = z.bigint().lte(BigInt(5));
const positive = z.bigint().positive();
const negative = z.bigint().negative();
const nonnegative = z.bigint().nonnegative();
const nonpositive = z.bigint().nonpositive();
const multipleOfFive = z.bigint().multipleOf(BigInt(5));
test("passing validations", () => {
z.bigint().parse(BigInt(1));
z.bigint().parse(BigInt(0));
z.bigint().parse(BigInt(-1));
gtFive.parse(BigInt(6));
gteFive.parse(BigInt(5));
gteFive.parse(BigInt(6));
ltFive.parse(BigInt(4));
lteFive.parse(BigInt(5));
lteFive.parse(BigInt(4));
positive.parse(BigInt(3));
negative.parse(BigInt(-2));
nonnegative.parse(BigInt(0));
nonnegative.parse(BigInt(7));
nonpositive.parse(BigInt(0));
nonpositive.parse(BigInt(-12));
multipleOfFive.parse(BigInt(15));
});
test("failing validations", () => {
expect(() => gtFive.parse(BigInt(5))).toThrow();
expect(() => gteFive.parse(BigInt(4))).toThrow();
expect(() => ltFive.parse(BigInt(5))).toThrow();
expect(() => lteFive.parse(BigInt(6))).toThrow();
expect(() => positive.parse(BigInt(0))).toThrow();
expect(() => positive.parse(BigInt(-2))).toThrow();
expect(() => negative.parse(BigInt(0))).toThrow();
expect(() => negative.parse(BigInt(3))).toThrow();
expect(() => nonnegative.parse(BigInt(-1))).toThrow();
expect(() => nonpositive.parse(BigInt(1))).toThrow();
expect(() => multipleOfFive.parse(BigInt(13))).toThrow();
});
test("min max getters", () => {
expect(z.bigint().min(BigInt(5)).minValue).toEqual(BigInt(5));
expect(z.bigint().min(BigInt(5)).min(BigInt(10)).minValue).toEqual(BigInt(10));
expect(z.bigint().max(BigInt(5)).maxValue).toEqual(BigInt(5));
expect(z.bigint().max(BigInt(5)).max(BigInt(1)).maxValue).toEqual(BigInt(1));
});

View File

@ -0,0 +1,63 @@
import { expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("branded types", () => {
const mySchema = z
.object({
name: z.string(),
})
.brand<"superschema">();
// simple branding
type MySchema = z.infer<typeof mySchema>;
expectTypeOf<MySchema>().toEqualTypeOf<{ name: string } & z.$brand<"superschema">>();
const doStuff = (arg: MySchema) => arg;
doStuff(mySchema.parse({ name: "hello there" }));
// inheritance
const extendedSchema = mySchema.brand<"subschema">();
type ExtendedSchema = z.infer<typeof extendedSchema>;
expectTypeOf<ExtendedSchema>().toEqualTypeOf<{ name: string } & z.BRAND<"superschema"> & z.BRAND<"subschema">>();
doStuff(extendedSchema.parse({ name: "hello again" }));
// number branding
const numberSchema = z.number().brand<42>();
type NumberSchema = z.infer<typeof numberSchema>;
expectTypeOf<NumberSchema>().toEqualTypeOf<number & { [z.$brand]: { 42: true } }>();
// symbol branding
const MyBrand: unique symbol = Symbol("hello");
type MyBrand = typeof MyBrand;
const symbolBrand = z.number().brand<"sup">().brand<typeof MyBrand>();
type SymbolBrand = z.infer<typeof symbolBrand>;
// number & { [z.BRAND]: { sup: true, [MyBrand]: true } }
expectTypeOf<SymbolBrand>().toEqualTypeOf<number & z.BRAND<"sup"> & z.BRAND<MyBrand>>();
// keeping brands out of input types
const age = z.number().brand<"age">();
type Age = z.infer<typeof age>;
type AgeInput = z.input<typeof age>;
expectTypeOf<AgeInput>().not.toEqualTypeOf<Age>();
expectTypeOf<number>().toEqualTypeOf<AgeInput>();
expectTypeOf<number & z.BRAND<"age">>().toEqualTypeOf<Age>();
// @ts-expect-error
doStuff({ name: "hello there!" });
});
test("$branded", () => {
const a = z.string().brand<"a">();
expectTypeOf<typeof a>().toEqualTypeOf<z.core.$ZodBranded<z.ZodString, "a">>();
});
test("branded record", () => {
const recordWithBrandedNumberKeys = z.record(z.string().brand("SomeBrand"), z.number());
type recordWithBrandedNumberKeys = z.infer<typeof recordWithBrandedNumberKeys>;
expectTypeOf<recordWithBrandedNumberKeys>().toEqualTypeOf<Record<string & z.core.$brand<"SomeBrand">, number>>();
});

View File

@ -0,0 +1,252 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
import type { util } from "zod/v4/core";
test("basic catch", () => {
expect(z.string().catch("default").parse(undefined)).toBe("default");
});
test("catch fn does not run when parsing succeeds", () => {
let isCalled = false;
const cb = () => {
isCalled = true;
return "asdf";
};
expect(z.string().catch(cb).parse("test")).toBe("test");
expect(isCalled).toEqual(false);
});
test("basic catch async", async () => {
const result = await z.string().catch("default").parseAsync(1243);
expect(result).toBe("default");
});
test("catch replace wrong types", () => {
expect(z.string().catch("default").parse(true)).toBe("default");
expect(z.string().catch("default").parse(true)).toBe("default");
expect(z.string().catch("default").parse(15)).toBe("default");
expect(z.string().catch("default").parse([])).toBe("default");
expect(z.string().catch("default").parse(new Map())).toBe("default");
expect(z.string().catch("default").parse(new Set())).toBe("default");
expect(z.string().catch("default").parse({})).toBe("default");
});
test("catch with transform", () => {
const stringWithDefault = z
.string()
.transform((val) => val.toUpperCase())
.catch("default");
expect(stringWithDefault.parse(undefined)).toBe("default");
expect(stringWithDefault.parse(15)).toBe("default");
expect(stringWithDefault).toBeInstanceOf(z.ZodCatch);
expect(stringWithDefault.unwrap()).toBeInstanceOf(z.ZodPipe);
expect(stringWithDefault.unwrap().in).toBeInstanceOf(z.ZodString);
expect(stringWithDefault.unwrap().out).toBeInstanceOf(z.ZodTransform);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | util.Whatever>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("catch on existing optional", () => {
const stringWithDefault = z.string().optional().catch("asdf");
expect(stringWithDefault.parse(undefined)).toBe(undefined);
expect(stringWithDefault.parse(15)).toBe("asdf");
expect(stringWithDefault).toBeInstanceOf(z.ZodCatch);
expect(stringWithDefault.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(stringWithDefault.unwrap().unwrap()).toBeInstanceOf(z.ZodString);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined | util.Whatever>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string | undefined>();
});
test("optional on catch", () => {
const stringWithDefault = z.string().catch("asdf").optional();
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | util.Whatever>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string | undefined>();
});
test("complex chain example", () => {
const complex = z
.string()
.catch("asdf")
.transform((val) => `${val}!`)
.transform((val) => val.toUpperCase())
.catch("qwer")
.unwrap()
.optional()
.catch("asdfasdf");
expect(complex.parse("qwer")).toBe("QWER!");
expect(complex.parse(15)).toBe("ASDF!");
expect(complex.parse(true)).toBe("ASDF!");
});
test("removeCatch", () => {
const stringWithRemovedDefault = z.string().catch("asdf").unwrap();
type out = z.output<typeof stringWithRemovedDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("nested", () => {
const inner = z.string().catch("asdf");
const outer = z.object({ inner }).catch({
inner: "asdf",
});
type input = z.input<typeof outer>;
expectTypeOf<input>().toEqualTypeOf<{ inner: string | util.Whatever } | util.Whatever>();
type out = z.output<typeof outer>;
expectTypeOf<out>().toEqualTypeOf<{ inner: string }>();
expect(outer.parse(undefined)).toEqual({ inner: "asdf" });
expect(outer.parse({})).toEqual({ inner: "asdf" });
expect(outer.parse({ inner: undefined })).toEqual({ inner: "asdf" });
});
test("chained catch", () => {
const stringWithDefault = z.string().catch("inner").catch("outer");
const result = stringWithDefault.parse(undefined);
expect(result).toEqual("inner");
const resultDiff = stringWithDefault.parse(5);
expect(resultDiff).toEqual("inner");
});
test("native enum", () => {
enum Fruits {
apple = "apple",
orange = "orange",
}
const schema = z.object({
fruit: z.nativeEnum(Fruits).catch(Fruits.apple),
});
expect(schema.parse({})).toEqual({ fruit: Fruits.apple });
expect(schema.parse({ fruit: 15 })).toEqual({ fruit: Fruits.apple });
});
test("enum", () => {
const schema = z.object({
fruit: z.enum(["apple", "orange"]).catch("apple"),
});
expect(schema.parse({})).toEqual({ fruit: "apple" });
expect(schema.parse({ fruit: true })).toEqual({ fruit: "apple" });
expect(schema.parse({ fruit: 15 })).toEqual({ fruit: "apple" });
});
test("reported issues with nested usage", () => {
const schema = z.object({
string: z.string(),
obj: z.object({
sub: z.object({
lit: z.literal("a"),
subCatch: z.number().catch(23),
}),
midCatch: z.number().catch(42),
}),
number: z.number().catch(0),
bool: z.boolean(),
});
try {
schema.parse({
string: {},
obj: {
sub: {
lit: "b",
subCatch: "24",
},
midCatch: 444,
},
number: "",
bool: "yes",
});
} catch (error) {
const issues = (error as z.ZodError).issues;
expect(issues.length).toEqual(3);
expect(issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received object",
"path": [
"string",
],
},
{
"code": "invalid_value",
"message": "Invalid input: expected "a"",
"path": [
"obj",
"sub",
"lit",
],
"values": [
"a",
],
},
{
"code": "invalid_type",
"expected": "boolean",
"message": "Invalid input: expected boolean, received string",
"path": [
"bool",
],
},
]
`);
// expect(issues[0].message).toMatch("string");
// expect(issues[1].message).toMatch("literal");
// expect(issues[2].message).toMatch("boolean");
}
});
test("catch error", () => {
const schema = z.object({
age: z.number(),
name: z.string().catch((ctx) => {
ctx.issues;
// issues = ctx.issues;
return "John Doe";
}),
});
const result = schema.safeParse({
age: null,
name: null,
});
expect(result.success).toEqual(false);
expect(result.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
"age"
],
"message": "Invalid input: expected number, received null"
}
]]
`);
});
test("ctx.input", () => {
const schema = z.string().catch((ctx) => {
return String(ctx.input);
});
expect(schema.parse(123)).toEqual("123");
});

View File

@ -0,0 +1,20 @@
import { expect, test } from "vitest";
test("coalesce", () => {
expect(true).toBe(true);
});
// test("nonoptional with default", () => {
// const schema = z.string().optional().coalesce("hi");
// expectTypeOf<typeof schema._input>().toEqualTypeOf<string | undefined>();
// expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
// expect(schema.parse(undefined)).toBe("hi");
// });
// test("nonoptional in object", () => {
// const schema = z.object({ hi: z.string().optional().nonoptional("hi") });
// expectTypeOf<typeof schema._input>().toEqualTypeOf<{ hi: string | undefined }>();
// expectTypeOf<typeof schema._output>().toEqualTypeOf<{ hi: string }>();
// expect(schema.parse(undefined)).toBe("hi");
// });

View File

@ -0,0 +1,160 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("string coercion", () => {
const schema = z.coerce.string();
expect(schema.parse("sup")).toEqual("sup");
expect(schema.parse("")).toEqual("");
expect(schema.parse(12)).toEqual("12");
expect(schema.parse(0)).toEqual("0");
expect(schema.parse(-12)).toEqual("-12");
expect(schema.parse(3.14)).toEqual("3.14");
expect(schema.parse(BigInt(15))).toEqual("15");
expect(schema.parse(Number.NaN)).toEqual("NaN");
expect(schema.parse(Number.POSITIVE_INFINITY)).toEqual("Infinity");
expect(schema.parse(Number.NEGATIVE_INFINITY)).toEqual("-Infinity");
expect(schema.parse(true)).toEqual("true");
expect(schema.parse(false)).toEqual("false");
expect(schema.parse(null)).toEqual("null");
expect(schema.parse(undefined)).toEqual("undefined");
expect(schema.parse({ hello: "world!" })).toEqual("[object Object]");
expect(schema.parse(["item", "another_item"])).toEqual("item,another_item");
expect(schema.parse([])).toEqual("");
expect(schema.parse(new Date("2022-01-01T00:00:00.000Z"))).toEqual(new Date("2022-01-01T00:00:00.000Z").toString());
});
test("number coercion", () => {
const schema = z.coerce.number();
expect(schema.parse("12")).toEqual(12);
expect(schema.parse("0")).toEqual(0);
expect(schema.parse("-12")).toEqual(-12);
expect(schema.parse("3.14")).toEqual(3.14);
expect(schema.parse("")).toEqual(0);
expect(() => schema.parse("NOT_A_NUMBER")).toThrow(); // z.ZodError
expect(schema.parse(12)).toEqual(12);
expect(schema.parse(0)).toEqual(0);
expect(schema.parse(-12)).toEqual(-12);
expect(schema.parse(3.14)).toEqual(3.14);
expect(schema.parse(BigInt(15))).toEqual(15);
expect(() => schema.parse(Number.NaN)).toThrow(); // z.ZodError
// expect(schema.parse(Number.POSITIVE_INFINITY)).toEqual(Number.POSITIVE_INFINITY);
// expect(schema.parse(Number.NEGATIVE_INFINITY)).toEqual(Number.NEGATIVE_INFINITY);
expect(schema.parse(true)).toEqual(1);
expect(schema.parse(false)).toEqual(0);
expect(schema.parse(null)).toEqual(0);
expect(() => schema.parse(undefined)).toThrow(); // z.ZodError
expect(() => schema.parse({ hello: "world!" })).toThrow(); // z.ZodError
expect(() => schema.parse(["item", "another_item"])).toThrow(); // z.ZodError
expect(schema.parse([])).toEqual(0);
expect(schema.parse(new Date(1670139203496))).toEqual(1670139203496);
});
test("boolean coercion", () => {
const schema = z.coerce.boolean();
expect(schema.parse("true")).toEqual(true);
expect(schema.parse("false")).toEqual(true);
expect(schema.parse("0")).toEqual(true);
expect(schema.parse("1")).toEqual(true);
expect(schema.parse("")).toEqual(false);
expect(schema.parse(1)).toEqual(true);
expect(schema.parse(0)).toEqual(false);
expect(schema.parse(-1)).toEqual(true);
expect(schema.parse(3.14)).toEqual(true);
expect(schema.parse(BigInt(15))).toEqual(true);
expect(schema.parse(Number.NaN)).toEqual(false);
expect(schema.parse(Number.POSITIVE_INFINITY)).toEqual(true);
expect(schema.parse(Number.NEGATIVE_INFINITY)).toEqual(true);
expect(schema.parse(true)).toEqual(true);
expect(schema.parse(false)).toEqual(false);
expect(schema.parse(null)).toEqual(false);
expect(schema.parse(undefined)).toEqual(false);
expect(schema.parse({ hello: "world!" })).toEqual(true);
expect(schema.parse(["item", "another_item"])).toEqual(true);
expect(schema.parse([])).toEqual(true);
expect(schema.parse(new Date(1670139203496))).toEqual(true);
});
test("bigint coercion", () => {
const schema = z.coerce.bigint();
expect(schema.parse("5")).toEqual(BigInt(5));
expect(schema.parse("0")).toEqual(BigInt(0));
expect(schema.parse("-5")).toEqual(BigInt(-5));
expect(() => schema.parse("3.14")).toThrow(); // not a z.ZodError!
expect(schema.parse("")).toEqual(BigInt(0));
expect(() => schema.parse("NOT_A_NUMBER")).toThrow(); // not a z.ZodError!
expect(schema.parse(5)).toEqual(BigInt(5));
expect(schema.parse(0)).toEqual(BigInt(0));
expect(schema.parse(-5)).toEqual(BigInt(-5));
expect(() => schema.parse(3.14)).toThrow(); // not a z.ZodError!
expect(schema.parse(BigInt(5))).toEqual(BigInt(5));
expect(() => schema.parse(Number.NaN)).toThrow(); // not a z.ZodError!
expect(() => schema.parse(Number.POSITIVE_INFINITY)).toThrow(); // not a z.ZodError!
expect(() => schema.parse(Number.NEGATIVE_INFINITY)).toThrow(); // not a z.ZodError!
expect(schema.parse(true)).toEqual(BigInt(1));
expect(schema.parse(false)).toEqual(BigInt(0));
expect(() => schema.parse(null)).toThrow(); // not a z.ZodError!
expect(() => schema.parse(undefined)).toThrow(); // not a z.ZodError!
expect(() => schema.parse({ hello: "world!" })).toThrow(); // not a z.ZodError!
expect(() => schema.parse(["item", "another_item"])).toThrow(); // not a z.ZodError!
expect(schema.parse([])).toEqual(BigInt(0));
expect(schema.parse(new Date(1670139203496))).toEqual(BigInt(1670139203496));
});
test("date coercion", () => {
const schema = z.coerce.date();
expect(schema.parse(new Date().toDateString())).toBeInstanceOf(Date);
expect(schema.parse(new Date().toISOString())).toBeInstanceOf(Date);
expect(schema.parse(new Date().toUTCString())).toBeInstanceOf(Date);
expect(schema.parse("5")).toBeInstanceOf(Date);
expect(schema.parse("2000-01-01")).toBeInstanceOf(Date);
// expect(schema.parse("0")).toBeInstanceOf(Date);
// expect(schema.parse("-5")).toBeInstanceOf(Date);
// expect(schema.parse("3.14")).toBeInstanceOf(Date);
expect(() => schema.parse("")).toThrow(); // z.ZodError
expect(() => schema.parse("NOT_A_DATE")).toThrow(); // z.ZodError
expect(schema.parse(5)).toBeInstanceOf(Date);
expect(schema.parse(0)).toBeInstanceOf(Date);
expect(schema.parse(-5)).toBeInstanceOf(Date);
expect(schema.parse(3.14)).toBeInstanceOf(Date);
expect(() => schema.parse(BigInt(5))).toThrow(); // not a z.ZodError!
expect(() => schema.parse(Number.NaN)).toThrow(); // z.ZodError
expect(() => schema.parse(Number.POSITIVE_INFINITY)).toThrow(); // z.ZodError
expect(() => schema.parse(Number.NEGATIVE_INFINITY)).toThrow(); // z.ZodError
expect(schema.parse(true)).toBeInstanceOf(Date);
expect(schema.parse(false)).toBeInstanceOf(Date);
expect(schema.parse(null)).toBeInstanceOf(Date);
expect(() => schema.parse(undefined)).toThrow(); // z.ZodError
expect(() => schema.parse({ hello: "world!" })).toThrow(); // z.ZodError
expect(() => schema.parse(["item", "another_item"])).toThrow(); // z.ZodError
expect(() => schema.parse([])).toThrow(); // z.ZodError
expect(schema.parse(new Date())).toBeInstanceOf(Date);
});
// test("template literal coercion", () => {
// const schema = z.coerce
// .templateLiteral()
// .interpolated(z.number().finite())
// .interpolated(
// z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]).optional()
// );
// expect(schema.parse(300)).toEqual("300");
// expect(schema.parse(BigInt(300))).toEqual("300");
// expect(schema.parse("300")).toEqual("300");
// expect(schema.parse("300px")).toEqual("300px");
// expect(schema.parse("300em")).toEqual("300em");
// expect(schema.parse("300rem")).toEqual("300rem");
// expect(schema.parse("300vh")).toEqual("300vh");
// expect(schema.parse("300vw")).toEqual("300vw");
// expect(schema.parse("300vmin")).toEqual("300vmin");
// expect(schema.parse("300vmax")).toEqual("300vmax");
// expect(schema.parse(["300px"])).toEqual("300px");
// });
test("override input type", () => {
const a = z.coerce.string<any>();
type input = z.input<typeof a>;
expectTypeOf<input>().toEqualTypeOf<any>();
type output = z.infer<typeof a>;
expectTypeOf<output>().toEqualTypeOf<string>();
});

View File

@ -0,0 +1,352 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("continuability", () => {
/**
* | $ZodGUID
| $ZodUUID
| $ZodEmail
| $ZodURL
| $ZodEmoji
| $ZodNanoID
| $ZodCUID
| $ZodCUID2
| $ZodULID
| $ZodXID
| $ZodKSUID
| $ZodISODateTime
| $ZodISODate
| $ZodISOTime
| $ZodISODuration
| $ZodIPv4
| $ZodIPv6
| $ZodCIDRv4
| $ZodCIDRv6
| $ZodBase64
| $ZodBase64URL
| $ZodE164
| $ZodJWT;
*/
expect(
z
.email()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "email",
"message": "Invalid email address",
"origin": "string",
"path": [],
"pattern": "/^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.uuid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "uuid",
"message": "Invalid UUID",
"origin": "string",
"path": [],
"pattern": "/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.url()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "url",
"message": "Invalid URL",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.jwt()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "jwt",
"message": "Invalid JWT",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cidrv4()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cidrv4",
"message": "Invalid IPv4 range",
"origin": "string",
"path": [],
"pattern": "/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cidrv6()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cidrv6",
"message": "Invalid IPv6 range",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ipv4()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ipv4",
"message": "Invalid IPv4 address",
"origin": "string",
"path": [],
"pattern": "/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ipv6()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ipv6",
"message": "Invalid IPv6 address",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.emoji()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "emoji",
"message": "Invalid emoji",
"origin": "string",
"path": [],
"pattern": "/^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$/u",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.nanoid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "nanoid",
"message": "Invalid nanoid",
"origin": "string",
"path": [],
"pattern": "/^[a-zA-Z0-9_-]{21}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cuid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cuid",
"message": "Invalid cuid",
"origin": "string",
"path": [],
"pattern": "/^[cC][^\\s-]{8,}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cuid2()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cuid2",
"message": "Invalid cuid2",
"origin": "string",
"path": [],
"pattern": "/^[0-9a-z]+$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ulid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ulid",
"message": "Invalid ULID",
"origin": "string",
"path": [],
"pattern": "/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.xid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "xid",
"message": "Invalid XID",
"origin": "string",
"path": [],
"pattern": "/^[0-9a-vA-V]{20}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ksuid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ksuid",
"message": "Invalid KSUID",
"origin": "string",
"path": [],
"pattern": "/^[A-Za-z0-9]{27}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
});

View File

@ -0,0 +1,40 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("passing validations", () => {
const example1 = z.custom<number>((x) => typeof x === "number");
example1.parse(1234);
expect(() => example1.parse({})).toThrow();
});
test("string params", () => {
const example1 = z.custom<number>((x) => typeof x !== "number", "customerr");
const result = example1.safeParse(1234);
expect(result.success).toEqual(false);
expect(JSON.stringify(result.error).includes("customerr")).toEqual(true);
});
test("instanceof", () => {
const fn = (value: string) => Uint8Array.from(Buffer.from(value, "base64"));
// Argument of type 'ZodCustom<Uint8Array<ArrayBuffer>, unknown>' is not assignable to parameter of type '$ZodType<any, Uint8Array<ArrayBuffer>>'.
z.string().transform(fn).pipe(z.instanceof(Uint8Array));
});
test("non-continuable by default", () => {
const A = z
.custom<string>((val) => typeof val === "string")
.transform((_) => {
throw new Error("Invalid input");
});
expect(A.safeParse(123).error!).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});

View File

@ -0,0 +1,31 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
const beforeBenchmarkDate = new Date(2022, 10, 4);
const benchmarkDate = new Date(2022, 10, 5);
const afterBenchmarkDate = new Date(2022, 10, 6);
const minCheck = z.date().min(benchmarkDate);
const maxCheck = z.date().max(benchmarkDate);
test("passing validations", () => {
minCheck.parse(benchmarkDate);
minCheck.parse(afterBenchmarkDate);
maxCheck.parse(benchmarkDate);
maxCheck.parse(beforeBenchmarkDate);
});
test("failing validations", () => {
expect(() => minCheck.parse(beforeBenchmarkDate)).toThrow();
expect(() => maxCheck.parse(afterBenchmarkDate)).toThrow();
});
test("min max getters", () => {
expect(minCheck.minDate).toEqual(benchmarkDate);
expect(minCheck.min(afterBenchmarkDate).minDate).toEqual(afterBenchmarkDate);
expect(maxCheck.maxDate).toEqual(benchmarkDate);
expect(maxCheck.max(beforeBenchmarkDate).maxDate).toEqual(beforeBenchmarkDate);
});

View File

@ -0,0 +1,296 @@
import { checkSync } from "recheck";
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("basic datetime parsing", () => {
const datetime = z.string().datetime();
datetime.parse("1970-01-01T00:00:00.000Z");
datetime.parse("2022-10-13T09:52:31.816Z");
datetime.parse("2022-10-13T09:52:31.8162314Z");
datetime.parse("1970-01-01T00:00:00Z");
datetime.parse("2022-10-13T09:52:31Z");
expect(() => datetime.parse("")).toThrow();
expect(() => datetime.parse("foo")).toThrow();
expect(() => datetime.parse("2020-10-14")).toThrow();
expect(() => datetime.parse("T18:45:12.123")).toThrow();
expect(() => datetime.parse("2020-10-14T17:42:29+00:00")).toThrow();
});
test("datetime parsing with precision -1", () => {
const datetimeNoMs = z.string().datetime({ precision: -1, offset: true, local: true });
datetimeNoMs.parse("1970-01-01T00:00Z");
datetimeNoMs.parse("2022-10-13T09:52Z");
datetimeNoMs.parse("2022-10-13T09:52+02:00");
datetimeNoMs.parse("2022-10-13T09:52");
expect(() => datetimeNoMs.parse("tuna")).toThrow();
expect(() => datetimeNoMs.parse("2022-10-13T09:52+02")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
});
test("datetime parsing with precision 0", () => {
const datetimeNoMs = z.string().datetime({ precision: 0 });
datetimeNoMs.parse("1970-01-01T00:00:00Z");
datetimeNoMs.parse("2022-10-13T09:52:31Z");
expect(() => datetimeNoMs.parse("tuna")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
});
test("datetime parsing with precision 3", () => {
const datetime3Ms = z.string().datetime({ precision: 3 });
datetime3Ms.parse("1970-01-01T00:00:00.000Z");
datetime3Ms.parse("2022-10-13T09:52:31.123Z");
expect(() => datetime3Ms.parse("tuna")).toThrow();
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.1Z")).toThrow();
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.12Z")).toThrow();
expect(() => datetime3Ms.parse("2022-10-13T09:52:31Z")).toThrow();
});
test("datetime parsing with offset", () => {
const datetimeOffset = z.string().datetime({ offset: true });
datetimeOffset.parse("1970-01-01T00:00:00.000Z");
datetimeOffset.parse("2022-10-13T09:52:31.816234134Z");
datetimeOffset.parse("1970-01-01T00:00:00Z");
datetimeOffset.parse("2022-10-13T09:52:31.4Z");
datetimeOffset.parse("2020-10-14T17:42:29+00:00");
datetimeOffset.parse("2020-10-14T17:42:29+03:15");
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+0315")).toThrow();
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+03")).toThrow();
expect(() => datetimeOffset.parse("tuna")).toThrow();
expect(() => datetimeOffset.parse("2022-10-13T09:52:31.Z")).toThrow();
});
test("datetime parsing with offset and precision 0", () => {
const datetimeOffsetNoMs = z.string().datetime({ offset: true, precision: 0 });
datetimeOffsetNoMs.parse("1970-01-01T00:00:00Z");
datetimeOffsetNoMs.parse("2022-10-13T09:52:31Z");
datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00:00");
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29+0000")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00")).toThrow();
expect(() => datetimeOffsetNoMs.parse("tuna")).toThrow();
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
});
test("datetime parsing with offset and precision 4", () => {
const datetimeOffset4Ms = z.string().datetime({ offset: true, precision: 4 });
datetimeOffset4Ms.parse("1970-01-01T00:00:00.1234Z");
datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00:00");
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+0000")).toThrow();
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00")).toThrow();
expect(() => datetimeOffset4Ms.parse("tuna")).toThrow();
expect(() => datetimeOffset4Ms.parse("1970-01-01T00:00:00.123Z")).toThrow();
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
});
test("datetime offset normalization", () => {
const a = z.iso.datetime({ offset: true });
expect(a.safeParse("2020-10-14T17:42:29+02")).toMatchObject({ success: false });
expect(a.safeParse("2020-10-14T17:42:29+0200")).toMatchObject({ success: false });
a.safeParse("2020-10-14T17:42:29+02:00");
});
test("datetime parsing with local option", () => {
const a = z.string().datetime({ local: true });
expect(a.safeParse("1970-01-01T00:00")).toMatchObject({ success: true });
expect(a.safeParse("1970-01-01T00:00:00")).toMatchObject({ success: true });
expect(a.safeParse("2022-10-13T09:52:31.816")).toMatchObject({ success: true });
expect(a.safeParse("1970-01-01T00:00:00.000")).toMatchObject({ success: true });
expect(a.safeParse("1970-01-01T00")).toMatchObject({ success: false });
// Should reject timezone indicators and invalid formats
expect(() => a.parse("2022-10-13T09:52:31+00:00")).toThrow();
expect(() => a.parse("2022-10-13 09:52:31")).toThrow();
expect(() => a.parse("2022-10-13T24:52:31")).toThrow();
expect(() => a.parse("2022-10-13T24:52")).toThrow();
expect(() => a.parse("2022-10-13T24:52Z")).toThrow();
});
test("datetime parsing with local and offset", () => {
const a = z.string().datetime({ local: true, offset: true });
// expect(a.parse("2022-10-13T12:52")).toEqual("2022-10-13T12:52:00");
a.parse("2022-10-13T12:52:00");
a.parse("2022-10-13T12:52:00Z");
a.parse("2022-10-13T12:52Z");
a.parse("2022-10-13T12:52");
a.parse("2022-10-13T12:52+02:00");
expect(() => a.parse("2022-10-13T12:52:00+02")).toThrow();
// expect(() => a.parse("2022-10-13T12:52Z")).toThrow();
// expect(() => a.parse("2022-10-13T12:52+02:00")).toThrow();
});
test("date parsing", () => {
const date = z.string().date();
date.parse("1970-01-01");
date.parse("2022-01-31");
date.parse("2022-03-31");
date.parse("2022-04-30");
date.parse("2022-05-31");
date.parse("2022-06-30");
date.parse("2022-07-31");
date.parse("2022-08-31");
date.parse("2022-09-30");
date.parse("2022-10-31");
date.parse("2022-11-30");
date.parse("2022-12-31");
date.parse("2000-02-29");
date.parse("2400-02-29");
expect(() => date.parse("2022-02-29")).toThrow();
expect(() => date.parse("2100-02-29")).toThrow();
expect(() => date.parse("2200-02-29")).toThrow();
expect(() => date.parse("2300-02-29")).toThrow();
expect(() => date.parse("2500-02-29")).toThrow();
expect(() => date.parse("")).toThrow();
expect(() => date.parse("foo")).toThrow();
expect(() => date.parse("200-01-01")).toThrow();
expect(() => date.parse("20000-01-01")).toThrow();
expect(() => date.parse("2000-0-01")).toThrow();
expect(() => date.parse("2000-011-01")).toThrow();
expect(() => date.parse("2000-01-0")).toThrow();
expect(() => date.parse("2000-01-011")).toThrow();
expect(() => date.parse("2000/01/01")).toThrow();
expect(() => date.parse("01-01-2022")).toThrow();
expect(() => date.parse("01/01/2022")).toThrow();
expect(() => date.parse("2000-01-01 00:00:00Z")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29+00:00")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29Z")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29.123Z")).toThrow();
expect(() => date.parse("2000-00-12")).toThrow();
expect(() => date.parse("2000-12-00")).toThrow();
expect(() => date.parse("2000-01-32")).toThrow();
expect(() => date.parse("2000-13-01")).toThrow();
expect(() => date.parse("2000-21-01")).toThrow();
expect(() => date.parse("2000-02-30")).toThrow();
expect(() => date.parse("2000-02-31")).toThrow();
expect(() => date.parse("2000-04-31")).toThrow();
expect(() => date.parse("2000-06-31")).toThrow();
expect(() => date.parse("2000-09-31")).toThrow();
expect(() => date.parse("2000-11-31")).toThrow();
});
test("time parsing", () => {
const time = z.string().time();
time.parse("00:00:00");
time.parse("23:00:00");
time.parse("00:59:00");
time.parse("00:00:59");
time.parse("23:59:59");
time.parse("09:52:31");
time.parse("23:59:59.9999999");
time.parse("00:00");
expect(() => time.parse("")).toThrow();
expect(() => time.parse("foo")).toThrow();
expect(() => time.parse("00:00:00Z")).toThrow();
expect(() => time.parse("0:00:00")).toThrow();
expect(() => time.parse("00:0:00")).toThrow();
expect(() => time.parse("00:00:0")).toThrow();
expect(() => time.parse("00:00:00.000+00:00")).toThrow();
expect(() => time.parse("24:00:00")).toThrow();
expect(() => time.parse("00:60:00")).toThrow();
expect(() => time.parse("00:00:60")).toThrow();
expect(() => time.parse("24:60:60")).toThrow();
const time2 = z.string().time({ precision: 2 });
time2.parse("00:00:00.00");
time2.parse("09:52:31.12");
time2.parse("23:59:59.99");
expect(() => time2.parse("")).toThrow();
expect(() => time2.parse("foo")).toThrow();
expect(() => time2.parse("00:00:00")).toThrow();
expect(() => time2.parse("00:00:00.00Z")).toThrow();
expect(() => time2.parse("00:00:00.0")).toThrow();
expect(() => time2.parse("00:00:00.000")).toThrow();
expect(() => time2.parse("00:00:00.00+00:00")).toThrow();
const time3 = z.string().time({ precision: z.TimePrecision.Minute });
time3.parse("00:00");
expect(() => time3.parse("00:00:00")).toThrow();
});
test("duration", () => {
const duration = z.string().duration();
const validDurations = [
"P3Y6M4DT12H30M5S",
"P2Y9M3DT12H31M8.001S",
// "+P3Y6M4DT12H30M5S",
// "-PT0.001S",
// "+PT0.001S",
"PT0,001S",
"PT12H30M5S",
// "-P2M1D",
// "P-2M-1D",
// "-P5DT10H",
// "P-5DT-10H",
"P1Y",
"P2MT30M",
"PT6H",
"P5W",
// "P0.5Y",
// "P0,5Y",
// "P42YT7.004M",
];
const invalidDurations = [
"foo bar",
"",
" ",
"P",
"PT",
"P1Y2MT",
"T1H",
"P0.5Y1D",
"P0,5Y6M",
"P1YT",
"P-2M-1D",
"P-5DT-10H",
"P1W2D",
"-P1D",
];
for (const val of validDurations) {
const result = duration.safeParse(val);
if (!result.success) {
throw Error(`Valid duration could not be parsed: ${val}`);
}
}
for (const val of invalidDurations) {
const result = duration.safeParse(val);
if (result.success) {
throw Error(`Invalid duration was successful parsed: ${val}`);
}
expect(result.error.issues[0].message).toEqual("Invalid ISO duration");
}
});
test("redos checker", () => {
const a = z.iso.datetime();
const b = z.string().datetime({ offset: true });
const c = z.string().datetime({ local: true });
const d = z.string().datetime({ local: true, offset: true, precision: 3 });
const e = z.string().date();
const f = z.string().time();
const g = z.string().duration();
for (const schema of [a, b, c, d, e, f, g]) {
const result = checkSync(schema._zod.pattern.source, "");
if (result.status !== "safe") throw Error("ReDoS issue");
}
});

View File

@ -0,0 +1,313 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("basic defaults", () => {
expect(z.string().default("default").parse(undefined)).toBe("default");
});
test("default with optional", () => {
const schema = z.string().optional().default("default");
expect(schema.parse(undefined)).toBe("default");
expect(schema.unwrap().parse(undefined)).toBe(undefined);
});
test("default with transform", () => {
const stringWithDefault = z
.string()
.transform((val) => val.toUpperCase())
.default("default");
expect(stringWithDefault.parse(undefined)).toBe("default");
expect(stringWithDefault).toBeInstanceOf(z.ZodDefault);
expect(stringWithDefault.unwrap()).toBeInstanceOf(z.ZodPipe);
expect(stringWithDefault.unwrap().in).toBeInstanceOf(z.ZodString);
expect(stringWithDefault.unwrap().out).toBeInstanceOf(z.ZodTransform);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("default on existing optional", () => {
const stringWithDefault = z.string().optional().default("asdf");
expect(stringWithDefault.parse(undefined)).toBe("asdf");
expect(stringWithDefault).toBeInstanceOf(z.ZodDefault);
expect(stringWithDefault.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(stringWithDefault.unwrap().unwrap()).toBeInstanceOf(z.ZodString);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("optional on default", () => {
const stringWithDefault = z.string().default("asdf").optional();
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string | undefined>();
expect(stringWithDefault.parse(undefined)).toBe("asdf");
});
// test("complex chain example", () => {
// const complex = z
// .string()
// .default("asdf")
// .transform((val) => val.toUpperCase())
// .default("qwer")
// .unwrap()
// .optional()
// .default("asdfasdf");
// expect(complex.parse(undefined)).toBe("asdfasdf");
// });
test("removeDefault", () => {
const stringWithRemovedDefault = z.string().default("asdf").removeDefault();
type out = z.output<typeof stringWithRemovedDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("apply default at output", () => {
const schema = z
.string()
.transform((_) => (Math.random() > 0 ? undefined : _))
.default("asdf");
expect(schema.parse("")).toEqual("asdf");
});
test("nested", () => {
const inner = z.string().default("asdf");
const outer = z.object({ inner }).default({
inner: "qwer",
});
type input = z.input<typeof outer>;
expectTypeOf<input>().toEqualTypeOf<{ inner?: string | undefined } | undefined>();
type out = z.output<typeof outer>;
expectTypeOf<out>().toEqualTypeOf<{ inner: string }>();
expect(outer.parse(undefined)).toEqual({ inner: "qwer" });
expect(outer.parse({})).toEqual({ inner: "asdf" });
expect(outer.parse({ inner: undefined })).toEqual({ inner: "asdf" });
});
test("chained defaults", () => {
const stringWithDefault = z.string().default("inner").default("outer");
const result = stringWithDefault.parse(undefined);
expect(result).toEqual("outer");
});
test("object optionality", () => {
const schema = z.object({
hi: z.string().default("hi"),
});
type schemaInput = z.input<typeof schema>;
type schemaOutput = z.output<typeof schema>;
expectTypeOf<schemaInput>().toEqualTypeOf<{ hi?: string | undefined }>();
expectTypeOf<schemaOutput>().toEqualTypeOf<{ hi: string }>();
expect(schema.parse({})).toEqual({
hi: "hi",
});
});
test("nested prefault/default", () => {
const a = z
.string()
.default("a")
.refine((val) => val.startsWith("a"));
const b = z
.string()
.refine((val) => val.startsWith("b"))
.default("b");
const c = z
.string()
.prefault("c")
.refine((val) => val.startsWith("c"));
const d = z
.string()
.refine((val) => val.startsWith("d"))
.prefault("d");
const obj = z.object({
a,
b,
c,
d,
});
expect(obj.safeParse({ a: "a1", b: "b1", c: "c1", d: "d1" })).toMatchInlineSnapshot(`
{
"data": {
"a": "a1",
"b": "b1",
"c": "c1",
"d": "d1",
},
"success": true,
}
`);
expect(obj.safeParse({ a: "f", b: "f", c: "f", d: "f" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [
"a"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"b"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"c"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"d"
],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(obj.safeParse({})).toMatchInlineSnapshot(`
{
"data": {
"a": "a",
"b": "b",
"c": "c",
"d": "d",
},
"success": true,
}
`);
expect(obj.safeParse({ a: undefined, b: undefined, c: undefined, d: undefined })).toMatchInlineSnapshot(`
{
"data": {
"a": "a",
"b": "b",
"c": "c",
"d": "d",
},
"success": true,
}
`);
const obj2 = z.object({
a: a.optional(),
b: b.optional(),
c: c.optional(),
d: d.optional(),
});
expect(obj2.safeParse({ a: undefined, b: undefined, c: undefined, d: undefined })).toMatchInlineSnapshot(`
{
"data": {
"a": "a",
"b": "b",
"c": "c",
"d": "d",
},
"success": true,
}
`);
expect(a.parse(undefined)).toBe("a");
expect(b.parse(undefined)).toBe("b");
expect(c.parse(undefined)).toBe("c");
expect(d.parse(undefined)).toBe("d");
});
test("failing default", () => {
const a = z
.string()
.default("z")
.refine((val) => val.startsWith("a"));
const b = z
.string()
.refine((val) => val.startsWith("b"))
.default("z");
const c = z
.string()
.prefault("z")
.refine((val) => val.startsWith("c"));
const d = z
.string()
.refine((val) => val.startsWith("d"))
.prefault("z");
const obj = z.object({
a,
b,
c,
d,
});
expect(
obj.safeParse({
a: undefined,
b: undefined,
c: undefined,
d: undefined,
}).error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "custom",
"message": "Invalid input",
"path": [
"a",
],
},
{
"code": "custom",
"message": "Invalid input",
"path": [
"c",
],
},
{
"code": "custom",
"message": "Invalid input",
"path": [
"d",
],
},
]
`);
});
test("partial should not clobber defaults", () => {
const objWithDefaults = z.object({
a: z.string().default("defaultA"),
b: z.string().default("defaultB"),
c: z.string().default("defaultC"),
});
const objPartialWithOneRequired = objWithDefaults.partial(); //.required({ a: true });
const test = objPartialWithOneRequired.parse({});
expect(test).toMatchInlineSnapshot(`
{
"a": "defaultA",
"b": "defaultB",
"c": "defaultC",
}
`);
});

View File

@ -0,0 +1,32 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
const description = "a description";
// test("passing `description` to schema should add a description", () => {
// expect(z.string({ description }).description).toEqual(description);
// expect(z.number({ description }).description).toEqual(description);
// expect(z.boolean({ description }).description).toEqual(description);
// });
test(".describe", () => {
expect(z.string().describe(description).description).toEqual(description);
expect(z.number().describe(description).description).toEqual(description);
expect(z.boolean().describe(description).description).toEqual(description);
});
test("adding description with z.globalRegistry", () => {
const schema = z.string();
z.core.globalRegistry.add(schema, { description });
z.core.globalRegistry.get(schema);
expect(schema.description).toEqual(description);
});
// in Zod 4 descriptions are not inherited
// test("description should carry over to chained schemas", () => {
// const schema = z.string().describe(description);
// expect(schema.description).toEqual(description);
// expect(schema.optional().description).toEqual(description);
// expect(schema.optional().nullable().default("default").description).toEqual(description);
// });

View File

@ -0,0 +1,619 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("_values", () => {
expect(z.string()._zod.values).toEqual(undefined);
expect(z.enum(["a", "b"])._zod.values).toEqual(new Set(["a", "b"]));
expect(z.nativeEnum({ a: "A", b: "B" })._zod.values).toEqual(new Set(["A", "B"]));
expect(z.literal("test")._zod.values).toEqual(new Set(["test"]));
expect(z.literal(123)._zod.values).toEqual(new Set([123]));
expect(z.literal(true)._zod.values).toEqual(new Set([true]));
expect(z.literal(BigInt(123))._zod.values).toEqual(new Set([BigInt(123)]));
expect(z.undefined()._zod.values).toEqual(new Set([undefined]));
expect(z.null()._zod.values).toEqual(new Set([null]));
const t = z.literal("test");
expect(t.optional()._zod.values).toEqual(new Set(["test", undefined]));
expect(t.nullable()._zod.values).toEqual(new Set(["test", null]));
expect(t.default("test")._zod.values).toEqual(new Set(["test"]));
expect(t.catch("test")._zod.values).toEqual(new Set(["test"]));
const pre = z.preprocess((val) => String(val), z.string()).pipe(z.literal("test"));
expect(pre._zod.values).toEqual(undefined);
const post = z.literal("test").transform((_) => Math.random());
expect(post._zod.values).toEqual(new Set(["test"]));
// Test that readonly literals pass through their values property
expect(z.literal("test").readonly()._zod.values).toEqual(new Set(["test"]));
});
test("valid parse - object", () => {
expect(
z
.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
])
.parse({ type: "a", a: "abc" })
).toEqual({ type: "a", a: "abc" });
});
test("valid - include discriminator key (deprecated)", () => {
expect(
z
.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
])
.parse({ type: "a", a: "abc" })
).toEqual({ type: "a", a: "abc" });
});
test("valid - optional discriminator (object)", () => {
const schema = z.discriminatedUnion("type", [
z.object({ type: z.literal("a").optional(), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
]);
expect(schema.parse({ type: "a", a: "abc" })).toEqual({ type: "a", a: "abc" });
expect(schema.parse({ a: "abc" })).toEqual({ a: "abc" });
});
test("valid - discriminator value of various primitive types", () => {
const schema = z.discriminatedUnion("type", [
z.object({ type: z.literal("1"), val: z.string() }),
z.object({ type: z.literal(1), val: z.string() }),
z.object({ type: z.literal(BigInt(1)), val: z.string() }),
z.object({ type: z.literal("true"), val: z.string() }),
z.object({ type: z.literal(true), val: z.string() }),
z.object({ type: z.literal("null"), val: z.string() }),
z.object({ type: z.null(), val: z.string() }),
z.object({ type: z.literal("undefined"), val: z.string() }),
z.object({ type: z.undefined(), val: z.string() }),
]);
expect(schema.parse({ type: "1", val: "val" })).toEqual({ type: "1", val: "val" });
expect(schema.parse({ type: 1, val: "val" })).toEqual({ type: 1, val: "val" });
expect(schema.parse({ type: BigInt(1), val: "val" })).toEqual({
type: BigInt(1),
val: "val",
});
expect(schema.parse({ type: "true", val: "val" })).toEqual({
type: "true",
val: "val",
});
expect(schema.parse({ type: true, val: "val" })).toEqual({
type: true,
val: "val",
});
expect(schema.parse({ type: "null", val: "val" })).toEqual({
type: "null",
val: "val",
});
expect(schema.parse({ type: null, val: "val" })).toEqual({
type: null,
val: "val",
});
expect(schema.parse({ type: "undefined", val: "val" })).toEqual({
type: "undefined",
val: "val",
});
expect(schema.parse({ type: undefined, val: "val" })).toEqual({
type: undefined,
val: "val",
});
const fail = schema.safeParse({
type: "not_a_key",
val: "val",
});
expect(fail.error).toBeInstanceOf(z.ZodError);
});
test("invalid - null", () => {
try {
z.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
]).parse(null);
throw new Error();
} catch (e: any) {
// [
// {
// code: z.ZodIssueCode.invalid_type,
// expected: z.ZodParsedType.object,
// input: null,
// message: "Expected object, received null",
// received: z.ZodParsedType.null,
// path: [],
// },
// ];
expect(e.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "object",
"message": "Invalid input: expected object, received null",
"path": [],
},
]
`);
}
});
test("invalid discriminator value", () => {
const result = z
.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
])
.safeParse({ type: "x", a: "abc" });
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_union",
"errors": [],
"note": "No matching discriminator",
"path": [
"type"
],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});
test("invalid discriminator value - unionFallback", () => {
const result = z
.discriminatedUnion(
"type",
[z.object({ type: z.literal("a"), a: z.string() }), z.object({ type: z.literal("b"), b: z.string() })],
{ unionFallback: true }
)
.safeParse({ type: "x", a: "abc" });
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_union",
"errors": [
[
{
"code": "invalid_value",
"values": [
"a"
],
"path": [
"type"
],
"message": "Invalid input: expected \\"a\\""
}
],
[
{
"code": "invalid_value",
"values": [
"b"
],
"path": [
"type"
],
"message": "Invalid input: expected \\"b\\""
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"b"
],
"message": "Invalid input: expected string, received undefined"
}
]
],
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});
test("valid discriminator value, invalid data", () => {
const result = z
.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
])
.safeParse({ type: "a", b: "abc" });
// [
// {
// code: z.ZodIssueCode.invalid_type,
// expected: z.ZodParsedType.string,
// message: "Required",
// path: ["a"],
// received: z.ZodParsedType.undefined,
// },
// ];
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"a"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("wrong schema - missing discriminator", () => {
try {
z.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ b: z.string() }) as any,
])._zod.propValues;
throw new Error();
} catch (e: any) {
expect(e.message.includes("Invalid discriminated union option")).toBe(true);
}
});
// removed to account for unions of unions
// test("wrong schema - duplicate discriminator values", () => {
// try {
// z.discriminatedUnion("type",[
// z.object({ type: z.literal("a"), a: z.string() }),
// z.object({ type: z.literal("a"), b: z.string() }),
// ]);
// throw new Error();
// } catch (e: any) {
// expect(e.message.includes("Duplicate discriminator value")).toEqual(true);
// }
// });
test("async - valid", async () => {
const schema = await z.discriminatedUnion("type", [
z.object({
type: z.literal("a"),
a: z
.string()
.refine(async () => true)
.transform(async (val) => Number(val)),
}),
z.object({
type: z.literal("b"),
b: z.string(),
}),
]);
const data = { type: "a", a: "1" };
const result = await schema.safeParseAsync(data);
expect(result.data).toEqual({ type: "a", a: 1 });
});
test("async - invalid", async () => {
// try {
const a = z.discriminatedUnion("type", [
z.object({
type: z.literal("a"),
a: z
.string()
.refine(async () => true)
.transform(async (val) => val),
}),
z.object({
type: z.literal("b"),
b: z.string(),
}),
]);
const result = await a.safeParseAsync({ type: "a", a: 1 });
// expect(JSON.parse(e.message)).toEqual([
// {
// code: "invalid_type",
// expected: "string",
// input: 1,
// received: "number",
// path: ["a"],
// message: "Expected string, received number",
// },
// ]);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"a"
],
"message": "Invalid input: expected string, received number"
}
]]
`);
});
test("valid - literals with .default or .pipe", () => {
const schema = z.discriminatedUnion("type", [
z.object({
type: z.literal("foo").default("foo"),
a: z.string(),
}),
z.object({
type: z.literal("custom"),
method: z.string(),
}),
z.object({
type: z.literal("bar").transform((val) => val),
c: z.string(),
}),
]);
expect(schema.parse({ type: "foo", a: "foo" })).toEqual({
type: "foo",
a: "foo",
});
});
test("enum and nativeEnum", () => {
enum MyEnum {
d = 0,
e = "e",
}
const schema = z.discriminatedUnion("key", [
z.object({
key: z.literal("a"),
// Add other properties specific to this option
}),
z.object({
key: z.enum(["b", "c"]),
// Add other properties specific to this option
}),
z.object({
key: z.nativeEnum(MyEnum),
// Add other properties specific to this option
}),
]);
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<{ key: "a" } | { key: "b" | "c" } | { key: MyEnum.d | MyEnum.e }>();
schema.parse({ key: "a" });
schema.parse({ key: "b" });
schema.parse({ key: "c" });
schema.parse({ key: MyEnum.d });
schema.parse({ key: MyEnum.e });
schema.parse({ key: "e" });
});
test("branded", () => {
const schema = z.discriminatedUnion("key", [
z.object({
key: z.literal("a"),
// Add other properties specific to this option
}),
z.object({
key: z.literal("b").brand<"asdfasdf">(),
// Add other properties specific to this option
}),
]);
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<{ key: "a" } | { key: "b" & z.core.$brand<"asdfasdf"> }>();
schema.parse({ key: "a" });
schema.parse({ key: "b" });
expect(() => {
schema.parse({ key: "c" });
}).toThrow();
});
test("optional and nullable", () => {
const schema = z.discriminatedUnion("key", [
z.object({
key: z.literal("a").optional(),
a: z.literal(true),
}),
z.object({
key: z.literal("b").nullable(),
b: z.literal(true),
// Add other properties specific to this option
}),
]);
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<{ key?: "a" | undefined; a: true } | { key: "b" | null; b: true }>();
schema.parse({ key: "a", a: true });
schema.parse({ key: undefined, a: true });
schema.parse({ key: "b", b: true });
schema.parse({ key: null, b: true });
expect(() => {
schema.parse({ key: null, a: true });
}).toThrow();
expect(() => {
schema.parse({ key: "b", a: true });
}).toThrow();
const value = schema.parse({ key: null, b: true });
if (!("key" in value)) value.a;
if (value.key === undefined) value.a;
if (value.key === "a") value.a;
if (value.key === "b") value.b;
if (value.key === null) value.b;
});
test("multiple discriminators", () => {
const FreeConfig = z.object({
type: z.literal("free"),
min_cents: z.null(),
});
// console.log(FreeConfig.shape.type);
const PricedConfig = z.object({
type: z.literal("fiat-price"),
// min_cents: z.int().nullable(),
min_cents: z.null(),
});
const Config = z.discriminatedUnion("type", [FreeConfig, PricedConfig]);
Config.parse({
min_cents: null,
type: "fiat-price",
name: "Standard",
});
expect(() => {
Config.parse({
min_cents: null,
type: "not real",
name: "Standard",
});
}).toThrow();
});
test("single element union", () => {
const schema = z.object({
a: z.literal("discKey"),
b: z.enum(["apple", "banana"]),
c: z.object({ id: z.string() }),
});
const input = {
a: "discKey",
b: "apple",
c: {}, // Invalid, as schema requires `id` property
};
// Validation must fail here, but it doesn't
const u = z.discriminatedUnion("a", [schema]);
const result = u.safeParse(input);
expect(result).toMatchObject({ success: false });
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"c",
"id"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
expect(u.options.length).toEqual(1);
});
test("nested discriminated unions", () => {
const BaseError = z.object({ status: z.literal("failed"), message: z.string() });
const MyErrors = z.discriminatedUnion("code", [
BaseError.extend({ code: z.literal(400) }),
BaseError.extend({ code: z.literal(401) }),
BaseError.extend({ code: z.literal(500) }),
]);
const MyResult = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.string() }),
MyErrors,
]);
expect(MyErrors._zod.propValues).toMatchInlineSnapshot(`
{
"code": Set {
400,
401,
500,
},
"status": Set {
"failed",
},
}
`);
expect(MyResult._zod.propValues).toMatchInlineSnapshot(`
{
"code": Set {
400,
401,
500,
},
"status": Set {
"success",
"failed",
},
}
`);
const result = MyResult.parse({ status: "success", data: "hello" });
expect(result).toMatchInlineSnapshot(`
{
"data": "hello",
"status": "success",
}
`);
const result2 = MyResult.parse({ status: "failed", code: 400, message: "bad request" });
expect(result2).toMatchInlineSnapshot(`
{
"code": 400,
"message": "bad request",
"status": "failed",
}
`);
const result3 = MyResult.parse({ status: "failed", code: 401, message: "unauthorized" });
expect(result3).toMatchInlineSnapshot(`
{
"code": 401,
"message": "unauthorized",
"status": "failed",
}
`);
const result4 = MyResult.parse({ status: "failed", code: 500, message: "internal server error" });
expect(result4).toMatchInlineSnapshot(`
{
"code": 500,
"message": "internal server error",
"status": "failed",
}
`);
});
test("readonly literal discriminator", () => {
const discUnion = z.discriminatedUnion("type", [
z.object({ type: z.literal("a").readonly(), a: z.string() }),
z.object({ type: z.literal("b"), b: z.number() }),
]);
// Test that both discriminator values are correctly included in propValues
const propValues = discUnion._zod.propValues;
expect(propValues?.type?.has("a")).toBe(true);
expect(propValues?.type?.has("b")).toBe(true);
// Test that the discriminated union works correctly
const result1 = discUnion.parse({ type: "a", a: "hello" });
expect(result1).toEqual({ type: "a", a: "hello" });
const result2 = discUnion.parse({ type: "b", b: 42 });
expect(result2).toEqual({ type: "b", b: 42 });
// Test that invalid discriminator values are rejected
expect(() => {
discUnion.parse({ type: "c", a: "hello" });
}).toThrow();
});

View File

@ -0,0 +1,285 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("enum from string array", () => {
const MyEnum = z.enum(["Red", "Green", "Blue"]);
expect(MyEnum.enum.Red).toEqual("Red");
type MyEnum = z.infer<typeof MyEnum>;
expectTypeOf<MyEnum>().toEqualTypeOf<"Red" | "Green" | "Blue">();
});
test("enum from const object", () => {
const Fruits: { Apple: "apple"; Banana: "banana" } = {
Apple: "apple",
Banana: "banana",
};
const fruitEnum = z.nativeEnum(Fruits);
type fruitEnum = z.infer<typeof fruitEnum>;
fruitEnum.parse("apple");
fruitEnum.parse("banana");
fruitEnum.parse(Fruits.Apple);
fruitEnum.parse(Fruits.Banana);
expectTypeOf<fruitEnum>().toEqualTypeOf<"apple" | "banana">();
});
test("enum from native enum", () => {
enum Fruits {
Apple = "apple",
Banana = "banana",
Orange = 3,
}
// @ts-ignore
const fruitEnum = z.nativeEnum(Fruits);
type fruitEnum = z.infer<typeof fruitEnum>;
fruitEnum.parse("apple");
fruitEnum.parse("banana");
fruitEnum.parse(Fruits.Apple);
fruitEnum.parse(Fruits.Banana);
expect(fruitEnum.safeParse("Apple").success).toEqual(false);
expect(fruitEnum.safeParse("Cantaloupe").success).toEqual(false);
expectTypeOf<fruitEnum>().toMatchTypeOf<Fruits>();
expectTypeOf<Fruits>().toMatchTypeOf<fruitEnum>();
});
test("enum from native enum with numeric keys", () => {
const FruitValues = {
Apple: 10,
Banana: 20,
// @ts-ignore
} as const;
const fruitEnum = z.nativeEnum(FruitValues);
type fruitEnum = z.infer<typeof fruitEnum>;
fruitEnum.parse(10);
fruitEnum.parse(20);
fruitEnum.parse(FruitValues.Apple);
fruitEnum.parse(FruitValues.Banana);
expectTypeOf<fruitEnum>().toEqualTypeOf<10 | 20>();
});
test("issue metadata", () => {
const schema = z.enum(["Red", "Green", "Blue"]);
const result = schema.safeParse("Yellow");
expect(result.error!.issues[0]).toMatchInlineSnapshot(`
{
"code": "invalid_value",
"message": "Invalid option: expected one of "Red"|"Green"|"Blue"",
"path": [],
"values": [
"Red",
"Green",
"Blue",
],
}
`);
});
test("enum from non-const inputs", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"];
const FoodEnum = z.enum(foods);
expectTypeOf<z.infer<typeof FoodEnum>>().toEqualTypeOf<string>();
expect(FoodEnum.safeParse("Pasta").success).toEqual(true);
expect(FoodEnum.safeParse("Cucumbers").success).toEqual(false);
});
test("get options", () => {
expect(z.enum(["tuna", "trout"]).options).toEqual(["tuna", "trout"]);
});
test("readonly enum", () => {
const HTTP_SUCCESS = ["200", "201"] as const;
const arg = z.enum(HTTP_SUCCESS);
type arg = z.infer<typeof arg>;
expectTypeOf<arg>().toEqualTypeOf<"200" | "201">();
arg.parse("201");
expect(() => arg.parse("202")).toThrow();
});
test("error map", () => {
const result = z
.enum(["test"], { error: (iss) => (iss.input === undefined ? "REQUIRED" : undefined) })
.safeParse(undefined);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].message).toEqual("REQUIRED");
}
});
test("type signatures", () => {
const a = z.enum(["a", "b", "c"]);
const b = z.enum(a.options);
expectTypeOf(a).toEqualTypeOf(b);
const c = z.enum({ a: 1, b: 2 } as const);
expectTypeOf(c.enum).toEqualTypeOf<{
readonly a: 1;
readonly b: 2;
}>();
enum Fruit {
Apple = "apple",
Banana = "banana",
Orange = "orange",
}
const d = z.enum(Fruit);
expectTypeOf(d.enum).toEqualTypeOf(Fruit);
const e = z.enum({ a: 1, b: 2 });
expectTypeOf(e.enum).toEqualTypeOf<{
readonly a: 1;
readonly b: 2;
}>();
});
test("extract", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods);
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
expect(ItalianEnum.safeParse("Pasta").success).toEqual(true);
expect(ItalianEnum.safeParse("Tacos").success).toEqual(false);
expectTypeOf<z.infer<typeof ItalianEnum>>().toEqualTypeOf<"Pasta" | "Pizza">();
});
test("exclude", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods);
const UnhealthyEnum = FoodEnum.exclude(["Salad"]);
expect(UnhealthyEnum.safeParse("Pasta").success).toEqual(true);
expect(UnhealthyEnum.safeParse("Salad").success).toEqual(false);
expectTypeOf<z.infer<typeof UnhealthyEnum>>().toEqualTypeOf<"Pasta" | "Pizza" | "Tacos" | "Burgers">();
const EmptyFoodEnum = FoodEnum.exclude(foods);
expectTypeOf<typeof EmptyFoodEnum>().toEqualTypeOf<z.ZodEnum<{}>>();
expectTypeOf<z.infer<typeof EmptyFoodEnum>>().toEqualTypeOf<never>();
});
test("error map inheritance", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods, { error: () => "This is not food!" });
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
const foodsError = FoodEnum.safeParse("Cucumbers");
const italianError = ItalianEnum.safeParse("Tacos");
expect(foodsError.error!.issues[0].message).toEqual(italianError.error!.issues[0].message);
const UnhealthyEnum = FoodEnum.exclude(["Salad"], {
error: () => ({ message: "This is not healthy food!" }),
});
const unhealthyError = UnhealthyEnum.safeParse("Salad");
if (!unhealthyError.success) {
expect(unhealthyError.error.issues[0].message).toEqual("This is not healthy food!");
}
});
test("readonly in ZodEnumDef", () => {
type _a = z.ZodEnum<{ readonly a: "a"; readonly b: "b" }>;
type _b = z.ZodEnum<{ a: "a"; b: "b" }>;
});
test("enum error message, invalid enum elementstring", () => {
const result = z.enum(["Tuna", "Trout"]).safeParse("Salmon");
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna",
"Trout"
],
"path": [],
"message": "Invalid option: expected one of \\"Tuna\\"|\\"Trout\\""
}
]]
`);
});
test("enum error message, invalid type", () => {
const result = z.enum(["Tuna", "Trout"]).safeParse(12);
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
// expect(result.error!.issues[0].message).toEqual('Invalid input: expected one of "Tuna"|"Trout"');
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna",
"Trout"
],
"path": [],
"message": "Invalid option: expected one of \\"Tuna\\"|\\"Trout\\""
}
]]
`);
});
test("nativeEnum default error message", () => {
enum Fish {
Tuna = "Tuna",
Trout = "Trout",
}
const result = z.nativeEnum(Fish).safeParse("Salmon");
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
// expect(result.error!.issues[0].message).toEqual('Invalid input: expected one of "Tuna"|"Trout"');
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna",
"Trout"
],
"path": [],
"message": "Invalid option: expected one of \\"Tuna\\"|\\"Trout\\""
}
]]
`);
});
test("enum with message returns the custom error message", () => {
const schema = z.enum(["apple", "banana"], {
message: "the value provided is invalid",
});
const result1 = schema.safeParse("berries");
expect(result1.success).toEqual(false);
if (!result1.success) {
expect(result1.error.issues[0].message).toEqual("the value provided is invalid");
}
const result2 = schema.safeParse(undefined);
expect(result2.success).toEqual(false);
if (!result2.success) {
expect(result2.error.issues[0].message).toEqual("the value provided is invalid");
}
const result3 = schema.safeParse("banana");
expect(result3.success).toEqual(true);
const result4 = schema.safeParse(null);
expect(result4.success).toEqual(false);
if (!result4.success) {
expect(result4.error.issues[0].message).toEqual("the value provided is invalid");
}
});
test("enum with diagonal keys", () => {
const schema_02 = z.enum({
A: 1,
B: "A",
});
expect(schema_02.safeParse("A")).toMatchObject({ success: true });
});

View File

@ -0,0 +1,527 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
declare const iss: z.core.$ZodIssueCode;
const Test = z.object({
f1: z.number(),
f2: z.string().optional(),
f3: z.string().nullable(),
f4: z.array(z.object({ t: z.union([z.string(), z.boolean()]) })),
});
// type TestFlattenedErrors = core.inferFlattenedErrors<typeof Test, { message: string; code: number }>;
// type TestFormErrors = core.inferFlattenedErrors<typeof Test>;
const parsed = Test.safeParse({});
test("regular error", () => {
expect(parsed).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
"f1"
],
"message": "Invalid input: expected number, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"f3"
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "array",
"code": "invalid_type",
"path": [
"f4"
],
"message": "Invalid input: expected array, received undefined"
}
]],
"success": false,
}
`);
});
test(".flatten()", () => {
const flattened = parsed.error!.flatten();
// flattened.
expectTypeOf(flattened).toMatchTypeOf<{
formErrors: string[];
fieldErrors: {
f2?: string[];
f1?: string[];
f3?: string[];
f4?: string[];
};
}>();
expect(flattened).toMatchInlineSnapshot(`
{
"fieldErrors": {
"f1": [
"Invalid input: expected number, received undefined",
],
"f3": [
"Invalid input: expected string, received undefined",
],
"f4": [
"Invalid input: expected array, received undefined",
],
},
"formErrors": [],
}
`);
});
test("custom .flatten()", () => {
type ErrorType = { message: string; code: number };
const flattened = parsed.error!.flatten((iss) => ({ message: iss.message, code: 1234 }));
expectTypeOf(flattened).toMatchTypeOf<{
formErrors: ErrorType[];
fieldErrors: {
f2?: ErrorType[];
f1?: ErrorType[];
f3?: ErrorType[];
f4?: ErrorType[];
};
}>();
expect(flattened).toMatchInlineSnapshot(`
{
"fieldErrors": {
"f1": [
{
"code": 1234,
"message": "Invalid input: expected number, received undefined",
},
],
"f3": [
{
"code": 1234,
"message": "Invalid input: expected string, received undefined",
},
],
"f4": [
{
"code": 1234,
"message": "Invalid input: expected array, received undefined",
},
],
},
"formErrors": [],
}
`);
});
test(".format()", () => {
const formatted = parsed.error!.format();
expectTypeOf(formatted).toMatchTypeOf<{
_errors: string[];
f2?: { _errors: string[] };
f1?: { _errors: string[] };
f3?: { _errors: string[] };
f4?: {
[x: number]: {
_errors: string[];
t?: {
_errors: string[];
};
};
_errors: string[];
};
}>();
expect(formatted).toMatchInlineSnapshot(`
{
"_errors": [],
"f1": {
"_errors": [
"Invalid input: expected number, received undefined",
],
},
"f3": {
"_errors": [
"Invalid input: expected string, received undefined",
],
},
"f4": {
"_errors": [
"Invalid input: expected array, received undefined",
],
},
}
`);
});
test("custom .format()", () => {
type ErrorType = { message: string; code: number };
const formatted = parsed.error!.format((iss) => ({ message: iss.message, code: 1234 }));
expectTypeOf(formatted).toMatchTypeOf<{
_errors: ErrorType[];
f2?: { _errors: ErrorType[] };
f1?: { _errors: ErrorType[] };
f3?: { _errors: ErrorType[] };
f4?: {
[x: number]: {
_errors: ErrorType[];
t?: {
_errors: ErrorType[];
};
};
_errors: ErrorType[];
};
}>();
expect(formatted).toMatchInlineSnapshot(`
{
"_errors": [],
"f1": {
"_errors": [
{
"code": 1234,
"message": "Invalid input: expected number, received undefined",
},
],
},
"f3": {
"_errors": [
{
"code": 1234,
"message": "Invalid input: expected string, received undefined",
},
],
},
"f4": {
"_errors": [
{
"code": 1234,
"message": "Invalid input: expected array, received undefined",
},
],
},
}
`);
});
test("all errors", () => {
const propertySchema = z.string();
const schema = z
.object({
a: propertySchema,
b: propertySchema,
})
.refine(
(val) => {
return val.a === val.b;
},
{ message: "Must be equal" }
);
const r1 = schema.safeParse({
a: "asdf",
b: "qwer",
});
expect(z.core.flattenError(r1.error!)).toEqual({
formErrors: ["Must be equal"],
fieldErrors: {},
});
const r2 = schema.safeParse({
a: null,
b: null,
});
// const error = _error as z.ZodError;
expect(z.core.flattenError(r2.error!)).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
"Invalid input: expected string, received null",
],
"b": [
"Invalid input: expected string, received null",
],
},
"formErrors": [],
}
`);
expect(z.core.flattenError(r2.error!, (iss) => iss.message.toUpperCase())).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
"INVALID INPUT: EXPECTED STRING, RECEIVED NULL",
],
"b": [
"INVALID INPUT: EXPECTED STRING, RECEIVED NULL",
],
},
"formErrors": [],
}
`);
// Test identity
expect(z.core.flattenError(r2.error!, (i: z.ZodIssue) => i)).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received null",
"path": [
"a",
],
},
],
"b": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received null",
"path": [
"b",
],
},
],
},
"formErrors": [],
}
`);
// Test mapping
const f1 = z.core.flattenError(r2.error!, (i: z.ZodIssue) => i.message.length);
expect(f1).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
45,
],
"b": [
45,
],
},
"formErrors": [],
}
`);
// expect(f1.fieldErrors.a![0]).toEqual("Invalid input: expected string".length);
// expect(f1).toMatchObject({
// formErrors: [],
// fieldErrors: {
// a: ["Invalid input: expected string".length],
// b: ["Invalid input: expected string".length],
// },
// });
});
const schema = z.strictObject({
username: z.string(),
favoriteNumbers: z.array(z.number()),
nesting: z.object({
a: z.string(),
}),
});
const result = schema.safeParse({
username: 1234,
favoriteNumbers: [1234, "4567"],
nesting: {
a: 123,
},
extra: 1234,
});
test("z.treeifyError", () => {
expect(z.treeifyError(result.error!)).toMatchInlineSnapshot(`
{
"errors": [
"Unrecognized key: "extra"",
],
"properties": {
"favoriteNumbers": {
"errors": [],
"items": [
,
{
"errors": [
"Invalid input: expected number, received string",
],
},
],
},
"nesting": {
"errors": [],
"properties": {
"a": {
"errors": [
"Invalid input: expected string, received number",
],
},
},
},
"username": {
"errors": [
"Invalid input: expected string, received number",
],
},
},
}
`);
});
test("z.treeifyError 2", () => {
const schema = z.strictObject({
name: z.string(),
logLevel: z.union([z.string(), z.number()]),
env: z.literal(["production", "development"]),
});
const data = {
name: 1000,
logLevel: false,
extra: 1000,
};
const result = schema.safeParse(data);
const err = z.treeifyError(result.error!);
expect(err).toMatchInlineSnapshot(`
{
"errors": [
"Unrecognized key: "extra"",
],
"properties": {
"env": {
"errors": [
"Invalid option: expected one of "production"|"development"",
],
},
"logLevel": {
"errors": [
"Invalid input: expected string, received boolean",
"Invalid input: expected number, received boolean",
],
},
"name": {
"errors": [
"Invalid input: expected string, received number",
],
},
},
}
`);
});
test("z.prettifyError", () => {
expect(z.prettifyError(result.error!)).toMatchInlineSnapshot(`
"✖ Unrecognized key: "extra"
✖ Invalid input: expected string, received number
→ at username
✖ Invalid input: expected number, received string
→ at favoriteNumbers[1]
✖ Invalid input: expected string, received number
→ at nesting.a"
`);
});
test("z.toDotPath", () => {
expect(z.core.toDotPath(["a", "b", 0, "c"])).toMatchInlineSnapshot(`"a.b[0].c"`);
expect(z.core.toDotPath(["a", Symbol("b"), 0, "c"])).toMatchInlineSnapshot(`"a["Symbol(b)"][0].c"`);
// Test with periods in keys
expect(z.core.toDotPath(["user.name", "first.last"])).toMatchInlineSnapshot(`"["user.name"]["first.last"]"`);
// Test with special characters
expect(z.core.toDotPath(["user", "$special", Symbol("#symbol")])).toMatchInlineSnapshot(
`"user.$special["Symbol(#symbol)"]"`
);
// Test with dots and quotes
expect(z.core.toDotPath(["search", `query("foo.bar"="abc")`])).toMatchInlineSnapshot(
`"search["query(\\"foo.bar\\"=\\"abc\\")"]"`
);
// Test with newlines
expect(z.core.toDotPath(["search", `foo\nbar`])).toMatchInlineSnapshot(`"search["foo\\nbar"]"`);
// Test with empty strings
expect(z.core.toDotPath(["", "empty"])).toMatchInlineSnapshot(`".empty"`);
// Test with array indices
expect(z.core.toDotPath(["items", 0, 1, 2])).toMatchInlineSnapshot(`"items[0][1][2]"`);
// Test with mixed path elements
expect(z.core.toDotPath(["users", "user.config", 0, "settings.theme"])).toMatchInlineSnapshot(
`"users["user.config"][0]["settings.theme"]"`
);
// Test with square brackets in keys
expect(z.core.toDotPath(["data[0]", "value"])).toMatchInlineSnapshot(`"["data[0]"].value"`);
// Test with empty path
expect(z.core.toDotPath([])).toMatchInlineSnapshot(`""`);
});
test("inheritance", () => {
const e1 = new z.ZodError([]);
expect(e1).toBeInstanceOf(z.core.$ZodError);
expect(e1).toBeInstanceOf(z.ZodError);
// expect(e1).not.toBeInstanceOf(Error);
const e2 = new z.ZodRealError([]);
expect(e2).toBeInstanceOf(z.ZodError);
expect(e2).toBeInstanceOf(z.ZodRealError);
expect(e2).toBeInstanceOf(Error);
});
test("disc union treeify/format", () => {
const schema = z.discriminatedUnion(
"foo",
[
z.object({
foo: z.literal("x"),
x: z.string(),
}),
z.object({
foo: z.literal("y"),
y: z.string(),
}),
],
{
error: "Invalid discriminator",
}
);
const error = schema.safeParse({ foo: "invalid" }).error;
expect(z.treeifyError(error!)).toMatchInlineSnapshot(`
{
"errors": [],
"properties": {
"foo": {
"errors": [
"Invalid discriminator",
],
},
},
}
`);
expect(z.prettifyError(error!)).toMatchInlineSnapshot(`
"✖ Invalid discriminator
→ at foo"
`);
expect(z.formatError(error!)).toMatchInlineSnapshot(`
{
"_errors": [],
"foo": {
"_errors": [
"Invalid discriminator",
],
},
}
`);
});

View File

@ -0,0 +1,711 @@
import { inspect } from "node:util";
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("error creation", () => {
const err1 = new z.ZodError([]);
err1.issues.push({
code: "invalid_type",
expected: "object",
path: [],
message: "",
input: "adf",
});
err1.isEmpty;
const err2 = new z.ZodError(err1.issues);
const err3 = new z.ZodError([]);
err3.addIssues(err1.issues);
err3.addIssue(err1.issues[0]);
err1.message;
err2.message;
err3.message;
});
test("do not allow error and message together", () => {
expect(() =>
z.string().refine((_) => true, {
message: "override",
error: (iss) => (iss.input === undefined ? "asdf" : null),
})
).toThrow();
});
const errorMap: z.ZodErrorMap = (issue) => {
if (issue.code === "invalid_type") {
if (issue.expected === "string") {
return { message: "bad type!" };
}
}
if (issue.code === "custom") {
return { message: `less-than-${issue.params?.minimum}` };
}
return undefined;
};
test("type error with custom error map", () => {
const result = z.string().safeParse(234, { error: errorMap });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "bad type!"
}
]]
`);
});
test("refinement fail with params", () => {
const result = z
.number()
.refine((val) => val >= 3, {
params: { minimum: 3 },
})
.safeParse(2, { error: errorMap });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"params": {
"minimum": 3
},
"message": "less-than-3"
}
]]
`);
});
test("hard coded error with custom errormap", () => {
const result = z
.string()
.refine((val) => val.length > 12, {
params: { minimum: 13 },
message: "override",
})
.safeParse("asdf", { error: () => "contextual" });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"params": {
"minimum": 13
},
"message": "override"
}
]]
`);
});
test("default error message", () => {
const result = z
.number()
.refine((x) => x > 3)
.safeParse(2);
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});
test("override error in refine", () => {
const result = z
.number()
.refine((x) => x > 3, "override")
.safeParse(2);
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "override"
}
]]
`);
});
test("override error in refinement", () => {
const result = z
.number()
.refine((x) => x > 3, {
message: "override",
})
.safeParse(2);
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "override"
}
]]
`);
});
test("array minimum", () => {
let result = z.array(z.string()).min(3, "tooshort").safeParse(["asdf", "qwer"]);
expect(result.success).toBe(false);
expect(result.error!.issues[0].code).toEqual("too_small");
expect(result.error!.issues[0].message).toEqual("tooshort");
result = z.array(z.string()).min(3).safeParse(["asdf", "qwer"]);
expect(result.success).toBe(false);
expect(result.error!.issues[0].code).toEqual("too_small");
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "array",
"code": "too_small",
"minimum": 3,
"inclusive": true,
"path": [],
"message": "Too small: expected array to have >=3 items"
}
]]
`);
});
test("literal bigint default error message", () => {
const result = z.literal(BigInt(12)).safeParse(BigInt(13));
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"12"
],
"path": [],
"message": "Invalid input: expected 12n"
}
]]
`);
});
test("custom path in custom error map", () => {
const schema = z.object({
items: z.array(z.string()).refine((data) => data.length > 3, {
path: ["items-too-few"],
}),
});
const errorMap: z.ZodErrorMap = (issue) => {
expect((issue.path ?? []).length).toBe(2);
return { message: "doesnt matter" };
};
const result = schema.safeParse({ items: ["first"] }, { error: errorMap });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"items",
"items-too-few"
],
"message": "doesnt matter"
}
]]
`);
});
// test("error metadata from value", () => {
// const dynamicRefine = z.string().refine(
// (val) => val === val.toUpperCase(),
// (val) => ({ params: { val } })
// );
// const result = dynamicRefine.safeParse("asdf");
// expect(result.success).toEqual(false);
// if (!result.success) {
// const sub = result.error.issues[0];
// expect(result.error.issues[0].code).toEqual("custom");
// if (sub.code === "custom") {
// expect(sub.params?.val).toEqual("asdf");
// }
// }
// });
// test("don't call refine after validation failed", () => {
// const asdf = z
// .union([
// z.number(),
// z.string().transform(z.number(), (val) => {
// return parseFloat(val);
// }),
// ])
// .refine((v) => v >= 1);
// expect(() => asdf.safeParse("foo")).not.toThrow();
// });
test("root level formatting", () => {
const schema = z.string().email();
const result = schema.safeParse("asdfsdf");
expect(result.success).toBe(false);
expect(result.error!.format()).toMatchInlineSnapshot(`
{
"_errors": [
"Invalid email address",
],
}
`);
});
test("custom path", () => {
const schema = z
.object({
password: z.string(),
confirm: z.string(),
})
.refine((val) => val.confirm === val.password, { path: ["confirm"] });
const result = schema.safeParse({
password: "peanuts",
confirm: "qeanuts",
});
expect(result.success).toBe(false);
const error = result.error!.format();
expect(error._errors).toEqual([]);
expect(error.password?._errors).toEqual(undefined);
expect(error.confirm?._errors).toEqual(["Invalid input"]);
});
test("custom path", () => {
const schema = z
.object({
password: z.string().min(6),
confirm: z.string().min(6),
})
.refine((val) => val.confirm === val.password);
const result = schema.safeParse({
password: "qwer",
confirm: "asdf",
});
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(3);
});
const schema = z.object({
inner: z.object({
name: z
.string()
.refine((val) => val.length > 5)
.array()
.refine((val) => val.length <= 1),
}),
});
test("no abort early on refinements", () => {
const invalidItem = {
inner: { name: ["aasd", "asdfasdfasfd"] },
};
const result1 = schema.safeParse(invalidItem);
expect(result1.success).toBe(false);
expect(result1.error!.issues.length).toEqual(2);
});
test("detect issue with input fallback", () => {
const schema = z
.string()
.transform((val) => val.length)
.refine(() => false, { message: "always fails" })
.refine(
(val) => {
if (typeof val !== "number") throw new Error();
return (val ^ 2) > 10;
} // should be number but it's a string
);
expect(() => schema.parse("hello")).toThrow(z.ZodError);
});
test("formatting", () => {
const invalidItem = {
inner: { name: ["aasd", "asdfasdfasfd"] },
};
const invalidArray = {
inner: { name: ["asdfasdf", "asdfasdfasfd"] },
};
const result1 = schema.safeParse(invalidItem);
const result2 = schema.safeParse(invalidArray);
expect(result1.success).toBe(false);
expect(result2.success).toBe(false);
const error1 = result1.error!.format();
expect(error1._errors).toEqual([]);
expect(error1.inner?._errors).toEqual([]);
expect(error1.inner?.name?.[1]).toEqual(undefined);
type FormattedError = z.inferFormattedError<typeof schema>;
const error2: FormattedError = result2.error!.format();
expect(error2._errors).toEqual([]);
expect(error2.inner?._errors).toEqual([]);
expect(error2.inner?.name?._errors).toEqual(["Invalid input"]);
expect(error2.inner?.name?.[0]).toEqual(undefined);
expect(error2.inner?.name?.[1]).toEqual(undefined);
expect(error2.inner?.name?.[2]).toEqual(undefined);
// test custom mapper
type FormattedErrorWithNumber = z.inferFormattedError<typeof schema, number>;
const errorWithNumber: FormattedErrorWithNumber = result2.error!.format(() => 5);
expect(errorWithNumber._errors).toEqual([]);
expect(errorWithNumber.inner?._errors).toEqual([]);
expect(errorWithNumber.inner?.name?._errors).toEqual([5]);
});
test("formatting with nullable and optional fields", () => {
const nameSchema = z.string().refine((val) => val.length > 5);
const schema = z.object({
nullableObject: z.object({ name: nameSchema }).nullable(),
nullableArray: z.array(nameSchema).nullable(),
nullableTuple: z.tuple([nameSchema, nameSchema, z.number()]).nullable(),
optionalObject: z.object({ name: nameSchema }).optional(),
optionalArray: z.array(nameSchema).optional(),
optionalTuple: z.tuple([nameSchema, nameSchema, z.number()]).optional(),
});
const invalidItem = {
nullableObject: { name: "abcd" },
nullableArray: ["abcd"],
nullableTuple: ["abcd", "abcd", 1],
optionalObject: { name: "abcd" },
optionalArray: ["abcd"],
optionalTuple: ["abcd", "abcd", 1],
};
const result = schema.safeParse(invalidItem);
expect(result.success).toBe(false);
const error: z.inferFormattedError<typeof schema> = result.error!.format();
expect(error._errors).toEqual([]);
expect(error.nullableObject?._errors).toEqual([]);
expect(error.nullableObject?.name?._errors).toEqual(["Invalid input"]);
expect(error.nullableArray?._errors).toEqual([]);
expect(error.nullableArray?.[0]?._errors).toEqual(["Invalid input"]);
expect(error.nullableTuple?._errors).toEqual([]);
expect(error.nullableTuple?.[0]?._errors).toEqual(["Invalid input"]);
expect(error.nullableTuple?.[1]?._errors).toEqual(["Invalid input"]);
expect(error.optionalObject?._errors).toEqual([]);
expect(error.optionalObject?.name?._errors).toEqual(["Invalid input"]);
expect(error.optionalArray?._errors).toEqual([]);
expect(error.optionalArray?.[0]?._errors).toEqual(["Invalid input"]);
expect(error.optionalTuple?._errors).toEqual([]);
expect(error.optionalTuple?.[0]?._errors).toEqual(["Invalid input"]);
expect(error.optionalTuple?.[1]?._errors).toEqual(["Invalid input"]);
expect(error).toMatchInlineSnapshot(`
{
"_errors": [],
"nullableArray": {
"0": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
"nullableObject": {
"_errors": [],
"name": {
"_errors": [
"Invalid input",
],
},
},
"nullableTuple": {
"0": {
"_errors": [
"Invalid input",
],
},
"1": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
"optionalArray": {
"0": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
"optionalObject": {
"_errors": [],
"name": {
"_errors": [
"Invalid input",
],
},
},
"optionalTuple": {
"0": {
"_errors": [
"Invalid input",
],
},
"1": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
}
`);
});
test("inferFlattenedErrors", () => {
const schemaWithTransform = z.object({ foo: z.string() }).transform((o) => ({ bar: o.foo }));
const result = schemaWithTransform.safeParse({});
expect(result.success).toBe(false);
type ValidationErrors = z.inferFlattenedErrors<typeof schemaWithTransform>;
const error: ValidationErrors = result.error!.flatten();
expect(error).toMatchInlineSnapshot(`
{
"fieldErrors": {
"foo": [
"Invalid input: expected string, received undefined",
],
},
"formErrors": [],
}
`);
});
const stringWithCustomError = z.string({
error: () => "bound",
});
test("schema-bound error map", () => {
const result = stringWithCustomError.safeParse(1234);
expect(result.success).toBe(false);
expect(result.error!.issues[0].message).toEqual("bound");
});
test("bound error map overrides contextual", () => {
// support contextual override
const result = stringWithCustomError.safeParse(undefined, {
error: () => ({ message: "override" }),
});
expect(result.success).toBe(false);
expect(result.error!.issues[0].message).toEqual("bound");
});
test("z.config customError ", () => {
// support overrideErrorMap
z.config({ customError: () => ({ message: "override" }) });
const result = stringWithCustomError.min(10).safeParse("tooshort");
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 10,
"inclusive": true,
"path": [],
"message": "override"
}
]]
`);
expect(result.error!.issues[0].message).toEqual("override");
z.config({ customError: undefined });
});
// test("invalid and required", () => {
// const str = z.string({
// invalid_type_error: "Invalid name",
// required_error: "Name is required",
// });
// const result1 = str.safeParse(1234);
// expect(result1.success).toBe(false);
// if (!result1.success) {
// expect(result1.error.issues[0].message).toEqual("Invalid name");
// }
// const result2 = str.safeParse(undefined);
// expect(result2.success).toBe(false);
// if (!result2.success) {
// expect(result2.error.issues[0].message).toEqual("Name is required");
// }
// });
// test("Fallback to default required error", () => {
// const str = z.string({
// invalid_type_error: "Invalid name",
// // required_error: "Name is required",
// });
// const result2 = str.safeParse(undefined);
// expect(result2.success).toBe(false);
// if (!result2.success) {
// expect(result2.error.issues[0].message).toEqual("Required");
// }
// });
// test("invalid and required and errorMap", () => {
// expect(() => {
// return z.string({
// invalid_type_error: "Invalid name",
// required_error: "Name is required",
// errorMap: () => ({ message: "override" }),
// });
// }).toThrow();
// });
// test("strict error message", () => {
// const errorMsg = "Invalid object";
// const obj = z.object({ x: z.string() }).strict(errorMsg);
// const result = obj.safeParse({ x: "a", y: "b" });
// expect(result.success).toBe(false);
// if (!result.success) {
// expect(result.error.issues[0].message).toEqual(errorMsg);
// }
// });
test("empty string error message", () => {
const schema = z.string().max(1, { message: "" });
const result = schema.safeParse("asdf");
expect(result.success).toBe(false);
expect(result.error!.issues[0].message).toEqual("");
});
test("dont short circuit on continuable errors", () => {
const user = z
.object({
password: z.string().min(6),
confirm: z.string(),
})
.refine((data) => data.password === data.confirm, {
message: "Passwords don't match",
path: ["confirm"],
});
const result = user.safeParse({ password: "asdf", confirm: "qwer" });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 6,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=6 characters"
},
{
"code": "custom",
"path": [
"confirm"
],
"message": "Passwords don't match"
}
]]
`);
// expect(result.error!.issues.length).toEqual(2);
});
test("string error params", () => {
const a = z.string("Bad!");
expect(a.safeParse(123).error!.issues[0].message).toBe("Bad!");
const b = z.string().min(5, "Too short!");
expect(b.safeParse("abc").error!.issues[0].message).toBe("Too short!");
const c = z.uuid("Bad UUID!");
expect(c.safeParse("not-a-uuid").error!.issues[0].message).toBe("Bad UUID!");
const d = z.string().datetime({ message: "Bad date!" });
expect(d.safeParse("not-a-date").error!.issues[0].message).toBe("Bad date!");
const e = z.array(z.string(), "Bad array!");
expect(e.safeParse("not-an-array").error!.issues[0].message).toBe("Bad array!");
const f = z.array(z.string()).min(5, "Too few items!");
expect(f.safeParse(["a", "b"]).error!.issues[0].message).toBe("Too few items!");
const g = z.set(z.string(), "Bad set!");
expect(g.safeParse("not-a-set").error!.issues[0].message).toBe("Bad set!");
const h = z.array(z.string(), "Bad array!");
expect(h.safeParse(123).error!.issues[0].message).toBe("Bad array!");
const i = z.set(z.string(), "Bad set!");
expect(i.safeParse(123).error!.issues[0].message).toBe("Bad set!");
const j = z.array(z.string(), "Bad array!");
expect(j.safeParse(null).error!.issues[0].message).toBe("Bad array!");
});
test("error inheritance", () => {
const e1 = z.string().safeParse(123).error!;
expect(e1).toBeInstanceOf(z.core.$ZodError);
expect(e1).toBeInstanceOf(z.ZodError);
expect(e1).toBeInstanceOf(z.ZodRealError);
// expect(e1).not.toBeInstanceOf(Error);
try {
z.string().parse(123);
} catch (e2) {
expect(e1).toBeInstanceOf(z.core.$ZodError);
expect(e2).toBeInstanceOf(z.ZodError);
expect(e2).toBeInstanceOf(z.ZodRealError);
// expect(e2).toBeInstanceOf(Error);
}
});
test("error serialization", () => {
try {
z.string().parse(123);
} catch (e) {
expect(e).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]]
`);
expect(inspect(e).split("\n").slice(0, 8).join("\n")).toMatchInlineSnapshot(`
"ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]"
`);
}
});

View File

@ -0,0 +1,91 @@
// @ts-ignore
import { File as WebFile } from "@web-std/file";
import { afterEach, beforeEach, expect, test } from "vitest";
import * as z from "zod/v4";
const minCheck = z.file().min(5);
const maxCheck = z.file().max(8);
const mimeCheck = z.file().mime(["text/plain", "application/json"]);
const originalFile = global.File;
beforeEach(async () => {
if (!globalThis.File) globalThis.File = WebFile;
});
afterEach(() => {
if (globalThis.File !== originalFile) {
globalThis.File = originalFile;
}
});
test("passing validations", () => {
minCheck.safeParse(new File(["12345"], "test.txt"));
maxCheck.safeParse(new File(["12345678"], "test.txt"));
mimeCheck.safeParse(new File([""], "test.csv", { type: "text/plain" }));
expect(() => mimeCheck.parse(new File([""], "test.txt"))).toThrow();
expect(() => mimeCheck.parse(new File([""], "test.txt", { type: "text/csv" }))).toThrow();
});
test("failing validations", () => {
expect(minCheck.safeParse(new File(["1234"], "test.txt"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "file",
"code": "too_small",
"minimum": 5,
"path": [],
"message": "Too small: expected file to have >5 bytes"
}
]],
"success": false,
}
`);
expect(maxCheck.safeParse(new File(["123456789"], "test.txt"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "file",
"code": "too_big",
"maximum": 8,
"path": [],
"message": "Too big: expected file to have <8 bytes"
}
]],
"success": false,
}
`);
expect(mimeCheck.safeParse(new File([""], "test.csv"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_value",
"values": [
"text/plain",
"application/json"
],
"path": [],
"message": "Invalid option: expected one of \\"text/plain\\"|\\"application/json\\""
}
]],
"success": false,
}
`);
expect(mimeCheck.safeParse(new File([""], "test.csv", { type: "text/csv" }))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_value",
"values": [
"text/plain",
"application/json"
],
"path": [],
"message": "Invalid option: expected one of \\"text/plain\\"|\\"application/json\\""
}
]],
"success": false,
}
`);
});

View File

@ -0,0 +1,175 @@
import { expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
import type * as core from "zod/v4/core";
test("first party switch", () => {
const myType = z.string() as core.$ZodTypes;
const def = myType._zod.def;
switch (def.type) {
case "string":
break;
case "number":
break;
case "bigint":
break;
case "boolean":
break;
case "date":
break;
case "symbol":
break;
case "undefined":
break;
case "null":
break;
case "any":
break;
case "unknown":
break;
case "never":
break;
case "void":
break;
case "array":
break;
case "object":
break;
case "union":
break;
case "intersection":
break;
case "tuple":
break;
case "record":
break;
case "map":
break;
case "set":
break;
case "literal":
break;
case "enum":
break;
case "promise":
break;
case "optional":
break;
case "nonoptional":
break;
case "nullable":
break;
case "default":
break;
case "prefault":
break;
case "template_literal":
break;
case "custom":
break;
case "transform":
break;
case "readonly":
break;
case "nan":
break;
case "pipe":
break;
case "success":
break;
case "catch":
break;
case "file":
break;
case "lazy":
break;
default:
expectTypeOf(def).toEqualTypeOf<never>();
}
});
test("$ZodSchemaTypes", () => {
const type = "string" as core.$ZodTypeDef["type"];
switch (type) {
case "string":
break;
case "number":
break;
case "int":
break;
case "bigint":
break;
case "boolean":
break;
case "date":
break;
case "symbol":
break;
case "undefined":
break;
case "null":
break;
case "any":
break;
case "unknown":
break;
case "never":
break;
case "void":
break;
case "array":
break;
case "object":
break;
case "union":
break;
case "intersection":
break;
case "tuple":
break;
case "record":
break;
case "map":
break;
case "set":
break;
case "literal":
break;
case "enum":
break;
case "promise":
break;
case "optional":
break;
case "nonoptional":
break;
case "nullable":
break;
case "default":
break;
case "prefault":
break;
case "template_literal":
break;
case "custom":
break;
case "transform":
break;
case "readonly":
break;
case "nan":
break;
case "pipe":
break;
case "success":
break;
case "catch":
break;
case "file":
break;
case "lazy":
break;
default:
expectTypeOf(type).toEqualTypeOf<never>();
}
});

View File

@ -0,0 +1,268 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const args1 = z.tuple([z.string()]);
const returns1 = z.number();
const func1 = z.function({
input: args1,
output: returns1,
});
test("function parsing", () => {
const parsed = func1.implement((arg: any) => arg.length);
const result = parsed("asdf");
expect(result).toBe(4);
});
test("parsed function fail 1", () => {
// @ts-expect-error
const parsed = func1.implement((x: string) => x);
expect(() => parsed("asdf")).toThrow();
});
test("parsed function fail 2", () => {
// @ts-expect-error
const parsed = func1.implement((x: string) => x);
expect(() => parsed(13 as any)).toThrow();
});
test("function inference 1", () => {
type func1 = (typeof func1)["_input"];
expectTypeOf<func1>().toEqualTypeOf<(k: string) => number>();
});
// test("method parsing", () => {
// const methodObject = z.object({
// property: z.number(),
// method: z
// .function()
// .input(z.tuple([z.string()]))
// .output(z.number()),
// });
// const methodInstance = {
// property: 3,
// method: function (s: string) {
// return s.length + this.property;
// },
// };
// const parsed = methodObject.parse(methodInstance);
// expect(parsed.method("length=8")).toBe(11); // 8 length + 3 property
// });
// test("async method parsing", async () => {
// const methodObject = z.object({
// property: z.number(),
// method: z.function().input(z.string()).output(z.promise(z.number())),
// });
// const methodInstance = {
// property: 3,
// method: async function (s: string) {
// return s.length + this.property;
// },
// };
// const parsed = methodObject.parse(methodInstance);
// expect(await parsed.method("length=8")).toBe(11); // 8 length + 3 property
// });
test("args method", () => {
const t1 = z.function();
type t1 = (typeof t1)["_input"];
expectTypeOf<t1>().toEqualTypeOf<(...args_1: never[]) => unknown>();
t1._input;
const t2args = z.tuple([z.string()], z.unknown());
const t2 = t1.input(t2args);
type t2 = (typeof t2)["_input"];
expectTypeOf<t2>().toEqualTypeOf<(arg: string, ...args_1: unknown[]) => unknown>();
const t3 = t2.output(z.boolean());
type t3 = (typeof t3)["_input"];
expectTypeOf<t3>().toEqualTypeOf<(arg: string, ...args_1: unknown[]) => boolean>();
});
// test("custom args", () => {
// const fn = z.function().implement((_a: string, _b: number) => {
// return new Date();
// });
// expectTypeOf(fn).toEqualTypeOf<(a: string, b: number) => Date>();
// });
const args2 = z.tuple([
z.object({
f1: z.number(),
f2: z.string().nullable(),
f3: z.array(z.boolean().optional()).optional(),
}),
]);
const returns2 = z.union([z.string(), z.number()]);
const func2 = z.function({
input: args2,
output: returns2,
});
test("function inference 2", () => {
type func2 = (typeof func2)["_input"];
expectTypeOf<func2>().toEqualTypeOf<
(arg: {
f3?: (boolean | undefined)[] | undefined;
f1: number;
f2: string | null;
}) => string | number
>();
});
test("valid function run", () => {
const validFunc2Instance = func2.implement((_x) => {
_x.f2;
_x.f3![0];
return "adf" as any;
});
validFunc2Instance({
f1: 21,
f2: "asdf",
f3: [true, false],
});
});
test("input validation error", () => {
const schema = z.function({
input: z.tuple([z.string()]),
output: z.void(),
});
const fn = schema.implement(() => 1234 as any);
// @ts-expect-error
const checker = () => fn();
try {
checker();
} catch (e: any) {
expect(e.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received undefined",
"path": [
0,
],
},
]
`);
}
});
test("array inputs", () => {
const a = z.function({
input: [
z.object({
name: z.string(),
age: z.number().int(),
}),
],
output: z.string(),
});
a.implement((args) => {
return `${args.age}`;
});
const b = z.function({
input: [
z.object({
name: z.string(),
age: z.number().int(),
}),
],
});
b.implement((args) => {
return `${args.age}`;
});
});
test("output validation error", () => {
const schema = z.function({
input: z.tuple([]),
output: z.string(),
});
const fn = schema.implement(() => 1234 as any);
try {
fn();
} catch (e: any) {
expect(e.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [],
},
]
`);
}
});
test("function with async refinements", async () => {
const schema = z
.function()
.input([z.string().refine(async (val) => val.length > 10)])
.output(z.promise(z.number().refine(async (val) => val > 10)));
const func = schema.implementAsync(async (val) => {
return val.length;
});
const results = [];
try {
await func("asdfasdf");
results.push("success");
} catch (_) {
results.push("fail");
}
try {
await func("asdflkjasdflkjsf");
results.push("success");
} catch (_) {
results.push("fail");
}
expect(results).toEqual(["fail", "success"]);
});
test("non async function with async refinements should fail", async () => {
const func = z
.function()
.input([z.string().refine(async (val) => val.length > 10)])
.output(z.number().refine(async (val) => val > 10))
.implement((val) => {
return val.length;
});
const results = [];
try {
await func("asdasdfasdffasdf");
results.push("success");
} catch (_) {
results.push("fail");
}
expect(results).toEqual(["fail"]);
});
test("extra parameters with rest", () => {
const maxLength5 = z
.function()
.input([z.string()], z.unknown())
.output(z.boolean())
.implement((str, _arg, _qewr) => {
return str.length <= 5;
});
const filteredList = ["apple", "orange", "pear", "banana", "strawberry"].filter(maxLength5);
expect(filteredList.length).toEqual(2);
});

View File

@ -0,0 +1,72 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
function nest<TData extends z.ZodType>(schema: TData) {
return z.object({
nested: schema,
});
}
test("generics", () => {
const a = nest(z.object({ a: z.string() }));
type a = z.infer<typeof a>;
expectTypeOf<a>().toEqualTypeOf<{ nested: { a: string } }>();
const b = nest(z.object({ a: z.string().optional() }));
type b = z.infer<typeof b>;
expectTypeOf<b>().toEqualTypeOf<{ nested: { a?: string | undefined } }>();
});
test("generics with optional", () => {
async function stripOuter<TData extends z.ZodType>(schema: TData, data: unknown) {
return z
.object({
nested: schema.optional(),
})
.transform((data) => {
return data.nested;
})
.parse({ nested: data });
}
const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" });
expectTypeOf<typeof result>().toEqualTypeOf<Promise<{ a: string } | undefined>>();
});
// test("assignability", () => {
// const createSchemaAndParse = <K extends string, VS extends z.ZodString>(key: K, valueSchema: VS, data: unknown) => {
// const schema = z.object({
// [key]: valueSchema,
// });
// // return { [key]: valueSchema };
// const parsed = schema.parse(data);
// return parsed;
// // const inferred: z.infer<z.ZodObject<{ [k in K]: VS }>> = parsed;
// // return inferred;
// };
// const parsed = createSchemaAndParse("foo", z.string(), { foo: "" });
// expectTypeOf<typeof parsed>().toEqualTypeOf<{ foo: string }>();
// });
test("nested no undefined", () => {
const inner = z.string().or(z.array(z.string()));
const outer = z.object({ inner });
type outerSchema = z.infer<typeof outer>;
expectTypeOf<outerSchema>().toEqualTypeOf<{ inner: string | string[] }>();
expect(outer.safeParse({ inner: undefined }).success).toEqual(false);
});
test("generic on output type", () => {
const createV4Schema = <Output>(opts: {
schema: z.ZodType<Output>;
}) => {
return opts.schema;
};
createV4Schema({
schema: z.object({
name: z.string(),
}),
})?._zod?.output?.name;
});

View File

@ -0,0 +1,829 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
import type { util } from "zod/v4/core";
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.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.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?, ...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?, ...boolean[]]>();
// type c = z.output<typeof c>;
});
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();
// partial enum
const d = z.record(z.enum(["a", "b"]).or(z.never()), z.string());
type d = z.output<typeof d>;
expectTypeOf<d>().toEqualTypeOf<Record<"a" | "b", string>>();
});
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);
});
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();
});
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 schema", () => {
const a = z.custom((val) => {
return typeof val === "string";
});
expect(z.parse(a, "hello")).toEqual("hello");
expect(() => z.parse(a, 123)).toThrow();
});
test("z.custom check", () => {
// @ts-expect-error Inference not possible, use z.refine()
z.date().check(z.custom((val) => val.getTime() > 0));
});
test("z.check", () => {
// this is a more flexible version of z.custom that accepts an arbitrary _parse logic
// the function should return base.$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.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>;
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();
});
// promise
test("z.promise", async () => {
const a = z.promise(z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<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("isPlainObject", () => {
expect(z.core.util.isPlainObject({})).toEqual(true);
expect(z.core.util.isPlainObject(Object.create(null))).toEqual(true);
expect(z.core.util.isPlainObject([])).toEqual(false);
expect(z.core.util.isPlainObject(new Date())).toEqual(false);
expect(z.core.util.isPlainObject(null)).toEqual(false);
expect(z.core.util.isPlainObject(undefined)).toEqual(false);
expect(z.core.util.isPlainObject("string")).toEqual(false);
expect(z.core.util.isPlainObject(123)).toEqual(false);
expect(z.core.util.isPlainObject(Symbol())).toEqual(false);
});
test("def typing", () => {
z.string().def.type satisfies "string";
z.number().def.type satisfies "number";
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.string().nullable().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";
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.string().optional().def.type satisfies "optional";
z.string().default("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.string().optional().nonoptional().def.type satisfies "nonoptional";
z.object({ key: z.string() }).readonly().def.type satisfies "readonly";
z.nan().def.type satisfies "nan";
z.unknown().pipe(z.number()).def.type satisfies "pipe";
z.success(z.string()).def.type satisfies "success";
z.string().catch("fallback").def.type satisfies "catch";
z.file().def.type satisfies "file";
});

View File

@ -0,0 +1,34 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("instanceof", async () => {
class Test {}
class Subtest extends Test {}
abstract class AbstractBar {
constructor(public val: string) {}
}
class Bar extends AbstractBar {}
const TestSchema = z.instanceof(Test);
const SubtestSchema = z.instanceof(Subtest);
const AbstractSchema = z.instanceof(AbstractBar);
const BarSchema = z.instanceof(Bar);
TestSchema.parse(new Test());
TestSchema.parse(new Subtest());
SubtestSchema.parse(new Subtest());
AbstractSchema.parse(new Bar("asdf"));
const bar = BarSchema.parse(new Bar("asdf"));
expect(bar.val).toEqual("asdf");
await expect(() => SubtestSchema.parse(new Test())).toThrow();
await expect(() => TestSchema.parse(12)).toThrow();
expectTypeOf<Test>().toEqualTypeOf<z.infer<typeof TestSchema>>();
});
test("instanceof fatal", () => {
const schema = z.instanceof(Date).refine((d) => d.toString());
const res = schema.safeParse(null);
expect(res.success).toBe(false);
});

View File

@ -0,0 +1,171 @@
import { expect, expectTypeOf, test } from "vitest";
import type { util } from "zod/v4/core";
import * as z from "zod/v4";
test("object intersection", () => {
const A = z.object({ a: z.string() });
const B = z.object({ b: z.string() });
const C = z.intersection(A, B); // BaseC.merge(HasID);
type C = z.infer<typeof C>;
expectTypeOf<C>().toEqualTypeOf<{ a: string } & { b: string }>();
const data = { a: "foo", b: "foo" };
expect(C.parse(data)).toEqual(data);
expect(() => C.parse({ a: "foo" })).toThrow();
});
test("object intersection: loose", () => {
const A = z.looseObject({ a: z.string() });
const B = z.object({ b: z.string() });
const C = z.intersection(A, B); // BaseC.merge(HasID);
type C = z.infer<typeof C>;
expectTypeOf<C>().toEqualTypeOf<{ a: string; [x: string]: unknown } & { b: string }>();
const data = { a: "foo", b: "foo", c: "extra" };
expect(C.parse(data)).toEqual(data);
expect(() => C.parse({ a: "foo" })).toThrow();
});
test("object intersection: strict", () => {
const A = z.strictObject({ a: z.string() });
const B = z.object({ b: z.string() });
const C = z.intersection(A, B); // BaseC.merge(HasID);
type C = z.infer<typeof C>;
expectTypeOf<C>().toEqualTypeOf<{ a: string } & { b: string }>();
const data = { a: "foo", b: "foo", c: "extra" };
const result = C.safeParse(data);
expect(result.success).toEqual(false);
});
test("deep intersection", () => {
const Animal = z.object({
properties: z.object({
is_animal: z.boolean(),
}),
});
const Cat = z.intersection(
z.object({
properties: z.object({
jumped: z.boolean(),
}),
}),
Animal
);
type Cat = util.Flatten<z.infer<typeof Cat>>;
expectTypeOf<Cat>().toEqualTypeOf<{ properties: { is_animal: boolean } & { jumped: boolean } }>();
const a = Cat.safeParse({ properties: { is_animal: true, jumped: true } });
expect(a.data!.properties).toEqual({ is_animal: true, jumped: true });
});
test("deep intersection of arrays", async () => {
const Author = z.object({
posts: z.array(
z.object({
post_id: z.number(),
})
),
});
const Registry = z.intersection(
Author,
z.object({
posts: z.array(
z.object({
title: z.string(),
})
),
})
);
const posts = [
{ post_id: 1, title: "Novels" },
{ post_id: 2, title: "Fairy tales" },
];
const cat = Registry.parse({ posts });
expect(cat.posts).toEqual(posts);
const asyncCat = await Registry.parseAsync({ posts });
expect(asyncCat.posts).toEqual(posts);
});
test("invalid intersection types", async () => {
const numberIntersection = z.intersection(
z.number(),
z.number().transform((x) => x + 1)
);
expect(() => {
numberIntersection.parse(1234);
}).toThrowErrorMatchingInlineSnapshot(`[Error: Unmergable intersection. Error path: []]`);
});
test("invalid array merge (incompatible lengths)", async () => {
const stringArrInt = z.intersection(
z.string().array(),
z
.string()
.array()
.transform((val) => [...val, "asdf"])
);
expect(() => stringArrInt.safeParse(["asdf", "qwer"])).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: []]`
);
});
test("invalid array merge (incompatible elements)", async () => {
const stringArrInt = z.intersection(
z.string().array(),
z
.string()
.array()
.transform((val) => [...val.slice(0, -1), "asdf"])
);
expect(() => stringArrInt.safeParse(["asdf", "qwer"])).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: [1]]`
);
});
test("invalid object merge", async () => {
const Cat = z.object({
phrase: z.string().transform((val) => `${val} Meow`),
});
const Dog = z.object({
phrase: z.string().transform((val) => `${val} Woof`),
});
const CatDog = z.intersection(Cat, Dog);
expect(() => CatDog.parse({ phrase: "Hello, my name is CatDog." })).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: ["phrase"]]`
);
});
test("invalid deep merge of object and array combination", async () => {
const University = z.object({
students: z.array(
z.object({
name: z.string().transform((val) => `Student name: ${val}`),
})
),
});
const Registry = z.intersection(
University,
z.object({
students: z.array(
z.object({
name: z.string(),
surname: z.string(),
})
),
})
);
const students = [{ name: "John", surname: "Doe" }];
expect(() => Registry.parse({ students })).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: ["students",0,"name"]]`
);
});

View File

@ -0,0 +1,108 @@
import { test } from "vitest";
// import * as z from "zod/v4";
test(() => {});
// test("overload types", () => {
// const schema = z.string().json();
// util.assertEqual<typeof schema, z.ZodString>(true);
// const schema2 = z.string().json(z.number());
// util.assertEqual<typeof schema2, z.ZodPipe<z.ZodTransform<any, string>, z.ZodNumber>>(true);
// const r2 = schema2.parse("12");
// util.assertEqual<number, typeof r2>(true);
// });
// test("parse string to json", async () => {
// const Env = z.object({
// myJsonConfig: z.string().jsonString(z.object({ foo: z.number() })),
// someOtherValue: z.string(),
// });
// expect(
// Env.parse({
// myJsonConfig: '{ "foo": 123 }',
// someOtherValue: "abc",
// })
// ).toEqual({
// myJsonConfig: { foo: 123 },
// someOtherValue: "abc",
// });
// const invalidValues = Env.safeParse({
// myJsonConfig: '{"foo": "not a number!"}',
// someOtherValue: null,
// });
// expect(JSON.parse(JSON.stringify(invalidValues))).toEqual({
// success: false,
// error: {
// name: "ZodError",
// issues: [
// {
// code: "invalid_type",
// expected: "number",
// input: "not a number!",
// received: "string",
// path: ["myJsonConfig", "foo"],
// message: "Expected number, received string",
// },
// {
// code: "invalid_type",
// expected: "string",
// input: null,
// received: "null",
// path: ["someOtherValue"],
// message: "Expected string, received null",
// },
// ],
// },
// });
// const invalidJsonSyntax = Env.safeParse({
// myJsonConfig: "This is not valid json",
// someOtherValue: null,
// });
// expect(JSON.parse(JSON.stringify(invalidJsonSyntax))).toMatchObject({
// success: false,
// error: {
// name: "ZodError",
// issues: [
// {
// code: "invalid_string",
// input: {
// _def: {
// catchall: {
// _def: {
// typeName: "ZodNever",
// },
// },
// typeName: "ZodObject",
// unknownKeys: "strip",
// },
// },
// validation: "json",
// message: "Invalid json",
// path: ["myJsonConfig"],
// },
// {
// code: "invalid_type",
// expected: "string",
// input: null,
// received: "null",
// path: ["someOtherValue"],
// message: "Expected string, received null",
// },
// ],
// },
// });
// });
// test("no argument", () => {
// const schema = z.string().json();
// util.assertEqual<typeof schema, z.ZodString>(true);
// z.string().json().parse(`{}`);
// z.string().json().parse(`null`);
// z.string().json().parse(`12`);
// z.string().json().parse(`{ "test": "test"}`);
// expect(() => z.string().json().parse(`asdf`)).toThrow();
// expect(() => z.string().json().parse(`{ "test": undefined }`)).toThrow();
// expect(() => z.string().json().parse(`{ "test": 12n }`)).toThrow();
// expect(() => z.string().json().parse(`{ test: "test" }`)).toThrow();
// });

View File

@ -0,0 +1,227 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("opt passthrough", () => {
const object = z.object({
a: z.lazy(() => z.string()),
b: z.lazy(() => z.string().optional()),
c: z.lazy(() => z.string().default("default")),
});
type ObjectTypeIn = z.input<typeof object>;
expectTypeOf<ObjectTypeIn>().toEqualTypeOf<{
a: string;
b?: string | undefined;
c?: string | undefined;
}>();
type ObjectTypeOut = z.output<typeof object>;
expectTypeOf<ObjectTypeOut>().toEqualTypeOf<{
a: string;
b?: string | undefined;
c: string;
}>();
const result = object.parse(
{
a: "hello",
b: undefined,
},
{ jitless: true }
);
expect(result).toEqual({
a: "hello",
// b: undefined,
c: "default",
});
expect(z.lazy(() => z.string())._zod.optin).toEqual(undefined);
expect(z.lazy(() => z.string())._zod.optout).toEqual(undefined);
expect(z.lazy(() => z.string().optional())._zod.optin).toEqual("optional");
expect(z.lazy(() => z.string().optional())._zod.optout).toEqual("optional");
expect(z.lazy(() => z.string().default("asdf"))._zod.optin).toEqual("optional");
expect(z.lazy(() => z.string().default("asdf"))._zod.optout).toEqual(undefined);
});
////////////// LAZY //////////////
test("schema getter", () => {
z.lazy(() => z.string()).parse("asdf");
});
test("lazy proxy", () => {
const schema = z.lazy(() => z.string())._zod.innerType.min(6);
schema.parse("123456");
expect(schema.safeParse("12345").success).toBe(false);
});
interface Category {
name: string;
subcategories: Category[];
}
const testCategory: Category = {
name: "I",
subcategories: [
{
name: "A",
subcategories: [
{
name: "1",
subcategories: [
{
name: "a",
subcategories: [],
},
],
},
],
},
],
};
test("recursion with z.lazy", () => {
const Category: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(Category),
})
);
Category.parse(testCategory);
});
type LinkedList = null | { value: number; next: LinkedList };
const linkedListExample = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null,
},
},
},
};
test("recursive union wit z.lazy", () => {
const LinkedListSchema: z.ZodType<LinkedList> = z.lazy(() =>
z.union([
z.null(),
z.object({
value: z.number(),
next: LinkedListSchema,
}),
])
);
LinkedListSchema.parse(linkedListExample);
});
interface A {
val: number;
b: B;
}
interface B {
val: number;
a?: A | undefined;
}
test("mutual recursion with lazy", () => {
const Alazy: z.ZodType<A> = z.lazy(() =>
z.object({
val: z.number(),
b: Blazy,
})
);
const Blazy: z.ZodType<B> = z.lazy(() =>
z.object({
val: z.number(),
a: Alazy.optional(),
})
);
const testData = {
val: 1,
b: {
val: 5,
a: {
val: 3,
b: {
val: 4,
a: {
val: 2,
b: {
val: 1,
},
},
},
},
},
};
Alazy.parse(testData);
Blazy.parse(testData.b);
expect(() => Alazy.parse({ val: "asdf" })).toThrow();
});
// TODO
test("mutual recursion with cyclical data", () => {
const a: any = { val: 1 };
const b: any = { val: 2 };
a.b = b;
b.a = a;
});
test("complicated self-recursion", () => {
const Category = z.object({
name: z.string(),
age: z.optional(z.number()),
get nullself() {
return Category.nullable();
},
get optself() {
return Category.optional();
},
get self() {
return Category;
},
get subcategories() {
return z.array(Category);
},
nested: z.object({
get sub() {
return Category;
},
}),
});
type _Category = z.output<typeof Category>;
});
test("lazy initialization", () => {
const a: any = z.lazy(() => a).optional();
const b: any = z.lazy(() => b).nullable();
const c: any = z.lazy(() => c).default({} as any);
const d: any = z.lazy(() => d).prefault({} as any);
const e: any = z.lazy(() => e).nonoptional();
const f: any = z.lazy(() => f).catch({} as any);
const g: any = z.lazy(() => z.object({ g })).readonly();
const baseCategorySchema = z.object({
name: z.string(),
});
type Category = z.infer<typeof baseCategorySchema> & {
subcategories: Category[];
};
const categorySchema: z.ZodType<Category> = baseCategorySchema.extend({
subcategories: z.lazy(() => categorySchema.array()),
});
});

View File

@ -0,0 +1,92 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
const literalTuna = z.literal("tuna");
const literalTunaCustomMessage = z.literal("tuna", {
message: "That's not a tuna",
});
const literalFortyTwo = z.literal(42);
const literalTrue = z.literal(true);
test("passing validations", () => {
literalTuna.parse("tuna");
literalFortyTwo.parse(42);
literalTrue.parse(true);
});
test("failing validations", () => {
expect(() => literalTuna.parse("shark")).toThrow();
expect(() => literalFortyTwo.parse(43)).toThrow();
expect(() => literalTrue.parse(false)).toThrow();
});
test("invalid_literal should have `input` field with data", () => {
const data = "shark";
const result = literalTuna.safeParse(data);
const issue = result.error!.issues[0];
expect(issue.code).toBe("invalid_value");
expect(issue).toMatchInlineSnapshot(`
{
"code": "invalid_value",
"message": "Invalid input: expected "tuna"",
"path": [],
"values": [
"tuna",
],
}
`);
});
test("invalid_literal should return default message", () => {
const data = "shark";
const result = literalTuna.safeParse(data);
const issue = result.error!.issues[0];
expect(issue.message).toEqual(`Invalid input: expected \"tuna\"`);
});
test("invalid_literal should return custom message", () => {
const data = "shark";
const result = literalTunaCustomMessage.safeParse(data);
const issue = result.error!.issues[0];
expect(issue.message).toEqual(`That's not a tuna`);
});
test("literal default error message", () => {
const result = z.literal("Tuna").safeParse("Trout");
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna"
],
"path": [],
"message": "Invalid input: expected \\"Tuna\\""
}
]]
`);
});
test("literal bigint default error message", () => {
const result = z.literal(BigInt(12)).safeParse(BigInt(13));
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].message).toEqual(`Invalid input: expected 12n`);
});
test(".value getter", () => {
expect(z.literal("tuna").value).toEqual("tuna");
expect(() => z.literal([1, 2, 3]).value).toThrow();
});
test("readonly", () => {
const a = ["asdf"] as const;
z.literal(a);
});

View File

@ -0,0 +1,196 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const stringMap = z.map(z.string(), z.string());
type stringMap = z.infer<typeof stringMap>;
test("type inference", () => {
expectTypeOf<stringMap>().toEqualTypeOf<Map<string, string>>();
});
test("valid parse", () => {
const result = stringMap.safeParse(
new Map([
["first", "foo"],
["second", "bar"],
])
);
expect(result.success).toEqual(true);
expect(result.data).toMatchInlineSnapshot(`
Map {
"first" => "foo",
"second" => "bar",
}
`);
});
test("valid parse async", async () => {
const asyncMap = z.map(
z.string().refine(async () => false, "bad key"),
z.string().refine(async () => false, "bad value")
);
const result = await asyncMap.safeParseAsync(new Map([["first", "foo"]]));
expect(result.success).toEqual(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"first"
],
"message": "bad key"
},
{
"code": "custom",
"path": [
"first"
],
"message": "bad value"
}
]]
`);
});
test("throws when a Set is given", () => {
const result = stringMap.safeParse(new Set([]));
expect(result.success).toEqual(false);
if (result.success === false) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].code).toEqual("invalid_type");
}
});
test("throws when the given map has invalid key and invalid input", () => {
const result = stringMap.safeParse(new Map([[42, Symbol()]]));
expect(result.success).toEqual(false);
if (result.success === false) {
expect(result.error.issues.length).toEqual(2);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
42
],
"message": "Invalid input: expected string, received number"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
42
],
"message": "Invalid input: expected string, received symbol"
}
]]
`);
}
});
test("throws when the given map has multiple invalid entries", () => {
// const result = stringMap.safeParse(new Map([[42, Symbol()]]));
const result = stringMap.safeParse(
new Map([
[1, "foo"],
["bar", 2],
] as [any, any][]) as Map<any, any>
);
// const result = stringMap.safeParse(new Map([[42, Symbol()]]));
expect(result.success).toEqual(false);
if (result.success === false) {
expect(result.error.issues.length).toEqual(2);
expect(result.error.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [
1,
],
},
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [
"bar",
],
},
]
`);
}
});
test("dirty", async () => {
const map = z.map(
z.string().refine((val) => val === val.toUpperCase(), {
message: "Keys must be uppercase",
}),
z.string()
);
const result = await map.spa(
new Map([
["first", "foo"],
["second", "bar"],
])
);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"first"
],
"message": "Keys must be uppercase"
},
{
"code": "custom",
"path": [
"second"
],
"message": "Keys must be uppercase"
}
]]
`);
}
});
test("map with object keys", () => {
const map = z.map(
z.object({
name: z.string(),
age: z.number(),
}),
z.string()
);
const data = new Map([
[{ name: "John", age: 30 }, "foo"],
[{ name: "Jane", age: 25 }, "bar"],
]);
const result = map.safeParse(data);
expect(result.success).toEqual(true);
expect(result.data!).toEqual(data);
const badData = new Map([["bad", "foo"]]);
const badResult = map.safeParse(badData);
expect(badResult.success).toEqual(false);
expect(badResult.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [
"bad"
],
"message": "Invalid input: expected object, received string"
}
]]
`);
});

View File

@ -0,0 +1,21 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const schema = z.nan();
test("passing validations", () => {
schema.parse(Number.NaN);
schema.parse(Number("Not a number"));
expectTypeOf<typeof schema._output>().toEqualTypeOf<number>();
});
test("failing validations", () => {
expect(() => schema.parse(5)).toThrow();
expect(() => schema.parse("John")).toThrow();
expect(() => schema.parse(true)).toThrow();
expect(() => schema.parse(null)).toThrow();
expect(() => schema.parse(undefined)).toThrow();
expect(() => schema.parse({})).toThrow();
expect(() => schema.parse([])).toThrow();
});

View File

@ -0,0 +1,168 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("nested refinements", () => {
const zodSchema = z
.object({
password: z.string().min(1),
nested: z
.object({
confirm: z
.string()
.min(1)
.refine((value) => value.length > 2, {
message: "Confirm length should be > 2",
}),
})
.refine(
(data) => {
return data.confirm === "bar";
},
{
path: ["confirm"],
error: 'Value must be "bar"',
}
),
})
.refine(
(data) => {
return data.nested.confirm === data.password;
},
{
path: ["nested", "confirm"],
error: "Password and confirm must match",
}
);
const DATA = {
password: "bar",
nested: { confirm: "" },
};
expect(zodSchema.safeParse(DATA)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"nested",
"confirm"
],
"message": "Too small: expected string to have >=1 characters"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Confirm length should be > 2"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Value must be \\"bar\\""
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Password and confirm must match"
}
]],
"success": false,
}
`);
expect(zodSchema.safeParse(DATA, { jitless: true })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"nested",
"confirm"
],
"message": "Too small: expected string to have >=1 characters"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Confirm length should be > 2"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Value must be \\"bar\\""
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Password and confirm must match"
}
]],
"success": false,
}
`);
expect(zodSchema["~standard"].validate(DATA)).toMatchInlineSnapshot(`
{
"issues": [
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected string to have >=1 characters",
"minimum": 1,
"origin": "string",
"path": [
"nested",
"confirm",
],
},
{
"code": "custom",
"message": "Confirm length should be > 2",
"path": [
"nested",
"confirm",
],
},
{
"code": "custom",
"message": "Value must be "bar"",
"path": [
"nested",
"confirm",
],
},
{
"code": "custom",
"message": "Password and confirm must match",
"path": [
"nested",
"confirm",
],
},
],
}
`);
});

View File

@ -0,0 +1,86 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("nonoptional", () => {
const schema = z.string().nonoptional();
expectTypeOf<typeof schema._input>().toEqualTypeOf<string>();
expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
const result = schema.safeParse(undefined);
expect(result.success).toBe(false);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("nonoptional with default", () => {
const schema = z.string().optional().nonoptional();
expectTypeOf<typeof schema._input>().toEqualTypeOf<string>();
expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
const result = schema.safeParse(undefined);
expect(result.success).toBe(false);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": [],
"message": "Invalid input: expected nonoptional, received undefined"
}
]],
"success": false,
}
`);
});
test("nonoptional in object", () => {
const schema = z.object({ hi: z.string().optional().nonoptional() });
expectTypeOf<typeof schema._input>().toEqualTypeOf<{ hi: string }>();
expectTypeOf<typeof schema._output>().toEqualTypeOf<{ hi: string }>();
const r1 = schema.safeParse({ hi: "asdf" });
expect(r1.success).toEqual(true);
const r2 = schema.safeParse({ hi: undefined });
// expect(schema.safeParse({ hi: undefined }).success).toEqual(false);
expect(r2.success).toEqual(false);
expect(r2.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": [
"hi"
],
"message": "Invalid input: expected nonoptional, received undefined"
}
]]
`);
const r3 = schema.safeParse({});
expect(r3.success).toEqual(false);
expect(r3.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": [
"hi"
],
"message": "Invalid input: expected nonoptional, received undefined"
}
]]
`);
});

View File

@ -0,0 +1,22 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test(".nullable()", () => {
const nullable = z.string().nullable();
expect(nullable.parse(null)).toBe(null);
expect(nullable.parse("asdf")).toBe("asdf");
expect(() => nullable.parse(123)).toThrow();
});
test(".nullable unwrap", () => {
const schema = z.string().nullable();
expect(schema).toBeInstanceOf(z.ZodNullable);
expect(schema.unwrap()).toBeInstanceOf(z.ZodString);
});
test("z.null", () => {
const n = z.null();
expect(n.parse(null)).toBe(null);
expect(() => n.parse("asdf")).toThrow();
});

View File

@ -0,0 +1,247 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("z.number() basic validation", () => {
const schema = z.number();
expect(schema.parse(1234)).toEqual(1234);
});
test("NaN validation", () => {
const schema = z.number();
expect(() => schema.parse(Number.NaN)).toThrow();
});
test("Infinity validation", () => {
const schema = z.number();
expect(schema.safeParse(Number.POSITIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
expect(schema.safeParse(Number.NEGATIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
});
test(".gt() validation", () => {
const schema = z.number().gt(0).gt(5);
expect(schema.parse(6)).toEqual(6);
expect(() => schema.parse(5)).toThrow();
});
test(".gte() validation", () => {
const schema = z.number().gt(0).gte(1).gte(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(4)).toThrow();
});
test(".min() validation", () => {
const schema = z.number().min(0).min(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(4)).toThrow();
});
test(".lt() validation", () => {
const schema = z.number().lte(10).lt(5);
expect(schema.parse(4)).toEqual(4);
expect(() => schema.parse(5)).toThrow();
});
test(".lte() validation", () => {
const schema = z.number().lte(10).lte(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(6)).toThrow();
});
test(".max() validation", () => {
const schema = z.number().max(10).max(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(6)).toThrow();
});
test(".int() validation", () => {
const schema = z.number().int();
expect(schema.parse(4)).toEqual(4);
expect(() => schema.parse(3.14)).toThrow();
});
test(".positive() validation", () => {
const schema = z.number().positive();
expect(schema.parse(1)).toEqual(1);
expect(() => schema.parse(0)).toThrow();
expect(() => schema.parse(-1)).toThrow();
});
test(".negative() validation", () => {
const schema = z.number().negative();
expect(schema.parse(-1)).toEqual(-1);
expect(() => schema.parse(0)).toThrow();
expect(() => schema.parse(1)).toThrow();
});
test(".nonpositive() validation", () => {
const schema = z.number().nonpositive();
expect(schema.parse(0)).toEqual(0);
expect(schema.parse(-1)).toEqual(-1);
expect(() => schema.parse(1)).toThrow();
});
test(".nonnegative() validation", () => {
const schema = z.number().nonnegative();
expect(schema.parse(0)).toEqual(0);
expect(schema.parse(1)).toEqual(1);
expect(() => schema.parse(-1)).toThrow();
});
test(".multipleOf() with positive divisor", () => {
const schema = z.number().multipleOf(5);
expect(schema.parse(15)).toEqual(15);
expect(schema.parse(-15)).toEqual(-15);
expect(() => schema.parse(7.5)).toThrow();
expect(() => schema.parse(-7.5)).toThrow();
});
test(".multipleOf() with negative divisor", () => {
const schema = z.number().multipleOf(-5);
expect(schema.parse(-15)).toEqual(-15);
expect(schema.parse(15)).toEqual(15);
expect(() => schema.parse(-7.5)).toThrow();
expect(() => schema.parse(7.5)).toThrow();
});
test(".step() validation", () => {
const schemaPointOne = z.number().step(0.1);
const schemaPointZeroZeroZeroOne = z.number().step(0.0001);
const schemaSixPointFour = z.number().step(6.4);
expect(schemaPointOne.parse(6)).toEqual(6);
expect(schemaPointOne.parse(6.1)).toEqual(6.1);
expect(schemaSixPointFour.parse(12.8)).toEqual(12.8);
expect(schemaPointZeroZeroZeroOne.parse(3.01)).toEqual(3.01);
expect(() => schemaPointOne.parse(6.11)).toThrow();
expect(() => schemaPointOne.parse(6.1000000001)).toThrow();
expect(() => schemaSixPointFour.parse(6.41)).toThrow();
});
test(".finite() validation", () => {
const schema = z.number().finite();
expect(schema.parse(123)).toEqual(123);
expect(schema.safeParse(Number.POSITIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
expect(schema.safeParse(Number.NEGATIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
});
test(".safe() validation", () => {
const schema = z.number().safe();
expect(schema.parse(Number.MIN_SAFE_INTEGER)).toEqual(Number.MIN_SAFE_INTEGER);
expect(schema.parse(Number.MAX_SAFE_INTEGER)).toEqual(Number.MAX_SAFE_INTEGER);
expect(() => schema.parse(Number.MIN_SAFE_INTEGER - 1)).toThrow();
expect(() => schema.parse(Number.MAX_SAFE_INTEGER + 1)).toThrow();
});
test("min value getters", () => {
expect(z.number().minValue).toBeNull;
expect(z.number().lt(5).minValue).toBeNull;
expect(z.number().lte(5).minValue).toBeNull;
expect(z.number().max(5).minValue).toBeNull;
expect(z.number().negative().minValue).toBeNull;
expect(z.number().nonpositive().minValue).toBeNull;
expect(z.number().int().minValue).toBeNull;
expect(z.number().multipleOf(5).minValue).toBeNull;
expect(z.number().finite().minValue).toBeNull;
expect(z.number().gt(5).minValue).toEqual(5);
expect(z.number().gte(5).minValue).toEqual(5);
expect(z.number().min(5).minValue).toEqual(5);
expect(z.number().min(5).min(10).minValue).toEqual(10);
expect(z.number().positive().minValue).toEqual(0);
expect(z.number().nonnegative().minValue).toEqual(0);
expect(z.number().safe().minValue).toEqual(Number.MIN_SAFE_INTEGER);
});
test("max value getters", () => {
expect(z.number().maxValue).toBeNull;
expect(z.number().gt(5).maxValue).toBeNull;
expect(z.number().gte(5).maxValue).toBeNull;
expect(z.number().min(5).maxValue).toBeNull;
expect(z.number().positive().maxValue).toBeNull;
expect(z.number().nonnegative().maxValue).toBeNull;
expect(z.number().int().minValue).toBeNull;
expect(z.number().multipleOf(5).minValue).toBeNull;
expect(z.number().finite().minValue).toBeNull;
expect(z.number().lt(5).maxValue).toEqual(5);
expect(z.number().lte(5).maxValue).toEqual(5);
expect(z.number().max(5).maxValue).toEqual(5);
expect(z.number().max(5).max(1).maxValue).toEqual(1);
expect(z.number().negative().maxValue).toEqual(0);
expect(z.number().nonpositive().maxValue).toEqual(0);
expect(z.number().safe().maxValue).toEqual(Number.MAX_SAFE_INTEGER);
});
test("int getter", () => {
expect(z.number().isInt).toEqual(false);
expect(z.number().int().isInt).toEqual(true);
expect(z.number().safe().isInt).toEqual(true);
expect(z.number().multipleOf(5).isInt).toEqual(true);
});
/** In Zod 4, number schemas don't accept infinite values. */
test("finite getter", () => {
expect(z.number().isFinite).toEqual(true);
});
test("string format methods", () => {
const a = z.int32().min(5);
expect(a.parse(6)).toEqual(6);
expect(() => a.parse(1)).toThrow();
});
test("error customization", () => {
z.number().gte(5, { error: (iss) => "Min: " + iss.minimum.valueOf() });
z.number().lte(5, { error: (iss) => "Max: " + iss.maximum.valueOf() });
});

View File

@ -0,0 +1,563 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
import * as core from "zod/v4/core";
const Test = z.object({
f1: z.number(),
f2: z.string().optional(),
f3: z.string().nullable(),
f4: z.array(z.object({ t: z.union([z.string(), z.boolean()]) })),
});
test("object type inference", () => {
type TestType = {
f1: number;
f2?: string | undefined;
f3: string | null;
f4: { t: string | boolean }[];
};
expectTypeOf<z.TypeOf<typeof Test>>().toEqualTypeOf<TestType>();
});
test("unknown throw", () => {
const asdf: unknown = 35;
expect(() => Test.parse(asdf)).toThrow();
});
test("shape() should return schema of particular key", () => {
const f1Schema = Test.shape.f1;
const f2Schema = Test.shape.f2;
const f3Schema = Test.shape.f3;
const f4Schema = Test.shape.f4;
expect(f1Schema).toBeInstanceOf(z.ZodNumber);
expect(f2Schema).toBeInstanceOf(z.ZodOptional);
expect(f3Schema).toBeInstanceOf(z.ZodNullable);
expect(f4Schema).toBeInstanceOf(z.ZodArray);
});
test("correct parsing", () => {
Test.parse({
f1: 12,
f2: "string",
f3: "string",
f4: [
{
t: "string",
},
],
});
Test.parse({
f1: 12,
f3: null,
f4: [
{
t: false,
},
],
});
});
test("nonstrict by default", () => {
z.object({ points: z.number() }).parse({
points: 2314,
unknown: "asdf",
});
});
test("parse optional keys ", () => {
const schema = z.object({
a: z.string().optional(),
});
expect(schema.parse({ a: "asdf" })).toEqual({ a: "asdf" });
});
test("empty object", () => {
const schema = z.object({});
expect(schema.parse({})).toEqual({});
expect(schema.parse({ name: "asdf" })).toEqual({});
expect(schema.safeParse(null).success).toEqual(false);
expect(schema.safeParse("asdf").success).toEqual(false);
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<Record<string, never>>();
});
const data = {
points: 2314,
unknown: "asdf",
};
test("strip by default", () => {
const val = z.object({ points: z.number() }).parse(data);
expect(val).toEqual({ points: 2314 });
});
test("unknownkeys override", () => {
const val = z.object({ points: z.number() }).strict().passthrough().strip().passthrough().parse(data);
expect(val).toEqual(data);
});
test("passthrough unknown", () => {
const val = z.object({ points: z.number() }).passthrough().parse(data);
expect(val).toEqual(data);
});
test("strip unknown", () => {
const val = z.object({ points: z.number() }).strip().parse(data);
expect(val).toEqual({ points: 2314 });
});
test("strict", () => {
const val = z.object({ points: z.number() }).strict().safeParse(data);
expect(val.success).toEqual(false);
});
test("catchall inference", () => {
const o1 = z
.object({
first: z.string(),
})
.catchall(z.number());
const d1 = o1.parse({ first: "asdf", num: 1243 });
// expectTypeOf<(typeof d1)["asdf"]>().toEqualTypeOf<number>();
expectTypeOf<(typeof d1)["first"]>().toEqualTypeOf<string>();
});
test("catchall overrides strict", () => {
const o1 = z.object({ first: z.string().optional() }).strict().catchall(z.number());
// should run fine
// setting a catchall overrides the unknownKeys behavior
o1.parse({
asdf: 1234,
});
// should only run catchall validation
// against unknown keys
o1.parse({
first: "asdf",
asdf: 1234,
});
});
test("catchall overrides strict", () => {
const o1 = z
.object({
first: z.string(),
})
.strict()
.catchall(z.number());
// should run fine
// setting a catchall overrides the unknownKeys behavior
o1.parse({
first: "asdf",
asdf: 1234,
});
});
test("optional keys are unset", async () => {
const SNamedEntity = z.object({
id: z.string(),
set: z.string().optional(),
unset: z.string().optional(),
});
const result = await SNamedEntity.parse({
id: "asdf",
set: undefined,
});
expect(Object.keys(result)).toEqual(["id", "set"]);
});
test("catchall parsing", async () => {
const result = z.object({ name: z.string() }).catchall(z.number()).parse({ name: "Foo", validExtraKey: 61 });
expect(result).toEqual({ name: "Foo", validExtraKey: 61 });
const result2 = z
.object({ name: z.string() })
.catchall(z.number())
.safeParse({ name: "Foo", validExtraKey: 61, invalid: "asdf" });
expect(result2.success).toEqual(false);
});
test("nonexistent keys", async () => {
const Schema = z.union([z.object({ a: z.string() }), z.object({ b: z.number() })]);
const obj = { a: "A" };
const result = await Schema.spa(obj); // Works with 1.11.10, breaks with 2.0.0-beta.21
expect(result.success).toBe(true);
});
test("test async union", async () => {
const Schema2 = z.union([
z.object({
ty: z.string(),
}),
z.object({
ty: z.number(),
}),
]);
const obj = { ty: "A" };
const result = await Schema2.spa(obj); // Works with 1.11.10, breaks with 2.0.0-beta.21
expect(result.success).toEqual(true);
});
test("test inferred merged type", async () => {
const asdf = z.object({ a: z.string() }).merge(z.object({ a: z.number() }));
type asdf = z.infer<typeof asdf>;
expectTypeOf<asdf>().toEqualTypeOf<{ a: number }>();
});
test("inferred type with Record shape", () => {
type A = z.ZodObject<Record<string, z.ZodType<string, number>>>;
expectTypeOf<z.infer<A>>().toEqualTypeOf<Record<string, string>>();
expectTypeOf<z.input<A>>().toEqualTypeOf<Record<string, number>>();
type B = z.ZodObject;
expectTypeOf<z.infer<B>>().toEqualTypeOf<Record<string, unknown>>();
expectTypeOf<z.input<B>>().toEqualTypeOf<Record<string, unknown>>();
});
test("inferred merged object type with optional properties", async () => {
const Merged = z
.object({ a: z.string(), b: z.string().optional() })
.merge(z.object({ a: z.string().optional(), b: z.string() }));
type Merged = z.infer<typeof Merged>;
expectTypeOf<Merged>().toEqualTypeOf<{ a?: string; b: string }>();
expectTypeOf<Merged>().toEqualTypeOf<{ a?: string; b: string }>();
});
test("inferred unioned object type with optional properties", async () => {
const Unioned = z.union([
z.object({ a: z.string(), b: z.string().optional() }),
z.object({ a: z.string().optional(), b: z.string() }),
]);
type Unioned = z.infer<typeof Unioned>;
expectTypeOf<Unioned>().toEqualTypeOf<{ a: string; b?: string } | { a?: string; b: string }>();
});
test("inferred enum type", async () => {
const Enum = z.object({ a: z.string(), b: z.string().optional() }).keyof();
expect(Enum.enum).toEqual({
a: "a",
b: "b",
});
expect(Enum._zod.def.entries).toEqual({
a: "a",
b: "b",
});
type Enum = z.infer<typeof Enum>;
expectTypeOf<Enum>().toEqualTypeOf<"a" | "b">();
});
test("inferred partial object type with optional properties", async () => {
const Partial = z.object({ a: z.string(), b: z.string().optional() }).partial();
type Partial = z.infer<typeof Partial>;
expectTypeOf<Partial>().toEqualTypeOf<{ a?: string; b?: string }>();
});
test("inferred picked object type with optional properties", async () => {
const Picked = z.object({ a: z.string(), b: z.string().optional() }).pick({ b: true });
type Picked = z.infer<typeof Picked>;
expectTypeOf<Picked>().toEqualTypeOf<{ b?: string }>();
});
test("inferred type for unknown/any keys", () => {
const myType = z.object({
anyOptional: z.any().optional(),
anyRequired: z.any(),
unknownOptional: z.unknown().optional(),
unknownRequired: z.unknown(),
});
type myType = z.infer<typeof myType>;
expectTypeOf<myType>().toEqualTypeOf<{
anyOptional?: any;
anyRequired: any;
unknownOptional?: unknown;
unknownRequired: unknown;
}>();
});
test("strictObject", async () => {
const strictObj = z.strictObject({
name: z.string(),
});
const syncResult = strictObj.safeParse({ name: "asdf", unexpected: 13 });
expect(syncResult.success).toEqual(false);
const asyncResult = await strictObj.spa({ name: "asdf", unexpected: 13 });
expect(asyncResult.success).toEqual(false);
});
test("object with refine", async () => {
const schema = z
.object({
a: z.string().default("foo"),
b: z.number(),
})
.refine(() => true);
expect(schema.parse({ b: 5 })).toEqual({ b: 5, a: "foo" });
const result = await schema.parseAsync({ b: 5 });
expect(result).toEqual({ b: 5, a: "foo" });
});
test("intersection of object with date", async () => {
const schema = z.object({
a: z.date(),
});
expect(z.intersection(schema, schema).parse({ a: new Date(1637353595983) })).toEqual({
a: new Date(1637353595983),
});
const result = await schema.parseAsync({ a: new Date(1637353595983) });
expect(result).toEqual({ a: new Date(1637353595983) });
});
test("intersection of object with refine with date", async () => {
const schema = z
.object({
a: z.date(),
})
.refine(() => true);
expect(z.intersection(schema, schema).parse({ a: new Date(1637353595983) })).toEqual({
a: new Date(1637353595983),
});
const result = await schema.parseAsync({ a: new Date(1637353595983) });
expect(result).toEqual({ a: new Date(1637353595983) });
});
test("constructor key", () => {
const person = z
.object({
name: z.string(),
})
.strict();
expect(() =>
person.parse({
name: "bob dylan",
constructor: 61,
})
).toThrow();
});
test("constructor key", () => {
const Example = z.object({
prop: z.string(),
opt: z.number().optional(),
arr: z.string().array(),
});
type Example = z.infer<typeof Example>;
expectTypeOf<keyof Example>().toEqualTypeOf<"prop" | "opt" | "arr">();
});
test("catchall", () => {
const a = z.object({});
expect(a._zod.def.catchall).toBeUndefined();
const b = z.strictObject({});
expect(b._zod.def.catchall).toBeInstanceOf(core.$ZodNever);
const c = z.looseObject({});
expect(c._zod.def.catchall).toBeInstanceOf(core.$ZodUnknown);
const d = z.object({}).catchall(z.number());
expect(d._zod.def.catchall).toBeInstanceOf(core.$ZodNumber);
});
test("unknownkeys merging", () => {
// This one is "strict"
const a = z.looseObject({
a: z.string(),
});
const b = z.strictObject({ b: z.string() });
// incoming object overrides
const c = a.merge(b);
expect(c._zod.def.catchall).toBeInstanceOf(core.$ZodNever);
});
const personToExtend = z.object({
firstName: z.string(),
lastName: z.string(),
});
test("extend() should return schema with new key", () => {
const PersonWithNickname = personToExtend.extend({ nickName: z.string() });
type PersonWithNickname = z.infer<typeof PersonWithNickname>;
const expected = { firstName: "f", nickName: "n", lastName: "l" };
const actual = PersonWithNickname.parse(expected);
expect(actual).toEqual(expected);
expectTypeOf<keyof PersonWithNickname>().toEqualTypeOf<"firstName" | "lastName" | "nickName">();
expectTypeOf<PersonWithNickname>().toEqualTypeOf<{ firstName: string; lastName: string; nickName: string }>();
});
test("extend() should have power to override existing key", () => {
const PersonWithNumberAsLastName = personToExtend.extend({
lastName: z.number(),
});
type PersonWithNumberAsLastName = z.infer<typeof PersonWithNumberAsLastName>;
const expected = { firstName: "f", lastName: 42 };
const actual = PersonWithNumberAsLastName.parse(expected);
expect(actual).toEqual(expected);
expectTypeOf<PersonWithNumberAsLastName>().toEqualTypeOf<{ firstName: string; lastName: number }>();
});
test("passthrough index signature", () => {
const a = z.object({ a: z.string() });
type a = z.infer<typeof a>;
expectTypeOf<a>().toEqualTypeOf<{ a: string }>();
const b = a.passthrough();
type b = z.infer<typeof b>;
expectTypeOf<b>().toEqualTypeOf<{ a: string; [k: string]: unknown }>();
});
// test("xor", () => {
// type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
// type XOR<T, U> = T extends object ? (U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : U) : T;
// type A = { name: string; a: number };
// type B = { name: string; b: number };
// type C = XOR<A, B>;
// type Outer = { data: C };
// const Outer = z.object({
// data: z.union([z.object({ name: z.string(), a: z.number() }), z.object({ name: z.string(), b: z.number() })]),
// }) satisfies z.ZodType<Outer, any>;
// });
test("assignability", () => {
z.object({ a: z.string() }) satisfies z.ZodObject<{ a: z.ZodString }>;
z.object({ a: z.string() }).catchall(z.number()) satisfies z.ZodObject<{ a: z.ZodString }>;
z.object({ a: z.string() }).strict() satisfies z.ZodObject;
z.object({}) satisfies z.ZodObject;
z.looseObject({ name: z.string() }) satisfies z.ZodObject<
{
name: z.ZodString;
},
z.core.$loose
>;
z.looseObject({ name: z.string() }) satisfies z.ZodObject<{
name: z.ZodString;
}>;
z.strictObject({ name: z.string() }) satisfies z.ZodObject<
{
name: z.ZodString;
},
z.core.$loose
>;
z.strictObject({ name: z.string() }) satisfies z.ZodObject<
{
name: z.ZodString;
},
z.core.$strict
>;
z.object({ name: z.string() }) satisfies z.ZodObject<{
name: z.ZodString;
}>;
z.object({
a: z.string(),
b: z.number(),
c: z.boolean(),
}) satisfies z.core.$ZodObject;
});
test("null prototype", () => {
const schema = z.object({ a: z.string() });
const obj = Object.create(null);
obj.a = "foo";
expect(schema.parse(obj)).toEqual({ a: "foo" });
});
test("empty objects", () => {
const A = z.looseObject({});
type Ain = z.input<typeof A>;
expectTypeOf<Ain>().toEqualTypeOf<Record<string, unknown>>();
type Aout = z.output<typeof A>;
expectTypeOf<Aout>().toEqualTypeOf<Record<string, unknown>>();
const B = z.object({});
type Bout = z.output<typeof B>;
expectTypeOf<Bout>().toEqualTypeOf<Record<string, never>>();
type Bin = z.input<typeof B>;
expectTypeOf<Bin>().toEqualTypeOf<Record<string, never>>();
const C = z.strictObject({});
type Cout = z.output<typeof C>;
expectTypeOf<Cout>().toEqualTypeOf<Record<string, never>>();
type Cin = z.input<typeof C>;
expectTypeOf<Cin>().toEqualTypeOf<Record<string, never>>();
});
test("preserve key order", () => {
const schema = z.object({
a: z.string().optional(),
b: z.string(),
});
const r1 = schema.safeParse({ a: "asdf", b: "qwer" });
const r2 = schema.safeParse({ a: "asdf", b: "qwer" }, { jitless: true });
expect(Object.keys(r1.data!)).toMatchInlineSnapshot(`
[
"a",
"b",
]
`);
expect(Object.keys(r1.data!)).toEqual(Object.keys(r2.data!));
});
test("empty shape", () => {
const a = z.object({});
a.parse({});
a.parse({}, { jitless: true });
a.parse(Object.create(null));
a.parse(Object.create(null), { jitless: true });
expect(() => a.parse([])).toThrow();
expect(() => a.parse([], { jitless: true })).toThrow();
});
test("zodtype assignability", () => {
// Does not error
z.object({ hello: z.string().optional() }) satisfies z.ZodType<{ hello?: string | undefined }>;
z.object({ hello: z.string() }) satisfies z.ZodType<{ hello?: string | undefined }>;
// @ts-expect-error
z.object({}) satisfies z.ZodType<{ hello: string | undefined }>;
// @ts-expect-error
z.object({ hello: z.string().optional() }) satisfies z.ZodType<{ hello: string | undefined }>;
// @ts-expect-error
z.object({ hello: z.string().optional() }) satisfies z.ZodType<{ hello: string }>;
// @ts-expect-error
z.object({ hello: z.number() }) satisfies z.ZodType<{ hello?: string | undefined }>;
});
test("index signature in shape", () => {
function makeZodObj<const T extends string>(key: T) {
return z.looseObject({
[key]: z.string(),
});
}
const schema = makeZodObj("foo");
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<Record<string, string>>();
});

View File

@ -0,0 +1,123 @@
// @ts-ignore TS6133
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test(".optional()", () => {
const schema = z.string().optional();
expect(schema.parse("adsf")).toEqual("adsf");
expect(schema.parse(undefined)).toEqual(undefined);
expect(schema.safeParse(null).success).toEqual(false);
expectTypeOf<typeof schema._output>().toEqualTypeOf<string | undefined>();
});
test("unwrap", () => {
const unwrapped = z.string().optional().unwrap();
expect(unwrapped).toBeInstanceOf(z.ZodString);
});
test("optionality", () => {
const a = z.string();
expect(a._zod.optin).toEqual(undefined);
expect(a._zod.optout).toEqual(undefined);
const b = z.string().optional();
expect(b._zod.optin).toEqual("optional");
expect(b._zod.optout).toEqual("optional");
const c = z.string().default("asdf");
expect(c._zod.optin).toEqual("optional");
expect(c._zod.optout).toEqual(undefined);
const d = z.string().optional().nullable();
expect(d._zod.optin).toEqual("optional");
expect(d._zod.optout).toEqual("optional");
const e = z.string().default("asdf").nullable();
expect(e._zod.optin).toEqual("optional");
expect(e._zod.optout).toEqual(undefined);
// z.undefined should NOT be optional
const f = z.undefined();
expect(f._zod.optin).toEqual("optional");
expect(f._zod.optout).toEqual("optional");
expectTypeOf<typeof f._zod.optin>().toEqualTypeOf<"optional" | undefined>();
expectTypeOf<typeof f._zod.optout>().toEqualTypeOf<"optional" | undefined>();
// z.union should be optional if any of the types are optional
const g = z.union([z.string(), z.undefined()]);
expect(g._zod.optin).toEqual("optional");
expect(g._zod.optout).toEqual("optional");
expectTypeOf<typeof g._zod.optin>().toEqualTypeOf<"optional" | undefined>();
expectTypeOf<typeof g._zod.optout>().toEqualTypeOf<"optional" | undefined>();
const h = z.union([z.string(), z.optional(z.string())]);
expect(h._zod.optin).toEqual("optional");
expect(h._zod.optout).toEqual("optional");
expectTypeOf<typeof h._zod.optin>().toEqualTypeOf<"optional">();
expectTypeOf<typeof h._zod.optout>().toEqualTypeOf<"optional">();
});
test("pipe optionality", () => {
z.string().optional()._zod.optin;
const a = z.string().optional().pipe(z.string());
expect(a._zod.optin).toEqual("optional");
expect(a._zod.optout).toEqual(undefined);
expectTypeOf<typeof a._zod.optin>().toEqualTypeOf<"optional">();
expectTypeOf<typeof a._zod.optout>().toEqualTypeOf<"optional" | undefined>();
const b = z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().optional());
expect(b._zod.optin).toEqual(undefined);
expect(b._zod.optout).toEqual("optional");
expectTypeOf<typeof b._zod.optin>().toEqualTypeOf<"optional" | undefined>();
expectTypeOf<typeof b._zod.optout>().toEqualTypeOf<"optional">();
const c = z.string().default("asdf").pipe(z.string());
expect(c._zod.optin).toEqual("optional");
expect(c._zod.optout).toEqual(undefined);
const d = z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().default("asdf"));
expect(d._zod.optin).toEqual(undefined);
expect(d._zod.optout).toEqual(undefined);
});
test("pipe optionality inside objects", () => {
const schema = z.object({
a: z.string().optional(),
b: z.string().optional().pipe(z.string()),
c: z.string().default("asdf").pipe(z.string()),
d: z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().optional()),
e: z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().default("asdf")),
});
type SchemaIn = z.input<typeof schema>;
expectTypeOf<SchemaIn>().toEqualTypeOf<{
a?: string | undefined;
b?: string | undefined;
c?: string | undefined;
d: string;
e: string;
}>();
type SchemaOut = z.output<typeof schema>;
expectTypeOf<SchemaOut>().toEqualTypeOf<{
a?: string | undefined;
b: string;
c: string;
d?: string | undefined;
e: string;
}>();
});

View File

@ -0,0 +1,147 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const nested = z.object({
name: z.string(),
age: z.number(),
outer: z.object({
inner: z.string(),
}),
array: z.array(z.object({ asdf: z.string() })),
});
test("shallow inference", () => {
const shallow = nested.partial();
type shallow = z.infer<typeof shallow>;
expectTypeOf<shallow>().toEqualTypeOf<{
name?: string | undefined;
age?: number | undefined;
outer?: { inner: string } | undefined;
array?: { asdf: string }[] | undefined;
}>();
});
test("shallow partial parse", () => {
const shallow = nested.partial();
shallow.parse({});
shallow.parse({
name: "asdf",
age: 23143,
});
});
test("required", () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
nullableField: z.number().nullable(),
nullishField: z.string().nullish(),
});
const requiredObject = object.required();
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.name.unwrap()).toBeInstanceOf(z.ZodString);
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.age.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.field.unwrap()).toBeInstanceOf(z.ZodDefault);
expect(requiredObject.shape.nullableField).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.nullableField.unwrap()).toBeInstanceOf(z.ZodNullable);
expect(requiredObject.shape.nullishField).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.nullishField.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(requiredObject.shape.nullishField.unwrap().unwrap()).toBeInstanceOf(z.ZodNullable);
});
test("required inference", () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
nullableField: z.number().nullable(),
nullishField: z.string().nullish(),
});
const requiredObject = object.required();
type required = z.infer<typeof requiredObject>;
type expected = {
name: string;
age: number;
field: string;
nullableField: number | null;
nullishField: string | null;
};
expectTypeOf<expected>().toEqualTypeOf<required>();
});
test("required with mask", () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
country: z.string().optional(),
});
const requiredObject = object.required({ age: true });
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString);
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault);
expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional);
});
test("required with mask -- ignore falsy values", () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
country: z.string().optional(),
});
// @ts-expect-error
const requiredObject = object.required({ age: true, country: false });
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString);
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault);
expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional);
});
test("partial with mask", async () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
country: z.string(),
});
const masked = object.partial({ age: true, field: true, name: true }).strict();
expect(masked.shape.name).toBeInstanceOf(z.ZodOptional);
expect(masked.shape.age).toBeInstanceOf(z.ZodOptional);
expect(masked.shape.field).toBeInstanceOf(z.ZodOptional);
expect(masked.shape.country).toBeInstanceOf(z.ZodString);
masked.parse({ country: "US" });
await masked.parseAsync({ country: "US" });
});
test("partial with mask -- ignore falsy values", async () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
country: z.string(),
});
// @ts-expect-error
const masked = object.partial({ name: true, country: false }).strict();
expect(masked.shape.name).toBeInstanceOf(z.ZodOptional);
expect(masked.shape.age).toBeInstanceOf(z.ZodOptional);
expect(masked.shape.field).toBeInstanceOf(z.ZodDefault);
expect(masked.shape.country).toBeInstanceOf(z.ZodString);
masked.parse({ country: "US" });
await masked.parseAsync({ country: "US" });
});

View File

@ -0,0 +1,127 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const fish = z.object({
name: z.string(),
age: z.number(),
nested: z.object({}),
});
test("pick type inference", () => {
const nameonlyFish = fish.pick({ name: true });
type nameonlyFish = z.infer<typeof nameonlyFish>;
expectTypeOf<nameonlyFish>().toEqualTypeOf<{ name: string }>();
});
test("pick parse - success", () => {
const nameonlyFish = fish.pick({ name: true });
nameonlyFish.parse({ name: "bob" });
// @ts-expect-error checking runtime picks `name` only.
const anotherNameonlyFish = fish.pick({ name: true, age: false });
anotherNameonlyFish.parse({ name: "bob" });
});
test("pick parse - fail", () => {
fish.pick({ name: true }).parse({ name: "12" } as any);
fish.pick({ name: true }).parse({ name: "bob", age: 12 } as any);
fish.pick({ age: true }).parse({ age: 12 } as any);
const nameonlyFish = fish.pick({ name: true }).strict();
const bad1 = () => nameonlyFish.parse({ name: 12 } as any);
const bad2 = () => nameonlyFish.parse({ name: "bob", age: 12 } as any);
const bad3 = () => nameonlyFish.parse({ age: 12 } as any);
// @ts-expect-error checking runtime picks `name` only.
const anotherNameonlyFish = fish.pick({ name: true, age: false }).strict();
const bad4 = () => anotherNameonlyFish.parse({ name: "bob", age: 12 } as any);
expect(bad1).toThrow();
expect(bad2).toThrow();
expect(bad3).toThrow();
expect(bad4).toThrow();
});
test("pick - remove optional", () => {
const schema = z.object({ a: z.string(), b: z.string().optional() });
expect("a" in schema._zod.def.shape).toEqual(true);
expect("b" in schema._zod.def.shape!).toEqual(true);
const picked = schema.pick({ a: true });
expect("a" in picked._zod.def.shape).toEqual(true);
expect("b" in picked._zod.def.shape!).toEqual(false);
});
test("omit type inference", () => {
const nonameFish = fish.omit({ name: true });
type nonameFish = z.infer<typeof nonameFish>;
expectTypeOf<nonameFish>().toEqualTypeOf<{ age: number; nested: Record<string, never> }>();
});
test("omit parse - success", () => {
const nonameFish = fish.omit({ name: true });
nonameFish.parse({ age: 12, nested: {} });
// @ts-expect-error checking runtime omits `name` only.
const anotherNonameFish = fish.omit({ name: true, age: false });
anotherNonameFish.parse({ age: 12, nested: {} });
});
test("omit parse - fail", () => {
const nonameFish = fish.omit({ name: true });
const bad1 = () => nonameFish.parse({ name: 12 } as any);
const bad2 = () => nonameFish.parse({ age: 12 } as any);
const bad3 = () => nonameFish.parse({} as any);
// @ts-expect-error checking runtime omits `name` only.
const anotherNonameFish = fish.omit({ name: true, age: false });
const bad4 = () => anotherNonameFish.parse({ nested: {} } as any);
expect(bad1).toThrow();
expect(bad2).toThrow();
expect(bad3).toThrow();
expect(bad4).toThrow();
});
test("omit - remove optional", () => {
const schema = z.object({ a: z.string(), b: z.string().optional() });
expect("a" in schema._zod.def.shape).toEqual(true);
const omitted = schema.omit({ a: true });
expect("a" in omitted._zod.def.shape).toEqual(false);
});
test("nonstrict inference", () => {
const laxfish = fish.pick({ name: true }).catchall(z.any());
type laxfish = z.infer<typeof laxfish>;
expectTypeOf<laxfish>().toEqualTypeOf<{ name: string; [k: string]: any }>();
});
test("nonstrict parsing - pass", () => {
const laxfish = fish.passthrough().pick({ name: true });
laxfish.parse({ name: "asdf", whatever: "asdf" });
laxfish.parse({ name: "asdf", age: 12, nested: {} });
});
test("nonstrict parsing - fail", () => {
const laxfish = fish.passthrough().pick({ name: true });
const bad = () => laxfish.parse({ whatever: "asdf" } as any);
expect(bad).toThrow();
});
test("pick/omit/required/partial - do not allow unknown keys", () => {
const schema = z.object({
name: z.string(),
age: z.number(),
});
expect(() => schema.pick({ name: true, asdf: true })).toThrow();
// @ts-expect-error
expect(() => schema.pick({ $unknown: true })).toThrow();
// @ts-expect-error
expect(() => schema.omit({ $unknown: true })).toThrow();
// @ts-expect-error
expect(() => schema.required({ $unknown: true })).toThrow();
// @ts-expect-error
expect(() => schema.partial({ $unknown: true })).toThrow();
});

View File

@ -0,0 +1,81 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("string to number pipe", () => {
const schema = z.string().transform(Number).pipe(z.number());
expect(schema.parse("1234")).toEqual(1234);
});
test("string to number pipe async", async () => {
const schema = z
.string()
.transform(async (val) => Number(val))
.pipe(z.number());
expect(await schema.parseAsync("1234")).toEqual(1234);
});
test("string with default fallback", () => {
const stringWithDefault = z
.pipe(
z.transform((v) => (v === "none" ? undefined : v)),
z.string()
)
.catch("default");
expect(stringWithDefault.parse("ok")).toBe("ok");
expect(stringWithDefault.parse(undefined)).toBe("default");
expect(stringWithDefault.parse("none")).toBe("default");
expect(stringWithDefault.parse(15)).toBe("default");
});
test("continue on non-fatal errors", () => {
const schema = z
.string()
.refine((c) => c === "1234", "A")
.transform((val) => Number(val))
.refine((c) => c === 1234, "B");
schema.parse("1234");
expect(schema.safeParse("4321")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "A"
},
{
"code": "custom",
"path": [],
"message": "B"
}
]],
"success": false,
}
`);
});
test("break on fatal errors", () => {
const schema = z
.string()
.refine((c) => c === "1234", { message: "A", abort: true })
.transform((val) => Number(val))
.refine((c) => c === 1234, "B");
schema.parse("1234");
expect(schema.safeParse("4321")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "A"
}
]],
"success": false,
}
`);
});

View File

@ -0,0 +1,37 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("basic prefault", () => {
const a = z.prefault(z.string().trim(), " default ");
expect(a).toBeInstanceOf(z.ZodPrefault);
expect(a.parse(" asdf ")).toEqual("asdf");
expect(a.parse(undefined)).toEqual("default");
type inp = z.input<typeof a>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof a>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("prefault inside object", () => {
// test optinality
const a = z.object({
name: z.string().optional(),
age: z.number().default(1234),
email: z.string().prefault("1234"),
});
type inp = z.input<typeof a>;
expectTypeOf<inp>().toEqualTypeOf<{
name?: string | undefined;
age?: number | undefined;
email?: string | undefined;
}>();
type out = z.output<typeof a>;
expectTypeOf<out>().toEqualTypeOf<{
name?: string | undefined;
age: number;
email: string;
}>();
});

View File

@ -0,0 +1,298 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("preprocess", () => {
const schema = z.preprocess((data) => [data], z.string().array());
const value = schema.parse("asdf");
expect(value).toEqual(["asdf"]);
expectTypeOf<(typeof schema)["_input"]>().toEqualTypeOf<unknown>();
});
test("async preprocess", async () => {
const schema = z.preprocess(async (data) => {
return [data];
}, z.string().array());
const value = await schema.safeParseAsync("asdf");
expect(value.data).toEqual(["asdf"]);
expect(value).toMatchInlineSnapshot(`
{
"data": [
"asdf",
],
"success": true,
}
`);
});
test("ctx.addIssue accepts string", () => {
const schema = z.preprocess((_, ctx) => {
ctx.addIssue("bad stuff");
}, z.string());
const result = schema.safeParse("asdf");
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"message": "bad stuff",
"code": "custom",
"path": []
}
]],
"success": false,
}
`);
});
test("preprocess ctx.addIssue with parse", () => {
const a = z.preprocess((data, ctx) => {
ctx.addIssue({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string());
const result = a.safeParse("asdf");
// expect(result.error!.toJSON()).toContain("not one of our allowed strings");
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]],
"success": false,
}
`);
});
test("preprocess ctx.addIssue non-fatal by default", () => {
const schema = z.preprocess((data, ctx) => {
ctx.addIssue({
code: "custom",
message: `custom error`,
});
return data;
}, z.string());
const result = schema.safeParse(1234);
expect(result.error!.issues).toHaveLength(2);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "custom error",
"path": []
},
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]],
"success": false,
}
`);
});
test("preprocess ctx.addIssue fatal true", () => {
const schema = z.preprocess((data, ctx) => {
ctx.addIssue({
input: data,
code: "custom",
origin: "custom",
message: `custom error`,
fatal: true,
});
return data;
}, z.string());
const result = schema.safeParse(1234);
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"origin": "custom",
"message": "custom error",
"fatal": true,
"path": []
}
]],
"success": false,
}
`);
});
test("async preprocess ctx.addIssue with parseAsync", async () => {
const schema = z.preprocess(async (data, ctx) => {
ctx.addIssue({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string());
const result = await schema.safeParseAsync("asdf");
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]],
"success": false,
}
`);
});
test("z.NEVER in preprocess", () => {
const foo = z.preprocess((val, ctx) => {
if (!val) {
ctx.addIssue({ input: val, code: "custom", message: "bad" });
return z.NEVER;
}
return val;
}, z.number());
type foo = z.infer<typeof foo>;
expectTypeOf<foo>().toEqualTypeOf<number>();
const result = foo.safeParse(undefined);
expect(result.error!.issues).toHaveLength(2);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "bad",
"path": []
},
{
"expected": "number",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected number, received object"
}
]],
"success": false,
}
`);
});
test("preprocess as the second property of object", () => {
const schema = z.object({
nonEmptyStr: z.string().min(1),
positiveNum: z.preprocess((v) => Number(v), z.number().positive()),
});
const result = schema.safeParse({
nonEmptyStr: "",
positiveNum: "",
});
expect(result.error!.issues).toHaveLength(2);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"nonEmptyStr"
],
"message": "Too small: expected string to have >=1 characters"
},
{
"origin": "number",
"code": "too_small",
"minimum": 0,
"inclusive": false,
"path": [
"positiveNum"
],
"message": "Too small: expected number to be >0"
}
]],
"success": false,
}
`);
});
test("preprocess validates with sibling errors", () => {
const schema = z.object({
missing: z.string().refine(() => false),
preprocess: z.preprocess((data: any) => data?.trim(), z.string().regex(/ asdf/)),
});
const result = schema.safeParse({ preprocess: " asdf" });
expect(result.error!.issues).toHaveLength(2);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"missing"
],
"message": "Invalid input: expected string, received undefined"
},
{
"origin": "string",
"code": "invalid_format",
"format": "regex",
"pattern": "/ asdf/",
"path": [
"preprocess"
],
"message": "Invalid string: must match pattern / asdf/"
}
]],
"success": false,
}
`);
});
test("perform transform with non-fatal issues", () => {
const A = z
.string()
.refine((_) => false)
.min(4)
.transform((val) => val.length)
.pipe(z.number())
.refine((_) => false);
expect(A.safeParse("asdfasdf").error!.issues).toHaveLength(2);
expect(A.safeParse("asdfasdf").error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
},
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});

View File

@ -0,0 +1,175 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const literalStringSchema = z.literal("asdf");
const literalNumberSchema = z.literal(12);
const literalBooleanSchema = z.literal(true);
const literalBigIntSchema = z.literal(BigInt(42));
const stringSchema = z.string();
const numberSchema = z.number();
const bigintSchema = z.bigint();
const booleanSchema = z.boolean();
const dateSchema = z.date();
const symbolSchema = z.symbol();
const nullSchema = z.null();
const undefinedSchema = z.undefined();
const stringSchemaOptional = z.string().optional();
const stringSchemaNullable = z.string().nullable();
const numberSchemaOptional = z.number().optional();
const numberSchemaNullable = z.number().nullable();
const bigintSchemaOptional = z.bigint().optional();
const bigintSchemaNullable = z.bigint().nullable();
const booleanSchemaOptional = z.boolean().optional();
const booleanSchemaNullable = z.boolean().nullable();
const dateSchemaOptional = z.date().optional();
const dateSchemaNullable = z.date().nullable();
const symbolSchemaOptional = z.symbol().optional();
const symbolSchemaNullable = z.symbol().nullable();
test("literal string schema", () => {
expect(literalStringSchema.parse("asdf")).toBe("asdf");
expect(() => literalStringSchema.parse("not_asdf")).toThrow();
expect(() => literalStringSchema.parse(123)).toThrow();
expect(() => literalStringSchema.parse(true)).toThrow();
expect(() => literalStringSchema.parse({})).toThrow();
});
test("literal number schema", () => {
expect(literalNumberSchema.parse(12)).toBe(12);
expect(() => literalNumberSchema.parse(13)).toThrow();
expect(() => literalNumberSchema.parse("foo")).toThrow();
expect(() => literalNumberSchema.parse(true)).toThrow();
expect(() => literalNumberSchema.parse({})).toThrow();
});
test("literal boolean schema", () => {
expect(literalBooleanSchema.parse(true)).toBe(true);
expect(() => literalBooleanSchema.parse(false)).toThrow();
expect(() => literalBooleanSchema.parse("asdf")).toThrow();
expect(() => literalBooleanSchema.parse(123)).toThrow();
expect(() => literalBooleanSchema.parse({})).toThrow();
});
test("literal bigint schema", () => {
expect(literalBigIntSchema.parse(BigInt(42))).toBe(BigInt(42));
expect(() => literalBigIntSchema.parse(BigInt(43))).toThrow();
expect(() => literalBigIntSchema.parse("asdf")).toThrow();
expect(() => literalBigIntSchema.parse(123)).toThrow();
expect(() => literalBigIntSchema.parse({})).toThrow();
});
test("string schema", () => {
stringSchema.parse("foo");
expect(() => stringSchema.parse(Math.random())).toThrow();
expect(() => stringSchema.parse(true)).toThrow();
expect(() => stringSchema.parse(undefined)).toThrow();
expect(() => stringSchema.parse(null)).toThrow();
});
test("number schema", () => {
numberSchema.parse(Math.random());
expect(() => numberSchema.parse("foo")).toThrow();
expect(() => numberSchema.parse(BigInt(17))).toThrow();
expect(() => numberSchema.parse(true)).toThrow();
expect(() => numberSchema.parse(undefined)).toThrow();
expect(() => numberSchema.parse(null)).toThrow();
});
test("bigint schema", () => {
bigintSchema.parse(BigInt(17));
expect(() => bigintSchema.parse("foo")).toThrow();
expect(() => bigintSchema.parse(Math.random())).toThrow();
expect(() => bigintSchema.parse(true)).toThrow();
expect(() => bigintSchema.parse(undefined)).toThrow();
expect(() => bigintSchema.parse(null)).toThrow();
});
test("boolean schema", () => {
booleanSchema.parse(true);
expect(() => booleanSchema.parse("foo")).toThrow();
expect(() => booleanSchema.parse(Math.random())).toThrow();
expect(() => booleanSchema.parse(undefined)).toThrow();
expect(() => booleanSchema.parse(null)).toThrow();
});
test("date schema", async () => {
dateSchema.parse(new Date());
expect(() => dateSchema.parse("foo")).toThrow();
expect(() => dateSchema.parse(Math.random())).toThrow();
expect(() => dateSchema.parse(true)).toThrow();
expect(() => dateSchema.parse(undefined)).toThrow();
expect(() => dateSchema.parse(null)).toThrow();
expect(await dateSchema.safeParseAsync(new Date("invalid"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "date",
"code": "invalid_type",
"received": "Invalid Date",
"path": [],
"message": "Invalid input: expected date, received Date"
}
]],
"success": false,
}
`);
});
test("symbol schema", () => {
symbolSchema.parse(Symbol("foo"));
expect(() => symbolSchema.parse("foo")).toThrow();
expect(() => symbolSchema.parse(Math.random())).toThrow();
expect(() => symbolSchema.parse(true)).toThrow();
expect(() => symbolSchema.parse(new Date())).toThrow();
expect(() => symbolSchema.parse(undefined)).toThrow();
expect(() => symbolSchema.parse(null)).toThrow();
});
test("undefined schema", () => {
undefinedSchema.parse(undefined);
expect(() => undefinedSchema.parse("foo")).toThrow();
expect(() => undefinedSchema.parse(Math.random())).toThrow();
expect(() => undefinedSchema.parse(true)).toThrow();
expect(() => undefinedSchema.parse(null)).toThrow();
});
test("null schema", () => {
nullSchema.parse(null);
expect(() => nullSchema.parse("foo")).toThrow();
expect(() => nullSchema.parse(Math.random())).toThrow();
expect(() => nullSchema.parse(true)).toThrow();
expect(() => nullSchema.parse(undefined)).toThrow();
});
test("primitive inference", () => {
expectTypeOf<z.TypeOf<typeof literalStringSchema>>().toEqualTypeOf<"asdf">();
expectTypeOf<z.TypeOf<typeof literalNumberSchema>>().toEqualTypeOf<12>();
expectTypeOf<z.TypeOf<typeof literalBooleanSchema>>().toEqualTypeOf<true>();
expectTypeOf<z.TypeOf<typeof literalBigIntSchema>>().toEqualTypeOf<bigint>();
expectTypeOf<z.TypeOf<typeof stringSchema>>().toEqualTypeOf<string>();
expectTypeOf<z.TypeOf<typeof numberSchema>>().toEqualTypeOf<number>();
expectTypeOf<z.TypeOf<typeof bigintSchema>>().toEqualTypeOf<bigint>();
expectTypeOf<z.TypeOf<typeof booleanSchema>>().toEqualTypeOf<boolean>();
expectTypeOf<z.TypeOf<typeof dateSchema>>().toEqualTypeOf<Date>();
expectTypeOf<z.TypeOf<typeof symbolSchema>>().toEqualTypeOf<symbol>();
expectTypeOf<z.TypeOf<typeof nullSchema>>().toEqualTypeOf<null>();
expectTypeOf<z.TypeOf<typeof undefinedSchema>>().toEqualTypeOf<undefined>();
expectTypeOf<z.TypeOf<typeof stringSchemaOptional>>().toEqualTypeOf<string | undefined>();
expectTypeOf<z.TypeOf<typeof stringSchemaNullable>>().toEqualTypeOf<string | null>();
expectTypeOf<z.TypeOf<typeof numberSchemaOptional>>().toEqualTypeOf<number | undefined>();
expectTypeOf<z.TypeOf<typeof numberSchemaNullable>>().toEqualTypeOf<number | null>();
expectTypeOf<z.TypeOf<typeof bigintSchemaOptional>>().toEqualTypeOf<bigint | undefined>();
expectTypeOf<z.TypeOf<typeof bigintSchemaNullable>>().toEqualTypeOf<bigint | null>();
expectTypeOf<z.TypeOf<typeof booleanSchemaOptional>>().toEqualTypeOf<boolean | undefined>();
expectTypeOf<z.TypeOf<typeof booleanSchemaNullable>>().toEqualTypeOf<boolean | null>();
expectTypeOf<z.TypeOf<typeof dateSchemaOptional>>().toEqualTypeOf<Date | undefined>();
expectTypeOf<z.TypeOf<typeof dateSchemaNullable>>().toEqualTypeOf<Date | null>();
expectTypeOf<z.TypeOf<typeof symbolSchemaOptional>>().toEqualTypeOf<symbol | undefined>();
expectTypeOf<z.TypeOf<typeof symbolSchemaNullable>>().toEqualTypeOf<symbol | null>();
});
test("get literal values", () => {
expect(literalStringSchema.values).toEqual(new Set(["asdf"]));
expect(literalStringSchema._zod.def.values).toEqual(["asdf"]);
});

View File

@ -0,0 +1,81 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const promSchema = z.promise(
z.object({
name: z.string(),
age: z.number(),
})
);
test("promise inference", () => {
type promSchemaType = z.infer<typeof promSchema>;
expectTypeOf<promSchemaType>().toEqualTypeOf<{ name: string; age: number }>();
});
test("promise parsing success", async () => {
// expect(() => promSchema.parse(Promise.resolve({ name: "Bobby", age: 10 }))).toThrow();
const pr = promSchema.parseAsync(Promise.resolve({ name: "Bobby", age: 10 }));
expect(pr).toBeInstanceOf(Promise);
const result = await pr;
expect(result).toMatchInlineSnapshot(`
{
"age": 10,
"name": "Bobby",
}
`);
});
test("promise parsing fail", async () => {
const bad = await promSchema.safeParseAsync(Promise.resolve({ name: "Bobby", age: "10" }));
expect(bad.success).toBe(false);
expect(bad.error).toBeInstanceOf(z.ZodError);
});
test("promise parsing fail 2", async () => {
const result = await promSchema.safeParseAsync(Promise.resolve({ name: "Bobby", age: "10" }));
expect(result.success).toBe(false);
expect(result.error).toBeInstanceOf(z.ZodError);
});
test("promise parsing fail", () => {
const bad = () => promSchema.parse({ then: () => {}, catch: {} });
expect(bad).toThrow();
});
test("sync promise parsing", () => {
expect(() => z.promise(z.string()).parse(Promise.resolve("asfd"))).toThrow();
});
const asyncFunction = z.function({
input: z.tuple([]),
output: promSchema,
});
test("async function pass", async () => {
const validatedFunction = asyncFunction.implementAsync(async () => {
return { name: "jimmy", age: 14 };
});
await expect(validatedFunction()).resolves.toEqual({
name: "jimmy",
age: 14,
});
});
test("async function fail", async () => {
const validatedFunction = asyncFunction.implementAsync(() => {
return Promise.resolve("asdf" as any);
});
await expect(validatedFunction()).rejects.toBeInstanceOf(z.core.$ZodError);
});
test("async promise parsing", () => {
const res = z.promise(z.number()).parseAsync(Promise.resolve(12));
expect(res).toBeInstanceOf(Promise);
});
test("resolves", () => {
const foo = z.literal("foo");
const res = z.promise(foo);
expect(res.unwrap()).toEqual(foo);
});

View File

@ -0,0 +1,23 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
declare module "zod/v4" {
interface ZodType {
/** @deprecated */
_classic(): string;
}
}
test("prototype extension", () => {
z.ZodType.prototype._classic = function () {
return "_classic";
};
// should pass
const result = z.string()._classic();
expect(result).toBe("_classic");
// expectTypeOf<typeof result>().toEqualTypeOf<string>();
// clean up
z.ZodType.prototype._classic = undefined;
});

View File

@ -0,0 +1,252 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
enum testEnum {
A = 0,
B = 1,
}
test("flat inference", () => {
const readonlyString = z.string().readonly();
const readonlyNumber = z.number().readonly();
const readonlyNaN = z.nan().readonly();
const readonlyBigInt = z.bigint().readonly();
const readonlyBoolean = z.boolean().readonly();
const readonlyDate = z.date().readonly();
const readonlyUndefined = z.undefined().readonly();
const readonlyNull = z.null().readonly();
const readonlyAny = z.any().readonly();
const readonlyUnknown = z.unknown().readonly();
const readonlyVoid = z.void().readonly();
const readonlyStringArray = z.array(z.string()).readonly();
const readonlyTuple = z.tuple([z.string(), z.number()]).readonly();
const readonlyMap = z.map(z.string(), z.date()).readonly();
const readonlySet = z.set(z.string()).readonly();
const readonlyStringRecord = z.record(z.string(), z.string()).readonly();
const readonlyNumberRecord = z.record(z.string(), z.number()).readonly();
const readonlyObject = z.object({ a: z.string(), 1: z.number() }).readonly();
const readonlyEnum = z.nativeEnum(testEnum).readonly();
const readonlyPromise = z.promise(z.string()).readonly();
expectTypeOf<typeof readonlyString._output>().toEqualTypeOf<string>();
expectTypeOf<typeof readonlyNumber._output>().toEqualTypeOf<number>();
expectTypeOf<typeof readonlyNaN._output>().toEqualTypeOf<number>();
expectTypeOf<typeof readonlyBigInt._output>().toEqualTypeOf<bigint>();
expectTypeOf<typeof readonlyBoolean._output>().toEqualTypeOf<boolean>();
expectTypeOf<typeof readonlyDate._output>().toEqualTypeOf<Date>();
expectTypeOf<typeof readonlyUndefined._output>().toEqualTypeOf<undefined>();
expectTypeOf<typeof readonlyNull._output>().toEqualTypeOf<null>();
expectTypeOf<typeof readonlyAny._output>().toEqualTypeOf<any>();
expectTypeOf<typeof readonlyUnknown._output>().toEqualTypeOf<Readonly<unknown>>();
expectTypeOf<typeof readonlyVoid._output>().toEqualTypeOf<void>();
expectTypeOf<typeof readonlyStringArray._output>().toEqualTypeOf<readonly string[]>();
expectTypeOf<typeof readonlyTuple._output>().toEqualTypeOf<readonly [string, number]>();
expectTypeOf<typeof readonlyMap._output>().toEqualTypeOf<ReadonlyMap<string, Date>>();
expectTypeOf<typeof readonlySet._output>().toEqualTypeOf<ReadonlySet<string>>();
expectTypeOf<typeof readonlyStringRecord._output>().toEqualTypeOf<Readonly<Record<string, string>>>();
expectTypeOf<typeof readonlyNumberRecord._output>().toEqualTypeOf<Readonly<Record<string, number>>>();
expectTypeOf<typeof readonlyObject._output>().toEqualTypeOf<{ readonly a: string; readonly 1: number }>();
expectTypeOf<typeof readonlyEnum._output>().toEqualTypeOf<Readonly<testEnum>>();
expectTypeOf<typeof readonlyPromise._output>().toEqualTypeOf<string>();
});
// test("deep inference", () => {
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[0]>>().toEqualTypeOf<string>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[1]>>().toEqualTypeOf<number>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[2]>>().toEqualTypeOf<number>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[3]>>().toEqualTypeOf<bigint>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[4]>>().toEqualTypeOf<boolean>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[5]>>().toEqualTypeOf<Date>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[6]>>().toEqualTypeOf<undefined>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[7]>>().toEqualTypeOf<null>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[8]>>().toEqualTypeOf<any>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[9]>
// >().toEqualTypeOf<Readonly<unknown>>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[10]>>().toEqualTypeOf<void>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[11]>
// >().toEqualTypeOf<(args_0: string, args_1: number, ...args_2: unknown[]) => unknown>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[12]>
// >().toEqualTypeOf<readonly string[]>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[13]>
// >().toEqualTypeOf<readonly [string, number]>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[14]>
// >().toEqualTypeOf<ReadonlyMap<string, Date>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[15]>
// >().toEqualTypeOf<ReadonlySet<Promise<string>>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[16]>
// >().toEqualTypeOf<Readonly<Record<string, string>>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[17]>
// >().toEqualTypeOf<Readonly<Record<string, number>>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[18]>
// >().toEqualTypeOf<{ readonly a: string; readonly 1: number }>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[19]>
// >().toEqualTypeOf<Readonly<testEnum>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[20]>
// >().toEqualTypeOf<Promise<string>>();
// expectTypeOf<
// z.infer<typeof crazyDeepReadonlySchema>
// >().toEqualTypeOf<ReadonlyMap<
// ReadonlySet<readonly [string, number]>,
// {
// readonly a: {
// readonly [x: string]: readonly any[];
// };
// readonly b: {
// readonly c: {
// readonly d: {
// readonly e: {
// readonly f: {
// readonly g?: {};
// };
// };
// };
// };
// };
// }
// >>();
// });
test("object freezing", async () => {
expect(Object.isFrozen(z.array(z.string()).readonly().parse(["a"]))).toBe(true);
expect(Object.isFrozen(z.tuple([z.string(), z.number()]).readonly().parse(["a", 1]))).toBe(true);
expect(
Object.isFrozen(
z
.map(z.string(), z.date())
.readonly()
.parse(new Map([["a", new Date()]]))
)
).toBe(true);
expect(Object.isFrozen(z.record(z.string(), z.string()).readonly().parse({ a: "b" }))).toBe(true);
expect(Object.isFrozen(z.record(z.string(), z.number()).readonly().parse({ a: 1 }))).toBe(true);
expect(Object.isFrozen(z.object({ a: z.string(), 1: z.number() }).readonly().parse({ a: "b", 1: 2 }))).toBe(true);
expect(
Object.isFrozen(
await z
.set(z.promise(z.string()))
.readonly()
.parseAsync(new Set([Promise.resolve("a")]))
)
).toBe(true);
expect(Object.isFrozen(await z.promise(z.string()).readonly().parseAsync(Promise.resolve("a")))).toBe(true);
});
test("async object freezing", async () => {
expect(Object.isFrozen(await z.array(z.string()).readonly().parseAsync(["a"]))).toBe(true);
expect(Object.isFrozen(await z.tuple([z.string(), z.number()]).readonly().parseAsync(["a", 1]))).toBe(true);
expect(
Object.isFrozen(
await z
.map(z.string(), z.date())
.readonly()
.parseAsync(new Map([["a", new Date()]]))
)
).toBe(true);
expect(
Object.isFrozen(
await z
.set(z.promise(z.string()))
.readonly()
.parseAsync(new Set([Promise.resolve("a")]))
)
).toBe(true);
expect(Object.isFrozen(await z.record(z.string(), z.string()).readonly().parseAsync({ a: "b" }))).toBe(true);
expect(Object.isFrozen(await z.record(z.string(), z.number()).readonly().parseAsync({ a: 1 }))).toBe(true);
expect(
Object.isFrozen(await z.object({ a: z.string(), 1: z.number() }).readonly().parseAsync({ a: "b", 1: 2 }))
).toBe(true);
expect(Object.isFrozen(await z.promise(z.string()).readonly().parseAsync(Promise.resolve("a")))).toBe(true);
});
test("readonly inference", () => {
const readonlyStringArray = z.string().array().readonly();
const readonlyStringTuple = z.tuple([z.string()]).readonly();
const deepReadonly = z.object({ a: z.string() }).readonly();
type readonlyStringArray = z.infer<typeof readonlyStringArray>;
type readonlyStringTuple = z.infer<typeof readonlyStringTuple>;
type deepReadonly = z.infer<typeof deepReadonly>;
expectTypeOf<readonlyStringArray>().toEqualTypeOf<readonly string[]>();
expectTypeOf<readonlyStringTuple>().toEqualTypeOf<readonly [string]>();
expectTypeOf<deepReadonly>().toEqualTypeOf<{ readonly a: string }>();
});
test("readonly parse", () => {
const schema = z.array(z.string()).readonly();
const readonlyArray = ["a", "b", "c"] as const;
const mutableArray = ["a", "b", "c"];
const result1 = schema.parse(readonlyArray);
const result2 = schema.parse(mutableArray);
expect(result1).toEqual(readonlyArray);
expect(result2).toEqual(mutableArray);
});
test("readonly parse with tuples", () => {
const schema = z.tuple([z.string(), z.number()]).readonly();
schema.parse(["a", 1]);
});
test("readonly and the get method", () => {
const readonlyString = z.string().readonly();
const readonlyNumber1 = z.number().readonly();
const readonlyNumber2 = z.number().readonly();
const readonlyBigInt = z.bigint().readonly();
const readonlyBoolean = z.boolean().readonly();
const readonlyDate = z.date().readonly();
const readonlyUndefined = z.undefined().readonly();
const readonlyNull = z.null().readonly();
const readonlyAny = z.any().readonly();
const readonlyUnknown = z.unknown().readonly();
const readonlyVoid = z.void().readonly();
// const readonlyFunction = z.function(z.tuple([z.string(), z.number()]), z.unknown()).readonly();
const readonlyStringArray = z.string().array().readonly();
const readonlyTuple = z.tuple([z.string(), z.number()]).readonly();
expectTypeOf<z.infer<typeof readonlyString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof readonlyNumber1>>().toEqualTypeOf<number>();
expectTypeOf<z.infer<typeof readonlyNumber2>>().toEqualTypeOf<number>();
expectTypeOf<z.infer<typeof readonlyBigInt>>().toEqualTypeOf<bigint>();
expectTypeOf<z.infer<typeof readonlyBoolean>>().toEqualTypeOf<boolean>();
expectTypeOf<z.infer<typeof readonlyDate>>().toEqualTypeOf<Date>();
expectTypeOf<z.infer<typeof readonlyUndefined>>().toEqualTypeOf<undefined>();
expectTypeOf<z.infer<typeof readonlyNull>>().toEqualTypeOf<null>();
expectTypeOf<z.infer<typeof readonlyAny>>().toEqualTypeOf<any>();
expectTypeOf<z.infer<typeof readonlyUnknown>>().toEqualTypeOf<Readonly<unknown>>();
expectTypeOf<z.infer<typeof readonlyVoid>>().toEqualTypeOf<void>();
// expectTypeOf<z.infer<typeof readonlyFunction>>().toEqualTypeOf<
// (args_0: string, args_1: number, ...args_2: unknown[]) => unknown
// >();
expectTypeOf<z.infer<typeof readonlyStringArray>>().toEqualTypeOf<readonly string[]>();
expectTypeOf<z.infer<typeof readonlyTuple>>().toEqualTypeOf<readonly [string, number]>();
expect(readonlyString.parse("asdf")).toEqual("asdf");
expect(readonlyNumber1.parse(1234)).toEqual(1234);
expect(readonlyNumber2.parse(1234)).toEqual(1234);
const bigIntVal = BigInt(1);
expect(readonlyBigInt.parse(bigIntVal)).toEqual(bigIntVal);
expect(readonlyBoolean.parse(true)).toEqual(true);
const dateVal = new Date();
expect(readonlyDate.parse(dateVal)).toEqual(dateVal);
expect(readonlyUndefined.parse(undefined)).toEqual(undefined);
expect(readonlyNull.parse(null)).toEqual(null);
expect(readonlyAny.parse("whatever")).toEqual("whatever");
expect(readonlyUnknown.parse("whatever")).toEqual("whatever");
expect(readonlyVoid.parse(undefined)).toEqual(undefined);
// expect(readonlyFunction.parse(() => void 0)).toEqual(() => void 0);
expect(readonlyStringArray.parse(["asdf"])).toEqual(["asdf"]);
expect(readonlyTuple.parse(["asdf", 1234])).toEqual(["asdf", 1234]);
});

View File

@ -0,0 +1,342 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("type inference", () => {
const booleanRecord = z.record(z.string(), z.boolean());
type booleanRecord = typeof booleanRecord._output;
const recordWithEnumKeys = z.record(z.enum(["Tuna", "Salmon"]), z.string());
type recordWithEnumKeys = z.infer<typeof recordWithEnumKeys>;
const recordWithLiteralKey = z.record(z.literal(["Tuna", "Salmon"]), z.string());
type recordWithLiteralKey = z.infer<typeof recordWithLiteralKey>;
const recordWithLiteralUnionKeys = z.record(z.union([z.literal("Tuna"), z.literal("Salmon")]), z.string());
type recordWithLiteralUnionKeys = z.infer<typeof recordWithLiteralUnionKeys>;
expectTypeOf<booleanRecord>().toEqualTypeOf<Record<string, boolean>>();
expectTypeOf<recordWithEnumKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
expectTypeOf<recordWithLiteralKey>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
expectTypeOf<recordWithLiteralUnionKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
});
test("enum exhaustiveness", () => {
const schema = z.record(z.enum(["Tuna", "Salmon"]), z.string());
expect(
schema.parse({
Tuna: "asdf",
Salmon: "asdf",
})
).toEqual({
Tuna: "asdf",
Salmon: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("literal exhaustiveness", () => {
const schema = z.record(z.literal(["Tuna", "Salmon"]), z.string());
schema.parse({
Tuna: "asdf",
Salmon: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("pipe exhaustiveness", () => {
const schema = z.record(z.enum(["Tuna", "Salmon"]).pipe(z.any()), z.string());
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf" })).toEqual({
Tuna: "asdf",
Salmon: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("union exhaustiveness", () => {
const schema = z.record(z.union([z.literal("Tuna"), z.literal("Salmon")]), z.string());
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf" })).toEqual({
Tuna: "asdf",
Salmon: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("string record parse - pass", () => {
const schema = z.record(z.string(), z.boolean());
schema.parse({
k1: true,
k2: false,
1234: false,
});
expect(schema.safeParse({ asdf: 1234 }).success).toEqual(false);
expect(schema.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "record",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected record, received string"
}
]],
"success": false,
}
`);
});
test("key and value getters", () => {
const rec = z.record(z.string(), z.number());
rec.keyType.parse("asdf");
rec.valueType.parse(1234);
});
test("is not vulnerable to prototype pollution", async () => {
const rec = z.record(
z.string(),
z.object({
a: z.string(),
})
);
const data = JSON.parse(`
{
"__proto__": {
"a": "evil"
},
"b": {
"a": "good"
}
}
`);
const obj1 = rec.parse(data);
expect(obj1.a).toBeUndefined();
const obj2 = rec.safeParse(data);
expect(obj2.success).toBe(true);
if (obj2.success) {
expect(obj2.data.a).toBeUndefined();
}
const obj3 = await rec.parseAsync(data);
expect(obj3.a).toBeUndefined();
const obj4 = await rec.safeParseAsync(data);
expect(obj4.success).toBe(true);
if (obj4.success) {
expect(obj4.data.a).toBeUndefined();
}
});
test("dont remove undefined values", () => {
const result1 = z.record(z.string(), z.any()).parse({ foo: undefined });
expect(result1).toEqual({
foo: undefined,
});
});
test("allow undefined values", () => {
const schema = z.record(z.string(), z.undefined());
expect(
Object.keys(
schema.parse({
_test: undefined,
})
)
).toEqual(["_test"]);
});
test("async parsing", async () => {
const schema = z
.record(
z.string(),
z
.string()
.optional()
.refine(async () => true)
)
.refine(async () => true);
const data = {
foo: "bar",
baz: "qux",
};
const result = await schema.safeParseAsync(data);
expect(result.data).toEqual(data);
});
test("async parsing", async () => {
const schema = z
.record(
z.string(),
z
.string()
.optional()
.refine(async () => false)
)
.refine(async () => false);
const data = {
foo: "bar",
baz: "qux",
};
const result = await schema.safeParseAsync(data);
expect(result.success).toEqual(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"foo"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"baz"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});
test("partial record", () => {
const schema = z.partialRecord(z.string(), z.string());
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<Partial<Record<string, string>>>();
const Keys = z.enum(["id", "name", "email"]).or(z.never());
const Person = z.partialRecord(Keys, z.string());
expectTypeOf<z.infer<typeof Person>>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();
});

View File

@ -0,0 +1,356 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("recursion with z.lazy", () => {
const data = {
name: "I",
subcategories: [
{
name: "A",
subcategories: [
{
name: "1",
subcategories: [
{
name: "a",
subcategories: [],
},
],
},
],
},
],
};
const Category = z.object({
name: z.string(),
get subcategories() {
return z.array(Category).optional().nullable();
},
});
type Category = z.infer<typeof Category>;
interface _Category {
name: string;
subcategories?: _Category[] | undefined | null;
}
expectTypeOf<Category>().toEqualTypeOf<_Category>();
Category.parse(data);
});
test("recursion involving union type", () => {
const data = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null,
},
},
},
};
const LL = z.object({
value: z.number(),
get next() {
return LL.nullable();
},
});
type LL = z.infer<typeof LL>;
type _LL = {
value: number;
next: _LL | null;
};
expectTypeOf<LL>().toEqualTypeOf<_LL>();
LL.parse(data);
});
test("mutual recursion - native", () => {
const Alazy = z.object({
val: z.number(),
get b() {
return Blazy;
},
});
const Blazy = z.object({
val: z.number(),
get a() {
return Alazy.optional();
},
});
const testData = {
val: 1,
b: {
val: 5,
a: {
val: 3,
b: {
val: 4,
a: {
val: 2,
b: {
val: 1,
},
},
},
},
},
};
type Alazy = z.infer<typeof Alazy>;
type Blazy = z.infer<typeof Blazy>;
interface _Alazy {
val: number;
b: _Blazy;
}
interface _Blazy {
val: number;
a?: _Alazy | undefined;
}
expectTypeOf<Alazy>().toEqualTypeOf<_Alazy>();
expectTypeOf<Blazy>().toEqualTypeOf<_Blazy>();
Alazy.parse(testData);
Blazy.parse(testData.b);
expect(() => Alazy.parse({ val: "asdf" })).toThrow();
});
test("pick and omit with getter", () => {
const Category = z.strictObject({
name: z.string(),
get subcategories() {
return z.array(Category);
},
});
type Category = z.infer<typeof Category>;
interface _Category {
name: string;
subcategories: _Category[];
}
expectTypeOf<Category>().toEqualTypeOf<_Category>();
const PickedCategory = Category.pick({ name: true });
const OmittedCategory = Category.omit({ subcategories: true });
const picked = { name: "test" };
const omitted = { name: "test" };
PickedCategory.parse(picked);
OmittedCategory.parse(omitted);
expect(() => PickedCategory.parse({ name: "test", subcategories: [] })).toThrow();
expect(() => OmittedCategory.parse({ name: "test", subcategories: [] })).toThrow();
});
test("deferred self-recursion", () => {
const Feature = z.object({
title: z.string(),
get features(): z.ZodOptional<z.ZodArray<typeof Feature>> {
return z.optional(z.array(Feature)); //.optional();
},
});
// type Feature = z.infer<typeof Feature>;
const Output = z.object({
id: z.int(), //.nonnegative(),
name: z.string(),
get features(): z.ZodArray<typeof Feature> {
return Feature.array();
},
});
type Output = z.output<typeof Output>;
type _Feature = {
title: string;
features?: _Feature[] | undefined;
};
type _Output = {
id: number;
name: string;
features: _Feature[];
};
// expectTypeOf<Feature>().toEqualTypeOf<_Feature>();
expectTypeOf<Output>().toEqualTypeOf<_Output>();
});
test("deferred mutual recursion", () => {
const Slot = z.object({
slotCode: z.string(),
get blocks() {
return z.array(Block);
},
});
type Slot = z.infer<typeof Slot>;
const Block = z.object({
blockCode: z.string(),
get slots() {
return z.array(Slot).optional();
},
});
type Block = z.infer<typeof Block>;
const Page = z.object({
slots: z.array(Slot),
});
type Page = z.infer<typeof Page>;
type _Slot = {
slotCode: string;
blocks: _Block[];
};
type _Block = {
blockCode: string;
slots?: _Slot[] | undefined;
};
type _Page = {
slots: _Slot[];
};
expectTypeOf<Slot>().toEqualTypeOf<_Slot>();
expectTypeOf<Block>().toEqualTypeOf<_Block>();
expectTypeOf<Page>().toEqualTypeOf<_Page>();
});
test("mutual recursion with meta", () => {
const A = z
.object({
name: z.string(),
get b() {
return B;
},
})
.readonly()
.meta({ id: "A" })
.optional();
const B = z
.object({
name: z.string(),
get a() {
return A;
},
})
.readonly()
.meta({ id: "B" });
type A = z.infer<typeof A>;
type B = z.infer<typeof B>;
type _A =
| Readonly<{
name: string;
b: _B;
}>
| undefined;
// | undefined;
type _B = Readonly<{
name: string;
a?: _A;
}>;
expectTypeOf<A>().toEqualTypeOf<_A>();
expectTypeOf<B>().toEqualTypeOf<_B>();
});
test("recursion compatibility", () => {
// array
const A = z.object({
get array() {
return A.array();
},
get optional() {
return A.optional();
},
get nullable() {
return A.nullable();
},
get nonoptional() {
return A.nonoptional();
},
get readonly() {
return A.readonly();
},
get describe() {
return A.describe("A recursive type");
},
get meta() {
return A.meta({ description: "A recursive type" });
},
get pipe() {
return A.pipe(z.any());
},
get strict() {
return A.strict();
},
get tuple() {
return z.tuple([A, A]);
},
get object() {
return z
.object({
subcategories: A,
})
.strict()
.loose();
},
get union() {
return z.union([A, A]);
},
get intersection() {
return z.intersection(A, A);
},
get record() {
return z.record(z.string(), A);
},
get map() {
return z.map(z.string(), A);
},
get set() {
return z.set(A);
},
get lazy() {
return z.lazy(() => A);
},
get promise() {
return z.promise(A);
},
});
});
// biome-ignore lint: sadf
export type RecursiveA = z.ZodUnion<
[
z.ZodObject<{
a: z.ZodDefault<RecursiveA>;
b: z.ZodPrefault<RecursiveA>;
c: z.ZodNonOptional<RecursiveA>;
d: z.ZodOptional<RecursiveA>;
e: z.ZodNullable<RecursiveA>;
g: z.ZodReadonly<RecursiveA>;
h: z.ZodPipe<RecursiveA, z.ZodString>;
i: z.ZodArray<RecursiveA>;
j: z.ZodSet<RecursiveA>;
k: z.ZodMap<RecursiveA, RecursiveA>;
l: z.ZodRecord<z.ZodString, RecursiveA>;
m: z.ZodUnion<[RecursiveA, RecursiveA]>;
n: z.ZodIntersection<RecursiveA, RecursiveA>;
o: z.ZodLazy<RecursiveA>;
p: z.ZodPromise<RecursiveA>;
q: z.ZodCatch<RecursiveA>;
r: z.ZodSuccess<RecursiveA>;
s: z.ZodTransform<RecursiveA, string>;
t: z.ZodTuple<[RecursiveA, RecursiveA]>;
u: z.ZodObject<{
a: RecursiveA;
}>;
}>,
]
>;

View File

@ -0,0 +1,532 @@
import { describe, expect, test } from "vitest";
import * as z from "zod/v4";
describe("basic refinement functionality", () => {
test("should create a new schema instance when refining", () => {
const obj1 = z.object({
first: z.string(),
second: z.string(),
});
const obj2 = obj1.partial().strict();
const obj3 = obj2.refine((data) => data.first || data.second, "Either first or second should be filled in.");
expect(obj1 === (obj2 as any)).toEqual(false);
expect(obj2 === (obj3 as any)).toEqual(false);
});
test("should validate according to refinement logic", () => {
const schema = z
.object({
first: z.string(),
second: z.string(),
})
.partial()
.strict()
.refine((data) => data.first || data.second, "Either first or second should be filled in.");
// Should fail on empty object
expect(() => schema.parse({})).toThrow();
// Should pass with first property
expect(schema.parse({ first: "a" })).toEqual({ first: "a" });
// Should pass with second property
expect(schema.parse({ second: "a" })).toEqual({ second: "a" });
// Should pass with both properties
expect(schema.parse({ first: "a", second: "a" })).toEqual({ first: "a", second: "a" });
});
test("should validate strict mode correctly", () => {
const schema = z
.object({
first: z.string(),
second: z.string(),
})
.partial()
.strict();
// Should throw on extra properties
expect(() => schema.parse({ third: "adsf" })).toThrow();
});
});
describe("refinement with custom error messages", () => {
test("should use custom error message when validation fails", () => {
const validationSchema = z
.object({
email: z.string().email(),
password: z.string(),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, "Both password and confirmation must match");
const result = validationSchema.safeParse({
email: "aaaa@gmail.com",
password: "aaaaaaaa",
confirmPassword: "bbbbbbbb",
});
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].message).toEqual("Both password and confirmation must match");
}
});
});
describe("async refinements", () => {
test("should support async refinement functions", async () => {
const validationSchema = z
.object({
email: z.string().email(),
password: z.string(),
confirmPassword: z.string(),
})
.refine(
(data) => Promise.resolve().then(() => data.password === data.confirmPassword),
"Both password and confirmation must match"
);
// Should pass with matching passwords
const validData = {
email: "aaaa@gmail.com",
password: "password",
confirmPassword: "password",
};
await expect(validationSchema.parseAsync(validData)).resolves.toEqual(validData);
// Should fail with non-matching passwords
await expect(
validationSchema.parseAsync({
email: "aaaa@gmail.com",
password: "password",
confirmPassword: "different",
})
).rejects.toThrow();
});
});
describe("early termination options", () => {
test("should abort early with continue: false", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val.length < 2) {
ctx.addIssue({
code: "custom",
message: "BAD",
continue: false,
});
}
})
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("BAD");
}
});
test("should abort early with fatal: true", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val.length < 2) {
ctx.addIssue({
code: "custom",
fatal: true,
message: "BAD",
});
}
})
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("BAD");
}
});
test("should abort early with abort flag", () => {
const schema = z
.string()
.refine((_) => false, { abort: true })
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
}
});
});
describe("custom error paths", () => {
test("should use custom path in error message", async () => {
const result = await z
.object({ password: z.string(), confirm: z.string() })
.refine((data) => data.confirm === data.password, { path: ["confirm"] })
.safeParse({ password: "asdf", confirm: "qewr" });
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].path).toEqual(["confirm"]);
}
});
});
describe("superRefine functionality", () => {
test("should support multiple validation rules", () => {
const Strings = z.array(z.string()).superRefine((val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
input: val,
code: "too_big",
origin: "array",
maximum: 3,
inclusive: true,
exact: true,
message: "Too many items 😡",
});
}
if (val.length !== new Set(val).size) {
ctx.addIssue({
input: val,
code: "custom",
message: `No duplicates allowed.`,
});
}
});
// Should fail with too many items and duplicates
const result = Strings.safeParse(["asfd", "asfd", "asfd", "asfd"]);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
expect(result.error.issues[0].message).toEqual("Too many items 😡");
expect(result.error.issues[1].message).toEqual("No duplicates allowed.");
}
// Should pass with valid input
const validArray = ["asfd", "qwer"];
expect(Strings.parse(validArray)).toEqual(validArray);
});
test("should support async superRefine", async () => {
const Strings = z.array(z.string()).superRefine(async (val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
input: val,
code: "too_big",
origin: "array",
maximum: 3,
inclusive: true,
message: "Too many items 😡",
});
}
if (val.length !== new Set(val).size) {
ctx.addIssue({
input: val,
code: "custom",
message: `No duplicates allowed.`,
});
}
});
// Should fail with too many items and duplicates
const result = await Strings.safeParseAsync(["asfd", "asfd", "asfd", "asfd"]);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
}
// Should pass with valid input
const validArray = ["asfd", "qwer"];
await expect(Strings.parseAsync(validArray)).resolves.toEqual(validArray);
});
test("should accept string as shorthand for custom error message", () => {
const schema = z.string().superRefine((_, ctx) => {
ctx.addIssue("bad stuff");
});
const result = schema.safeParse("asdf");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues).toHaveLength(1);
expect(result.error.issues[0].message).toEqual("bad stuff");
}
});
test("should respect fatal flag in superRefine", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val === "") {
ctx.addIssue({
input: val,
code: "custom",
message: "foo",
fatal: true,
});
}
})
.superRefine((val, ctx) => {
if (val !== " ") {
ctx.addIssue({
input: val,
code: "custom",
message: "bar",
});
}
});
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("foo");
}
});
});
describe("chained refinements", () => {
test("should collect all validation errors when appropriate", () => {
const objectSchema = z
.object({
length: z.number(),
size: z.number(),
})
.refine(({ length }) => length > 5, {
path: ["length"],
message: "length greater than 5",
})
.refine(({ size }) => size > 7, {
path: ["size"],
message: "size greater than 7",
});
// Should fail with one error
const r1 = objectSchema.safeParse({
length: 4,
size: 9,
});
expect(r1.success).toEqual(false);
if (!r1.success) {
expect(r1.error.issues.length).toEqual(1);
expect(r1.error.issues[0].path).toEqual(["length"]);
}
// Should fail with two errors
const r2 = objectSchema.safeParse({
length: 4,
size: 3,
});
expect(r2.success).toEqual(false);
if (!r2.success) {
expect(r2.error.issues.length).toEqual(2);
}
// Should pass with valid input
const validData = {
length: 6,
size: 8,
};
expect(objectSchema.parse(validData)).toEqual(validData);
});
});
// Commented tests can be uncommented once type-checking issues are resolved
/*
describe("type refinement", () => {
test("refinement type guard", () => {
const validationSchema = z.object({
a: z.string().refine((s): s is "a" => s === "a"),
});
type Input = z.input<typeof validationSchema>;
type Schema = z.infer<typeof validationSchema>;
expectTypeOf<Input["a"]>().not.toEqualTypeOf<"a">();
expectTypeOf<Input["a"]>().toEqualTypeOf<string>();
expectTypeOf<Schema["a"]>().toEqualTypeOf<"a">();
expectTypeOf<Schema["a"]>().not.toEqualTypeOf<string>();
});
test("superRefine - type narrowing", () => {
type NarrowType = { type: string; age: number };
const schema = z
.object({
type: z.string(),
age: z.number(),
})
.nullable()
.superRefine((arg, ctx): arg is NarrowType => {
if (!arg) {
// still need to make a call to ctx.addIssue
ctx.addIssue({
input: arg,
code: "custom",
message: "cannot be null",
fatal: true,
});
return false;
}
return true;
});
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<NarrowType>();
expect(schema.safeParse({ type: "test", age: 0 }).success).toEqual(true);
expect(schema.safeParse(null).success).toEqual(false);
});
test("chained mixed refining types", () => {
type firstRefinement = { first: string; second: number; third: true };
type secondRefinement = { first: "bob"; second: number; third: true };
type thirdRefinement = { first: "bob"; second: 33; third: true };
const schema = z
.object({
first: z.string(),
second: z.number(),
third: z.boolean(),
})
.nullable()
.refine((arg): arg is firstRefinement => !!arg?.third)
.superRefine((arg, ctx): arg is secondRefinement => {
expectTypeOf<typeof arg>().toEqualTypeOf<firstRefinement>();
if (arg.first !== "bob") {
ctx.addIssue({
input: arg,
code: "custom",
message: "`first` property must be `bob`",
});
return false;
}
return true;
})
.refine((arg): arg is thirdRefinement => {
expectTypeOf<typeof arg>().toEqualTypeOf<secondRefinement>();
return arg.second === 33;
});
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<thirdRefinement>();
});
});
*/
test("when", () => {
const schema = z
.strictObject({
password: z.string().min(8),
confirmPassword: z.string(),
other: z.string(),
})
.refine(
(data) => {
console.log("running check...");
console.log(data);
console.log(data.password);
return data.password === data.confirmPassword;
},
{
message: "Passwords do not match",
path: ["confirmPassword"],
when(payload) {
if (payload.value === undefined) return false;
if (payload.value === null) return false;
// no issues with confirmPassword or password
return payload.issues.every((iss) => iss.path?.[0] !== "confirmPassword" && iss.path?.[0] !== "password");
},
}
);
expect(schema.safeParse(undefined)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]],
"success": false,
}
`);
expect(schema.safeParse(null)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received null"
}
]],
"success": false,
}
`);
expect(
schema.safeParse({
password: "asdf",
confirmPassword: "asdfg",
other: "qwer",
})
).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 8,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=8 characters"
}
]],
"success": false,
}
`);
expect(
schema.safeParse({
password: "asdf",
confirmPassword: "asdfg",
other: 1234,
})
).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 8,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=8 characters"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"other"
],
"message": "Invalid input: expected string, received number"
}
]],
"success": false,
}
`);
});

View File

@ -0,0 +1,204 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("globalRegistry", () => {
const reg = z.registry();
const a = z.string();
reg.add(a);
expect(reg.has(a)).toEqual(true);
reg.remove(a);
expect(reg.has(a)).toEqual(false);
a.register(z.globalRegistry, { field: "sup" });
expect(z.globalRegistry.has(a)).toEqual(true);
expect(z.globalRegistry.get(a)).toEqual({ field: "sup" });
z.globalRegistry.remove(a);
expect(z.globalRegistry.has(a)).toEqual(false);
});
test("z.registry", () => {
const fieldRegistry = z.registry<{ name: string; description: string }>();
const a = z.string();
fieldRegistry.add(a, { name: "hello", description: "world" });
const a_meta = fieldRegistry.get(a);
expect(a_meta).toEqual({ name: "hello", description: "world" });
fieldRegistry.remove(a);
expect(fieldRegistry.has(a)).toEqual(false);
expect(fieldRegistry.get(a)).toEqual(undefined);
});
test("z.registry no metadata", () => {
const fieldRegistry = z.registry();
const a = z.string();
fieldRegistry.add(a);
fieldRegistry.add(z.number());
expect(fieldRegistry.get(a)).toEqual(undefined);
expect(fieldRegistry.has(a)).toEqual(true);
});
test("z.registry with schema constraints", () => {
const fieldRegistry = z.registry<{ name: string; description: string }, z.ZodString>();
const a = z.string();
fieldRegistry.add(a, { name: "hello", description: "world" });
// @ts-expect-error
fieldRegistry.add(z.number(), { name: "test" });
// @ts-expect-error
z.number().register(fieldRegistry, { name: "test", description: "test" });
});
// test("z.namedRegistry", () => {
// const namedReg = z
// .namedRegistry<{ name: string; description: string }>()
// .add(z.string(), { name: "hello", description: "world" })
// .add(z.number(), { name: "number", description: "number" });
// expect(namedReg.get("hello")).toEqual({
// name: "hello",
// description: "world",
// });
// expect(namedReg.has("hello")).toEqual(true);
// expect(namedReg.get("number")).toEqual({
// name: "number",
// description: "number",
// });
// // @ts-expect-error
// namedReg.get("world");
// // @ts-expect-error
// expect(namedReg.get("world")).toEqual(undefined);
// const hello = namedReg.get("hello");
// expect(hello).toEqual({ name: "hello", description: "world" });
// expectTypeOf<typeof hello>().toEqualTypeOf<{
// name: "hello";
// description: "world";
// }>();
// expectTypeOf<typeof namedReg.items>().toEqualTypeOf<{
// hello: { name: "hello"; description: "world" };
// number: { name: "number"; description: "number" };
// }>();
// });
test("output type in registry meta", () => {
const reg = z.registry<{ out: z.$output }>();
const a = z.string();
reg.add(a, { out: "asdf" });
// @ts-expect-error
reg.add(a, 1234);
expectTypeOf(reg.get(a)).toEqualTypeOf<{ out: string } | undefined>();
});
test("output type in registry meta - objects and arrays", () => {
const reg = z.registry<{ name: string; examples: z.$output[] }>();
const a = z.string();
reg.add(a, { name: "hello", examples: ["world"] });
// @ts-expect-error
reg.add(a, { name: "hello", examples: "world" });
expectTypeOf(reg.get(a)).toEqualTypeOf<{ name: string; examples: string[] } | undefined>();
});
test("input type in registry meta", () => {
const reg = z.registry<{ in: z.$input }>();
const a = z.pipe(z.number(), z.transform(String));
reg.add(a, { in: 1234 });
// @ts-expect-error
reg.add(a, "1234");
expectTypeOf(reg.get(a)).toEqualTypeOf<{ in: number } | undefined>();
});
test("input type in registry meta - objects and arrays", () => {
const reg = z.registry<{ name: string; examples: z.$input[] }>();
const a = z.pipe(z.number(), z.transform(String));
reg.add(a, { name: "hello", examples: [1234] });
// @ts-expect-error
reg.add(a, { name: "hello", examples: "world" });
expectTypeOf(reg.get(a)).toEqualTypeOf<{ name: string; examples: number[] } | undefined>();
});
test(".meta method", () => {
const a1 = z.string();
const a2 = a1.meta({ name: "hello" });
expect(a1.meta()).toEqual(undefined);
expect(a2.meta()).toEqual({ name: "hello" });
expect(a1 === a2).toEqual(false);
});
test(".meta metadata does not bubble up", () => {
const a1 = z.string().meta({ name: "hello" });
const a2 = a1.optional();
expect(a1.meta()).toEqual({ name: "hello" });
expect(a2.meta()).toEqual(undefined);
});
test(".describe", () => {
const a1 = z.string();
const a2 = a1.describe("Hello");
expect(a1.description).toEqual(undefined);
expect(a2.description).toEqual("Hello");
});
test("inherit across clone", () => {
const A = z.string().meta({ a: true });
expect(A.meta()).toEqual({ a: true });
const B = A.meta({ b: true });
expect(B.meta()).toEqual({ a: true, b: true });
const C = B.describe("hello");
expect(C.meta()).toEqual({ a: true, b: true, description: "hello" });
});
test("loose examples", () => {
z.string().register(z.globalRegistry, {
examples: ["example"],
});
});
test("function meta witout replacement", () => {
const myReg = z.registry<{
defaulter: (arg: string, test: boolean) => number;
}>();
const mySchema = z.date();
myReg.add(mySchema, {
defaulter: (arg, _test) => {
return arg.length;
},
});
expect(myReg.get(mySchema)!.defaulter("hello", true)).toEqual(5);
});
test("function meta with replacement", () => {
const myReg = z.registry<{
defaulter: (arg: z.$input, test: boolean) => z.$output;
}>();
const mySchema = z.string().transform((val) => val.length);
myReg.add(mySchema, {
defaulter: (arg, _test) => {
return arg.length;
},
});
expect(myReg.get(mySchema)!.defaulter("hello", true)).toEqual(5);
});
test("test .clear()", () => {
const reg = z.registry();
const a = z.string();
reg.add(a);
expect(reg.has(a)).toEqual(true);
reg.clear();
expect(reg.has(a)).toEqual(false);
});

View File

@ -0,0 +1,179 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const stringSet = z.set(z.string());
type stringSet = z.infer<typeof stringSet>;
const minTwo = z.set(z.string()).min(2);
const maxTwo = z.set(z.string()).max(2);
const justTwo = z.set(z.string()).size(2);
const nonEmpty = z.set(z.string()).nonempty();
const nonEmptyMax = z.set(z.string()).nonempty().max(2);
test("type inference", () => {
expectTypeOf<stringSet>().toEqualTypeOf<Set<string>>();
});
test("valid parse", () => {
const result = stringSet.safeParse(new Set(["first", "second"]));
expect(result.success).toEqual(true);
expect(result.data!.has("first")).toEqual(true);
expect(result.data!.has("second")).toEqual(true);
expect(result.data!.has("third")).toEqual(false);
expect(() => {
minTwo.parse(new Set(["a", "b"]));
minTwo.parse(new Set(["a", "b", "c"]));
maxTwo.parse(new Set(["a", "b"]));
maxTwo.parse(new Set(["a"]));
justTwo.parse(new Set(["a", "b"]));
nonEmpty.parse(new Set(["a"]));
nonEmptyMax.parse(new Set(["a"]));
}).not.toThrow();
});
test("valid parse async", async () => {
const result = await stringSet.spa(new Set(["first", "second"]));
expect(result.success).toEqual(true);
expect(result.data!.has("first")).toEqual(true);
expect(result.data!.has("second")).toEqual(true);
expect(result.data!.has("third")).toEqual(false);
const asyncResult = await stringSet.safeParse(new Set(["first", "second"]));
expect(asyncResult.success).toEqual(true);
expect(asyncResult.data!.has("first")).toEqual(true);
expect(asyncResult.data!.has("second")).toEqual(true);
expect(asyncResult.data!.has("third")).toEqual(false);
});
test("valid parse: size-related methods", () => {
expect(() => {
minTwo.parse(new Set(["a", "b"]));
minTwo.parse(new Set(["a", "b", "c"]));
maxTwo.parse(new Set(["a", "b"]));
maxTwo.parse(new Set(["a"]));
justTwo.parse(new Set(["a", "b"]));
nonEmpty.parse(new Set(["a"]));
nonEmptyMax.parse(new Set(["a"]));
}).not.toThrow();
const sizeZeroResult = stringSet.parse(new Set());
expect(sizeZeroResult.size).toBe(0);
const sizeTwoResult = minTwo.parse(new Set(["a", "b"]));
expect(sizeTwoResult.size).toBe(2);
});
test("failing when parsing empty set in nonempty ", () => {
const result = nonEmpty.safeParse(new Set());
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_small");
});
test("failing when set is smaller than min() ", () => {
const result = minTwo.safeParse(new Set(["just_one"]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_small");
});
test("failing when set is bigger than max() ", () => {
const result = maxTwo.safeParse(new Set(["one", "two", "three"]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_big");
});
test("doesnt throw when an empty set is given", () => {
const result = stringSet.safeParse(new Set([]));
expect(result.success).toEqual(true);
});
test("throws when a Map is given", () => {
const result = stringSet.safeParse(new Map([]));
expect(result.success).toEqual(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "set",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected set, received Map"
}
]]
`);
});
test("throws when the given set has invalid input", () => {
const result = stringSet.safeParse(new Set([Symbol()]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received symbol"
}
]]
`);
});
test("throws when the given set has multiple invalid entries", () => {
const result = stringSet.safeParse(new Set([1, 2] as any[]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(2);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
},
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]]
`);
});
test("min/max", async () => {
const schema = z.set(z.string()).min(4).max(5);
const r1 = schema.safeParse(new Set(["a", "b", "c", "d"]));
expect(r1.success).toEqual(true);
const r2 = schema.safeParse(new Set(["a", "b", "c"]));
expect(r2.success).toEqual(false);
expect(r2.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"message": "Too small: expected set to have >4 items",
"minimum": 4,
"origin": "set",
"path": [],
},
]
`);
const r3 = schema.safeParse(new Set(["a", "b", "c", "d", "e", "f"]));
expect(r3.success).toEqual(false);
expect(r3.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"maximum": 5,
"message": "Too big: expected set to have <5 items",
"origin": "set",
"path": [],
},
]
`);
});

View File

@ -0,0 +1,57 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("length checks", async () => {
const schema = z.string();
const result = await schema["~standard"].validate(12);
expect(result).toMatchInlineSnapshot(`
{
"issues": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [],
},
],
}
`);
});
test("length checks", async () => {
const schema = z.string();
const result = await schema["~standard"].validate("asdf");
expect(result).toMatchInlineSnapshot(`
{
"value": "asdf",
}
`);
});
test("length checks", async () => {
const schema = z.string().refine(async (val) => val.length > 5);
const result = await schema["~standard"].validate(12);
expect(result).toMatchInlineSnapshot(`
{
"issues": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [],
},
],
}
`);
});
test("length checks", async () => {
const schema = z.string().refine(async (val) => val.length > 5);
const result = await schema["~standard"].validate("234134134");
expect(result).toMatchInlineSnapshot(`
{
"value": "234134134",
}
`);
});

View File

@ -0,0 +1,109 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("string format methods", () => {
const a = z.email().min(10);
const b = z.email().max(10);
const c = z.email().length(10);
const d = z.email().uppercase();
const e = z.email().lowercase();
// Positive and negative cases for `a`
expect(a.safeParse("longemail@example.com").success).toBe(true); // Positive
expect(a.safeParse("ort@e.co").success).toBe(false); // Negative
// Positive and negative cases for `b`
expect(b.safeParse("sho@e.co").success).toBe(true); // Positive
expect(b.safeParse("longemail@example.com").success).toBe(false); // Negative
// Positive and negative cases for `c`
expect(c.safeParse("56780@e.co").success).toBe(true); // Positive
expect(c.safeParse("shoasdfasdfrt@e.co").success).toBe(false); // Negative
// Positive and negative cases for `d`
expect(d.safeParse("EMAIL@EXAMPLE.COM").success).toBe(true); // Positive
expect(d.safeParse("email@example.com").success).toBe(false); // Negative
// Positive and negative cases for `e`
expect(e.safeParse("email@example.com").success).toBe(true); // Positive
expect(e.safeParse("EMAIL@EXAMPLE.COM").success).toBe(false); // Negative
});
test("z.stringFormat", () => {
const ccRegex = /^(?:\d{14,19}|\d{4}(?: \d{3,6}){2,4}|\d{4}(?:-\d{3,6}){2,4})$/u;
const a = z
.stringFormat("creditCard", (val) => ccRegex.test(val), {
error: `Invalid credit card number`,
})
.refine((_) => false, "Also bad");
expect(a.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "creditCard",
"path": [],
"message": "Invalid credit card number"
},
{
"code": "custom",
"path": [],
"message": "Also bad"
}
]],
"success": false,
}
`);
expect(a.safeParse("1234-5678-9012-3456")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "Also bad"
}
]],
"success": false,
}
`);
expect(a.def.pattern).toMatchInlineSnapshot(`undefined`);
const b = z
.stringFormat("creditCard", ccRegex, {
abort: true,
error: `Invalid credit card number`,
})
.refine((_) => false, "Also bad");
expect(b.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "creditCard",
"path": [],
"message": "Invalid credit card number"
}
]],
"success": false,
}
`);
expect(b.safeParse("1234-5678-9012-3456")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "Also bad"
}
]],
"success": false,
}
`);
expect(b.def.pattern).toMatchInlineSnapshot(
`/\\^\\(\\?:\\\\d\\{14,19\\}\\|\\\\d\\{4\\}\\(\\?: \\\\d\\{3,6\\}\\)\\{2,4\\}\\|\\\\d\\{4\\}\\(\\?:-\\\\d\\{3,6\\}\\)\\{2,4\\}\\)\\$/u`
);
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,66 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("z.stringbool", () => {
const a = z.stringbool();
type a = z.infer<typeof a>;
expectTypeOf<a>().toEqualTypeOf<boolean>();
type a_in = z.input<typeof a>;
expectTypeOf<a_in>().toEqualTypeOf<string>();
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 });
});
test("custom values", () => {
const b = z.stringbool({
truthy: ["y"],
falsy: ["N"],
});
expect(z.parse(b, "y")).toEqual(true);
expect(z.parse(b, "Y")).toEqual(true);
expect(z.parse(b, "n")).toEqual(false);
expect(z.parse(b, "N")).toEqual(false);
expect(z.safeParse(b, "true")).toMatchObject({ success: false });
expect(z.safeParse(b, "false")).toMatchObject({ success: false });
});
test("custom values - case sensitive", () => {
const c = z.stringbool({
truthy: ["y"],
falsy: ["N"],
case: "sensitive",
});
expect(z.parse(c, "y")).toEqual(true);
expect(z.safeParse(c, "Y")).toMatchObject({ success: false });
expect(z.parse(c, "N")).toEqual(false);
expect(z.safeParse(c, "n")).toMatchObject({ success: false });
expect(z.safeParse(c, "TRUE")).toMatchObject({ success: false });
});
// test custom error messages
test("z.stringbool with custom error messages", () => {
const a = z.stringbool("wrong!");
expect(() => a.parse("")).toThrowError("wrong!");
});

View File

@ -0,0 +1,758 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const empty = z.templateLiteral([]);
const hello = z.templateLiteral(["hello"]);
const world = z.templateLiteral(["", z.literal("world")]);
const one = z.templateLiteral([1]);
const two = z.templateLiteral(["", z.literal(2)]);
const truee = z.templateLiteral([true]);
const anotherTrue = z.templateLiteral(["", z.literal(true)]);
const falsee = z.templateLiteral([false]);
const anotherFalse = z.templateLiteral(["", z.literal(false)]);
const nulll = z.templateLiteral([null]);
const anotherNull = z.templateLiteral(["", z.null()]);
const undefinedd = z.templateLiteral([undefined]);
const anotherUndefined = z.templateLiteral(["", z.undefined()]);
const anyString = z.templateLiteral(["", z.string()]);
const lazyString = z.templateLiteral(["", z.lazy(() => z.string())]);
const anyNumber = z.templateLiteral(["", z.number()]);
const anyInt = z.templateLiteral(["", z.number().int()]);
// const anyFiniteNumber = z.templateLiteral(["", z.number().finite()]);
// const anyNegativeNumber = z.templateLiteral(["", z.number().negative()]);
// const anyPositiveNumber = z.templateLiteral(["", z.number().positive()]);
// const zeroButInADumbWay = z.templateLiteral(["", z.number().nonnegative().nonpositive()]);
// const finiteButInADumbWay = z.templateLiteral(["", z.number().min(5).max(10)]);
const bool = z.templateLiteral(["", z.boolean()]);
const bigone = z.templateLiteral(["", z.literal(BigInt(1))]);
const anyBigint = z.templateLiteral(["", z.bigint()]);
const nullableYo = z.templateLiteral(["", z.nullable(z.literal("yo"))]);
const nullableString = z.templateLiteral(["", z.nullable(z.string())]);
const optionalYeah = z.templateLiteral(["", z.literal("yeah").optional()]);
const optionalString = z.templateLiteral(["", z.string().optional()]);
const optionalNumber = z.templateLiteral(["", z.number().optional()]);
const nullishBruh = z.templateLiteral(["", z.literal("bruh").nullish()]);
const nullishString = z.templateLiteral(["", z.string().nullish()]);
const cuid = z.templateLiteral(["", z.string().cuid()]);
const cuidZZZ = z.templateLiteral(["", z.string().cuid(), "ZZZ"]);
const cuid2 = z.templateLiteral(["", z.string().cuid2()]);
const datetime = z.templateLiteral(["", z.string().datetime()]);
const email = z.templateLiteral(["", z.string().email()]);
// const ip = z.templateLiteral(["", z.string().ip()]);
const ipv4 = z.templateLiteral(["", z.string().ipv4()]);
const ipv6 = z.templateLiteral(["", z.string().ipv6()]);
const ulid = z.templateLiteral(["", z.string().ulid()]);
const uuid = z.templateLiteral(["", z.string().uuid()]);
const stringAToZ = z.templateLiteral(["", z.string().regex(/^[a-z]+$/)]);
const stringStartsWith = z.templateLiteral(["", z.string().startsWith("hello")]);
const stringEndsWith = z.templateLiteral(["", z.string().endsWith("world")]);
const stringMax5 = z.templateLiteral(["", z.string().max(5)]);
const stringMin5 = z.templateLiteral(["", z.string().min(5)]);
const stringLen5 = z.templateLiteral(["", z.string().length(5)]);
const stringMin5Max10 = z.templateLiteral(["", z.string().min(5).max(10)]);
const stringStartsWithMax5 = z.templateLiteral(["", z.string().startsWith("hello").max(5)]);
const brandedString = z.templateLiteral(["", z.string().min(1).brand("myBrand")]);
// const anything = z.templateLiteral(["", z.any()]);
const url = z.templateLiteral(["https://", z.string().regex(/\w+/), ".", z.enum(["com", "net"])]);
const measurement = z.templateLiteral([
"",
z.number().finite(),
z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]).optional(),
]);
const connectionString = z.templateLiteral([
"mongodb://",
z
.templateLiteral([
"",
z.string().regex(/\w+/).describe("username"),
":",
z.string().regex(/\w+/).describe("password"),
"@",
])
.optional(),
z.string().regex(/\w+/).describe("host"),
":",
z.number().finite().int().positive().describe("port"),
z
.templateLiteral([
"/",
z.string().regex(/\w+/).optional().describe("defaultauthdb"),
z
.templateLiteral([
"?",
z
.string()
.regex(/^\w+=\w+(&\w+=\w+)*$/)
.optional()
.describe("options"),
])
.optional(),
])
.optional(),
]);
test("template literal type inference", () => {
expectTypeOf<z.infer<typeof empty>>().toEqualTypeOf<``>();
expectTypeOf<z.infer<typeof hello>>().toEqualTypeOf<`hello`>();
expectTypeOf<z.infer<typeof world>>().toEqualTypeOf<`world`>();
expectTypeOf<z.infer<typeof one>>().toEqualTypeOf<`1`>();
expectTypeOf<z.infer<typeof two>>().toEqualTypeOf<`2`>();
expectTypeOf<z.infer<typeof truee>>().toEqualTypeOf<`true`>();
expectTypeOf<z.infer<typeof anotherTrue>>().toEqualTypeOf<`true`>();
expectTypeOf<z.infer<typeof falsee>>().toEqualTypeOf<`false`>();
expectTypeOf<z.infer<typeof anotherFalse>>().toEqualTypeOf<`false`>();
expectTypeOf<z.infer<typeof nulll>>().toEqualTypeOf<`null`>();
expectTypeOf<z.infer<typeof anotherNull>>().toEqualTypeOf<`null`>();
expectTypeOf<z.infer<typeof undefinedd>>().toEqualTypeOf<``>();
expectTypeOf<z.infer<typeof anotherUndefined>>().toEqualTypeOf<``>();
expectTypeOf<z.infer<typeof anyString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof lazyString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof anyNumber>>().toEqualTypeOf<`${number}`>();
expectTypeOf<z.infer<typeof anyInt>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof anyFiniteNumber>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof anyNegativeNumber>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof anyPositiveNumber>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof zeroButInADumbWay>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof finiteButInADumbWay>>().toEqualTypeOf<`${number}`>();
expectTypeOf<z.infer<typeof bool>>().toEqualTypeOf<`true` | `false`>();
expectTypeOf<z.infer<typeof bigone>>().toEqualTypeOf<`${bigint}`>();
expectTypeOf<z.infer<typeof anyBigint>>().toEqualTypeOf<`${bigint}`>();
expectTypeOf<z.infer<typeof nullableYo>>().toEqualTypeOf<`yo` | `null`>();
expectTypeOf<z.infer<typeof nullableString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof optionalYeah>>().toEqualTypeOf<`yeah` | ``>();
expectTypeOf<z.infer<typeof optionalString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof optionalNumber>>().toEqualTypeOf<`${number}` | ``>();
expectTypeOf<z.infer<typeof nullishBruh>>().toEqualTypeOf<`bruh` | `null` | ``>();
expectTypeOf<z.infer<typeof nullishString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof cuid>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof cuidZZZ>>().toEqualTypeOf<`${string}ZZZ`>();
expectTypeOf<z.infer<typeof cuid2>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof datetime>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof email>>().toEqualTypeOf<string>();
// expectTypeOf<z.infer<typeof ip>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof ipv4>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof ipv6>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof ulid>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof uuid>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringAToZ>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringStartsWith>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringEndsWith>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringMax5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringMin5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringLen5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringMin5Max10>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringStartsWithMax5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof brandedString>>().toEqualTypeOf<`${string & z.core.$brand<"myBrand">}`>();
// expectTypeOf<z.infer<typeof anything>>().toEqualTypeOf<`${any}`>();
expectTypeOf<z.infer<typeof url>>().toEqualTypeOf<`https://${string}.com` | `https://${string}.net`>();
expectTypeOf<z.infer<typeof measurement>>().toEqualTypeOf<
| `${number}`
| `${number}px`
| `${number}em`
| `${number}rem`
| `${number}vh`
| `${number}vw`
| `${number}vmin`
| `${number}vmax`
>();
expectTypeOf<z.infer<typeof connectionString>>().toEqualTypeOf<
| `mongodb://${string}:${number}`
| `mongodb://${string}:${number}/${string}`
| `mongodb://${string}:${number}/${string}?${string}`
| `mongodb://${string}:${string}@${string}:${number}`
| `mongodb://${string}:${string}@${string}:${number}/${string}`
| `mongodb://${string}:${string}@${string}:${number}/${string}?${string}`
>();
});
test("template literal unsupported args", () => {
expect(() =>
// @ts-expect-error
z.templateLiteral([z.object({})])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.array(z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.union([z.object({}), z.string()])])
).toThrow();
// @ts-expect-error
expect(() => z.templateLiteral([z.date()])).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.custom<object>((_) => true)])
).toThrow();
expect(() =>
z.templateLiteral([
// @ts-expect-error
z.discriminatedUnion("discriminator", [z.object({}), z.object({})]),
])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.function()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.instanceof(class MyClass {})])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.intersection(z.object({}), z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.map(z.string(), z.string())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.nullable(z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.optional(z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.promise()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.record(z.unknown())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.set(z.string())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.symbol()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.tuple([z.string()])])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.unknown()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.void()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.never()])
).toThrow();
// @ts-expect-error
expect(() => z.templateLiteral([z.nan()])).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.pipe(z.string(), z.string())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.preprocess(() => true, z.boolean())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.object({}).brand("brand")])
).toThrow();
// these constraints aren't enforced but they shouldn't throw
z.templateLiteral([z.number().multipleOf(2)]);
z.templateLiteral([z.string().emoji()]);
z.templateLiteral([z.string().url()]);
z.templateLiteral([z.string().url()]);
z.templateLiteral([z.string().trim()]);
z.templateLiteral([z.string().includes("train")]);
z.templateLiteral([z.string().toLowerCase()]);
z.templateLiteral([z.string().toUpperCase()]);
});
test("template literal parsing - success - basic cases", () => {
expect(() => z.templateLiteral([]).parse(7)).toThrow();
empty.parse("");
hello.parse("hello");
world.parse("world");
one.parse("1");
two.parse("2");
truee.parse("true");
anotherTrue.parse("true");
falsee.parse("false");
anotherFalse.parse("false");
nulll.parse("null");
anotherNull.parse("null");
undefinedd.parse("undefined");
anotherUndefined.parse("undefined");
anyString.parse("blahblahblah");
anyString.parse("");
lazyString.parse("blahblahblah");
lazyString.parse("");
anyNumber.parse("123");
anyNumber.parse("1.23");
anyNumber.parse("0");
anyNumber.parse("-1.23");
anyNumber.parse("-123");
// anyNumber.parse("Infinity");
// anyNumber.parse("-Infinity");
anyInt.parse("123");
// anyInt.parse("-123");
// anyFiniteNumber.parse("123");
// anyFiniteNumber.parse("1.23");
// anyFiniteNumber.parse("0");
// anyFiniteNumber.parse("-1.23");
// anyFiniteNumber.parse("-123");
// anyNegativeNumber.parse("-123");
// anyNegativeNumber.parse("-1.23");
// anyNegativeNumber.parse("-Infinity");
// anyPositiveNumber.parse("123");
// anyPositiveNumber.parse("1.23");
// anyPositiveNumber.parse("Infinity");
// zeroButInADumbWay.parse("0");
// zeroButInADumbWay.parse("00000");
// finiteButInADumbWay.parse("5");
// finiteButInADumbWay.parse("10");
// finiteButInADumbWay.parse("6.66");
bool.parse("true");
bool.parse("false");
bigone.parse("1");
anyBigint.parse("123456");
anyBigint.parse("0");
// anyBigint.parse("-123456");
nullableYo.parse("yo");
nullableYo.parse("null");
nullableString.parse("abc");
nullableString.parse("null");
optionalYeah.parse("yeah");
optionalYeah.parse("");
optionalString.parse("abc");
optionalString.parse("");
optionalNumber.parse("123");
optionalNumber.parse("1.23");
optionalNumber.parse("0");
optionalNumber.parse("-1.23");
optionalNumber.parse("-123");
// optionalNumber.parse("Infinity");
// optionalNumber.parse("-Infinity");
nullishBruh.parse("bruh");
nullishBruh.parse("null");
nullishBruh.parse("");
cuid.parse("cjld2cyuq0000t3rmniod1foy");
cuidZZZ.parse("cjld2cyuq0000t3rmniod1foyZZZ");
cuid2.parse("tz4a98xxat96iws9zmbrgj3a");
datetime.parse(new Date().toISOString());
email.parse("info@example.com");
// ip.parse("213.174.246.205");
// ip.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
ipv4.parse("213.174.246.205");
ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW");
uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6");
stringAToZ.parse("asudgaskhdgashd");
stringStartsWith.parse("hello world");
stringEndsWith.parse("hello world");
stringMax5.parse("hello");
stringMin5.parse("hello");
stringLen5.parse("hello");
stringMin5Max10.parse("hello worl");
stringStartsWithMax5.parse("hello");
brandedString.parse("branded string");
});
test("template literal parsing - failure - basic cases", () => {
expect(() => empty.parse("a")).toThrow();
expect(() => hello.parse("hello!")).toThrow();
expect(() => hello.parse("!hello")).toThrow();
expect(() => world.parse("world!")).toThrow();
expect(() => world.parse("!world")).toThrow();
expect(() => one.parse("2")).toThrow();
expect(() => one.parse("12")).toThrow();
expect(() => one.parse("21")).toThrow();
expect(() => two.parse("1")).toThrow();
expect(() => two.parse("21")).toThrow();
expect(() => two.parse("12")).toThrow();
expect(() => truee.parse("false")).toThrow();
expect(() => truee.parse("1true")).toThrow();
expect(() => truee.parse("true1")).toThrow();
expect(() => anotherTrue.parse("false")).toThrow();
expect(() => anotherTrue.parse("1true")).toThrow();
expect(() => anotherTrue.parse("true1")).toThrow();
expect(() => falsee.parse("true")).toThrow();
expect(() => falsee.parse("1false")).toThrow();
expect(() => falsee.parse("false1")).toThrow();
expect(() => anotherFalse.parse("true")).toThrow();
expect(() => anotherFalse.parse("1false")).toThrow();
expect(() => anotherFalse.parse("false1")).toThrow();
expect(() => nulll.parse("123")).toThrow();
expect(() => nulll.parse("null1")).toThrow();
expect(() => nulll.parse("1null")).toThrow();
expect(() => anotherNull.parse("123")).toThrow();
expect(() => anotherNull.parse("null1")).toThrow();
expect(() => anotherNull.parse("1null")).toThrow();
expect(() => undefinedd.parse("123")).toThrow();
expect(() => undefinedd.parse("undefined1")).toThrow();
expect(() => undefinedd.parse("1undefined")).toThrow();
expect(() => anotherUndefined.parse("123")).toThrow();
expect(() => anotherUndefined.parse("undefined1")).toThrow();
expect(() => anotherUndefined.parse("1undefined")).toThrow();
expect(() => anyNumber.parse("2a")).toThrow();
expect(() => anyNumber.parse("a2")).toThrow();
expect(() => anyNumber.parse("-2a")).toThrow();
expect(() => anyNumber.parse("a-2")).toThrow();
expect(() => anyNumber.parse("2.5a")).toThrow();
expect(() => anyNumber.parse("a2.5")).toThrow();
expect(() => anyNumber.parse("Infinitya")).toThrow();
expect(() => anyNumber.parse("aInfinity")).toThrow();
expect(() => anyNumber.parse("-Infinitya")).toThrow();
expect(() => anyNumber.parse("a-Infinity")).toThrow();
expect(() => anyNumber.parse("2e5")).toThrow();
expect(() => anyNumber.parse("2e-5")).toThrow();
expect(() => anyNumber.parse("2e+5")).toThrow();
expect(() => anyNumber.parse("-2e5")).toThrow();
expect(() => anyNumber.parse("-2e-5")).toThrow();
expect(() => anyNumber.parse("-2e+5")).toThrow();
expect(() => anyNumber.parse("2.1e5")).toThrow();
expect(() => anyNumber.parse("2.1e-5")).toThrow();
expect(() => anyNumber.parse("2.1e+5")).toThrow();
expect(() => anyNumber.parse("-2.1e5")).toThrow();
expect(() => anyNumber.parse("-2.1e-5")).toThrow();
expect(() => anyNumber.parse("-2.1e+5")).toThrow();
expect(() => anyNumber.parse("-Infinity")).toThrow();
expect(() => anyNumber.parse("Infinity")).toThrow();
expect(() => anyInt.parse("1.23")).toThrow();
expect(() => anyInt.parse("-1.23")).toThrow();
expect(() => anyInt.parse("d1")).toThrow();
expect(() => anyInt.parse("1d")).toThrow();
// expect(() => anyFiniteNumber.parse("Infinity")).toThrow();
// expect(() => anyFiniteNumber.parse("-Infinity")).toThrow();
// expect(() => anyFiniteNumber.parse("123a")).toThrow();
// expect(() => anyFiniteNumber.parse("a123")).toThrow();
// expect(() => anyNegativeNumber.parse("0")).toThrow();
// expect(() => anyNegativeNumber.parse("1")).toThrow();
// expect(() => anyNegativeNumber.parse("Infinity")).toThrow();
// expect(() => anyPositiveNumber.parse("0")).toThrow();
// expect(() => anyPositiveNumber.parse("-1")).toThrow();
// expect(() => anyPositiveNumber.parse("-Infinity")).toThrow();
// expect(() => zeroButInADumbWay.parse("1")).toThrow();
// expect(() => zeroButInADumbWay.parse("-1")).toThrow();
// expect(() => finiteButInADumbWay.parse("Infinity")).toThrow();
// expect(() => finiteButInADumbWay.parse("-Infinity")).toThrow();
// expect(() => finiteButInADumbWay.parse("-5")).toThrow();
// expect(() => finiteButInADumbWay.parse("10a")).toThrow();
// expect(() => finiteButInADumbWay.parse("a10")).toThrow();
expect(() => bool.parse("123")).toThrow();
expect(() => bigone.parse("2")).toThrow();
expect(() => bigone.parse("c1")).toThrow();
expect(() => anyBigint.parse("1.23")).toThrow();
expect(() => anyBigint.parse("-1.23")).toThrow();
expect(() => anyBigint.parse("c123")).toThrow();
expect(() => nullableYo.parse("yo1")).toThrow();
expect(() => nullableYo.parse("1yo")).toThrow();
expect(() => nullableYo.parse("null1")).toThrow();
expect(() => nullableYo.parse("1null")).toThrow();
expect(() => optionalYeah.parse("yeah1")).toThrow();
expect(() => optionalYeah.parse("1yeah")).toThrow();
expect(() => optionalYeah.parse("undefined")).toThrow();
expect(() => optionalNumber.parse("123a")).toThrow();
expect(() => optionalNumber.parse("a123")).toThrow();
// expect(() => optionalNumber.parse("Infinitya")).toThrow();
// expect(() => optionalNumber.parse("aInfinity")).toThrow();
expect(() => nullishBruh.parse("bruh1")).toThrow();
expect(() => nullishBruh.parse("1bruh")).toThrow();
expect(() => nullishBruh.parse("null1")).toThrow();
expect(() => nullishBruh.parse("1null")).toThrow();
expect(() => nullishBruh.parse("undefined")).toThrow();
expect(() => cuid.parse("bjld2cyuq0000t3rmniod1foy")).toThrow();
expect(() => cuid.parse("cjld2cyu")).toThrow();
expect(() => cuid.parse("cjld2 cyu")).toThrow();
expect(() => cuid.parse("cjld2cyuq0000t3rmniod1foy ")).toThrow();
expect(() => cuid.parse("1cjld2cyuq0000t3rmniod1foy")).toThrow();
expect(() => cuidZZZ.parse("cjld2cyuq0000t3rmniod1foy")).toThrow();
expect(() => cuidZZZ.parse("cjld2cyuq0000t3rmniod1foyZZY")).toThrow();
expect(() => cuidZZZ.parse("cjld2cyuq0000t3rmniod1foyZZZ1")).toThrow();
expect(() => cuidZZZ.parse("1cjld2cyuq0000t3rmniod1foyZZZ")).toThrow();
expect(() => cuid2.parse("A9z4a98xxat96iws9zmbrgj3a")).toThrow();
expect(() => cuid2.parse("tz4a98xxat96iws9zmbrgj3!")).toThrow();
expect(() => datetime.parse("2022-01-01 00:00:00")).toThrow();
expect(() => email.parse("info@example.com@")).toThrow();
// expect(() => ip.parse("213.174.246:205")).toThrow();
// expect(() => ip.parse("c359.f57c:21e5:39eb:1187:e501:f936:b452")).toThrow();
expect(() => ipv4.parse("1213.174.246.205")).toThrow();
expect(() => ipv4.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452")).toThrow();
expect(() => ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b4521")).toThrow();
expect(() => ipv6.parse("213.174.246.205")).toThrow();
expect(() => ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW!")).toThrow();
expect(() => uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6Z")).toThrow();
expect(() => uuid.parse("Z808989fd-3a6e-4af2-b607-737323a176f6")).toThrow();
expect(() => stringAToZ.parse("asdasdasd1")).toThrow();
expect(() => stringAToZ.parse("1asdasdasd")).toThrow();
expect(() => stringStartsWith.parse("ahello")).toThrow();
expect(() => stringEndsWith.parse("worlda")).toThrow();
expect(() => stringMax5.parse("123456")).toThrow();
expect(() => stringMin5.parse("1234")).toThrow();
expect(() => stringLen5.parse("123456")).toThrow();
expect(() => stringLen5.parse("1234")).toThrow();
expect(() => stringMin5Max10.parse("1234")).toThrow();
expect(() => stringMin5Max10.parse("12345678901")).toThrow();
// the "startswith" overrides the max length
// expect(() => stringStartsWithMax5.parse("hello1")).toThrow();
expect(() => stringStartsWithMax5.parse("1hell")).toThrow();
expect(() => brandedString.parse("")).toThrow();
});
test("regexes", () => {
expect(empty._zod.pattern.source).toMatchInlineSnapshot(`"^$"`);
expect(hello._zod.pattern.source).toMatchInlineSnapshot(`"^hello$"`);
expect(world._zod.pattern.source).toMatchInlineSnapshot(`"^(world)$"`);
expect(one._zod.pattern.source).toMatchInlineSnapshot(`"^1$"`);
expect(two._zod.pattern.source).toMatchInlineSnapshot(`"^(2)$"`);
expect(truee._zod.pattern.source).toMatchInlineSnapshot(`"^true$"`);
expect(anotherTrue._zod.pattern.source).toMatchInlineSnapshot(`"^(true)$"`);
expect(falsee._zod.pattern.source).toMatchInlineSnapshot(`"^false$"`);
expect(anotherFalse._zod.pattern.source).toMatchInlineSnapshot(`"^(false)$"`);
expect(nulll._zod.pattern.source).toMatchInlineSnapshot(`"^null$"`);
expect(anotherNull._zod.pattern.source).toMatchInlineSnapshot(`"^null$"`);
expect(undefinedd._zod.pattern.source).toMatchInlineSnapshot(`"^undefined$"`);
expect(anotherUndefined._zod.pattern.source).toMatchInlineSnapshot(`"^undefined$"`);
expect(anyString._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{0,}$"`);
expect(lazyString._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{0,}$"`);
expect(anyNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
expect(anyInt._zod.pattern.source).toMatchInlineSnapshot(`"^\\d+$"`);
// expect(anyFiniteNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(anyNegativeNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(anyPositiveNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(zeroButInADumbWay._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(finiteButInADumbWay._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
expect(bool._zod.pattern.source).toMatchInlineSnapshot(`"^true|false$"`);
expect(bigone._zod.pattern.source).toMatchInlineSnapshot(`"^(1)$"`);
expect(anyBigint._zod.pattern.source).toMatchInlineSnapshot(`"^\\d+n?$"`);
expect(nullableYo._zod.pattern.source).toMatchInlineSnapshot(`"^((yo)|null)$"`);
expect(nullableString._zod.pattern.source).toMatchInlineSnapshot(`"^([\\s\\S]{0,}|null)$"`);
expect(optionalYeah._zod.pattern.source).toMatchInlineSnapshot(`"^((yeah))?$"`);
expect(optionalString._zod.pattern.source).toMatchInlineSnapshot(`"^([\\s\\S]{0,})?$"`);
expect(optionalNumber._zod.pattern.source).toMatchInlineSnapshot(`"^(-?\\d+(?:\\.\\d+)?)?$"`);
expect(nullishBruh._zod.pattern.source).toMatchInlineSnapshot(`"^(((bruh)|null))?$"`);
expect(nullishString._zod.pattern.source).toMatchInlineSnapshot(`"^(([\\s\\S]{0,}|null))?$"`);
expect(cuid._zod.pattern.source).toMatchInlineSnapshot(`"^[cC][^\\s-]{8,}$"`);
expect(cuidZZZ._zod.pattern.source).toMatchInlineSnapshot(`"^[cC][^\\s-]{8,}ZZZ$"`);
expect(cuid2._zod.pattern.source).toMatchInlineSnapshot(`"^[0-9a-z]+$"`);
expect(datetime._zod.pattern.source).toMatchInlineSnapshot(
`"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"`
);
expect(email._zod.pattern.source).toMatchInlineSnapshot(
`"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"`
);
// expect(ip._zod.pattern.source).toMatchInlineSnapshot(
// `"^(^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$)|(^(([a-fA-F0-9]{1,4}:){7}|::([a-fA-F0-9]{1,4}:){0,6}|([a-fA-F0-9]{1,4}:){1}:([a-fA-F0-9]{1,4}:){0,5}|([a-fA-F0-9]{1,4}:){2}:([a-fA-F0-9]{1,4}:){0,4}|([a-fA-F0-9]{1,4}:){3}:([a-fA-F0-9]{1,4}:){0,3}|([a-fA-F0-9]{1,4}:){4}:([a-fA-F0-9]{1,4}:){0,2}|([a-fA-F0-9]{1,4}:){5}:([a-fA-F0-9]{1,4}:){0,1})([a-fA-F0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$)$"`
// );
expect(ipv4._zod.pattern.source).toMatchInlineSnapshot(
`"^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$"`
);
expect(ipv6._zod.pattern.source).toMatchInlineSnapshot(
`"^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})$"`
);
expect(ulid._zod.pattern.source).toMatchInlineSnapshot(`"^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"`);
expect(uuid._zod.pattern.source).toMatchInlineSnapshot(
`"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$"`
);
expect(stringAToZ._zod.pattern.source).toMatchInlineSnapshot(`"^[a-z]+$"`);
expect(stringStartsWith._zod.pattern.source).toMatchInlineSnapshot(`"^hello.*$"`);
expect(stringEndsWith._zod.pattern.source).toMatchInlineSnapshot(`"^.*world$"`);
expect(stringMax5._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{0,5}$"`);
expect(stringMin5._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{5,}$"`);
expect(stringLen5._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{5,5}$"`);
expect(stringMin5Max10._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{5,10}$"`);
expect(brandedString._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{1,}$"`);
expect(url._zod.pattern.source).toMatchInlineSnapshot(`"^https:\\/\\/\\w+\\.(com|net)$"`);
expect(measurement._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?((px|em|rem|vh|vw|vmin|vmax))?$"`);
expect(connectionString._zod.pattern.source).toMatchInlineSnapshot(
`"^mongodb:\\/\\/(\\w+:\\w+@)?\\w+:\\d+(\\/(\\w+)?(\\?(\\w+=\\w+(&\\w+=\\w+)*)?)?)?$"`
);
});
test("template literal parsing - success - complex cases", () => {
url.parse("https://example.com");
url.parse("https://speedtest.net");
// measurement.parse(1);
// measurement.parse(1.1);
// measurement.parse(0);
// measurement.parse(-1.1);
// measurement.parse(-1);
measurement.parse("1");
measurement.parse("1.1");
measurement.parse("0");
measurement.parse("-1");
measurement.parse("-1.1");
measurement.parse("1px");
measurement.parse("1.1px");
measurement.parse("0px");
measurement.parse("-1px");
measurement.parse("-1.1px");
measurement.parse("1em");
measurement.parse("1.1em");
measurement.parse("0em");
measurement.parse("-1em");
measurement.parse("-1.1em");
measurement.parse("1rem");
measurement.parse("1.1rem");
measurement.parse("0rem");
measurement.parse("-1rem");
measurement.parse("-1.1rem");
measurement.parse("1vh");
measurement.parse("1.1vh");
measurement.parse("0vh");
measurement.parse("-1vh");
measurement.parse("-1.1vh");
measurement.parse("1vw");
measurement.parse("1.1vw");
measurement.parse("0vw");
measurement.parse("-1vw");
measurement.parse("-1.1vw");
measurement.parse("1vmin");
measurement.parse("1.1vmin");
measurement.parse("0vmin");
measurement.parse("-1vmin");
measurement.parse("-1.1vmin");
measurement.parse("1vmax");
measurement.parse("1.1vmax");
measurement.parse("0vmax");
measurement.parse("-1vmax");
measurement.parse("-1.1vmax");
connectionString.parse("mongodb://host:1234");
connectionString.parse("mongodb://host:1234/");
connectionString.parse("mongodb://host:1234/defaultauthdb");
connectionString.parse("mongodb://host:1234/defaultauthdb?authSource=admin");
connectionString.parse("mongodb://host:1234/defaultauthdb?authSource=admin&connectTimeoutMS=300000");
connectionString.parse("mongodb://host:1234/?authSource=admin");
connectionString.parse("mongodb://host:1234/?authSource=admin&connectTimeoutMS=300000");
connectionString.parse("mongodb://username:password@host:1234");
connectionString.parse("mongodb://username:password@host:1234/");
connectionString.parse("mongodb://username:password@host:1234/defaultauthdb");
connectionString.parse("mongodb://username:password@host:1234/defaultauthdb?authSource=admin");
connectionString.parse(
"mongodb://username:password@host:1234/defaultauthdb?authSource=admin&connectTimeoutMS=300000"
);
connectionString.parse("mongodb://username:password@host:1234/?authSource=admin");
connectionString.parse("mongodb://username:password@host:1234/?authSource=admin&connectTimeoutMS=300000");
});
test("template literal parsing - failure - complex cases", () => {
expect(() => url.parse("http://example.com")).toThrow();
expect(() => url.parse("https://.com")).toThrow();
expect(() => url.parse("https://examplecom")).toThrow();
expect(() => url.parse("https://example.org")).toThrow();
expect(() => url.parse("https://example.net.il")).toThrow();
expect(() => measurement.parse("1.1.1")).toThrow();
expect(() => measurement.parse("Infinity")).toThrow();
expect(() => measurement.parse("-Infinity")).toThrow();
expect(() => measurement.parse("NaN")).toThrow();
expect(() => measurement.parse("1%")).toThrow();
expect(() => connectionString.parse("mongod://host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://:1234")).toThrow();
expect(() => connectionString.parse("mongodb://host1234")).toThrow();
expect(() => connectionString.parse("mongodb://host:d234")).toThrow();
expect(() => connectionString.parse("mongodb://host:12.34")).toThrow();
expect(() => connectionString.parse("mongodb://host:-1234")).toThrow();
expect(() => connectionString.parse("mongodb://host:-12.34")).toThrow();
expect(() => connectionString.parse("mongodb://host:")).toThrow();
expect(() => connectionString.parse("mongodb://:password@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://usernamepassword@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://username:@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/defaultauthdb?authSourceadmin")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/?authSourceadmin")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/defaultauthdb?&authSource=admin")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/?&authSource=admin")).toThrow();
});
test("template literal parsing - failure - issue format", () => {
expect(anotherNull.safeParse("1null")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^null$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(cuidZZZ.safeParse("1cjld2cyuq0000t3rmniod1foyZZZ")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^[cC][^\\\\s-]{8,}ZZZ$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(stringMin5Max10.safeParse("1234")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^[\\\\s\\\\S]{5,10}$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(connectionString.safeParse("mongodb://host:1234/defaultauthdb?authSourceadmin")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^mongodb:\\\\/\\\\/(\\\\w+:\\\\w+@)?\\\\w+:\\\\d+(\\\\/(\\\\w+)?(\\\\?(\\\\w+=\\\\w+(&\\\\w+=\\\\w+)*)?)?)?$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(stringStartsWithMax5.safeParse("1hell")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^hello.*$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,250 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("transform ctx.addIssue with parse", () => {
const strs = ["foo", "bar"];
const schema = z.string().transform((data, ctx) => {
const i = strs.indexOf(data);
if (i === -1) {
ctx.addIssue({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
}
return data.length;
});
const result = schema.safeParse("asdf");
expect(result.success).toEqual(false);
expect(result.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]]
`);
});
test("transform ctx.addIssue with parseAsync", async () => {
const strs = ["foo", "bar"];
const result = await z
.string()
.transform(async (data, ctx) => {
const i = strs.indexOf(data);
if (i === -1) {
ctx.addIssue({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
}
return data.length;
})
.safeParseAsync("asdf");
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]],
"success": false,
}
`);
});
test("z.NEVER in transform", () => {
const foo = z
.number()
.optional()
.transform((val, ctx) => {
if (!val) {
ctx.addIssue({
input: val,
code: z.ZodIssueCode.custom,
message: "bad",
});
return z.NEVER;
}
return val;
});
type foo = z.infer<typeof foo>;
expectTypeOf<foo>().toEqualTypeOf<number>();
const arg = foo.safeParse(undefined);
if (!arg.success) {
expect(arg.error.issues[0].message).toEqual("bad");
}
});
test("basic transformations", () => {
const r1 = z
.string()
.transform((data) => data.length)
.parse("asdf");
expect(r1).toEqual(4);
});
test("coercion", () => {
const numToString = z.number().transform((n) => String(n));
const data = z
.object({
id: numToString,
})
.parse({ id: 5 });
expect(data).toEqual({ id: "5" });
});
test("async coercion", async () => {
const numToString = z.number().transform(async (n) => String(n));
const data = await z
.object({
id: numToString,
})
.parseAsync({ id: 5 });
expect(data).toEqual({ id: "5" });
});
test("sync coercion async error", async () => {
const asyncNumberToString = z.number().transform(async (n) => String(n));
expect(() =>
z
.object({
id: asyncNumberToString,
})
.parse({ id: 5 })
).toThrow();
// expect(data).toEqual({ id: '5' });
});
test("default", () => {
const data = z.string().default("asdf").parse(undefined); // => "asdf"
expect(data).toEqual("asdf");
});
test("dynamic default", () => {
const data = z
.string()
.default(() => "string")
.parse(undefined); // => "asdf"
expect(data).toEqual("string");
});
test("default when property is null or undefined", () => {
const data = z
.object({
foo: z.boolean().nullable().default(true),
bar: z.boolean().default(true),
})
.parse({ foo: null });
expect(data).toEqual({ foo: null, bar: true });
});
test("default with falsy values", () => {
const schema = z.object({
emptyStr: z.string().default("def"),
zero: z.number().default(5),
falseBoolean: z.boolean().default(true),
});
const input = { emptyStr: "", zero: 0, falseBoolean: true };
const output = schema.parse(input);
// defaults are not supposed to be used
expect(output).toEqual(input);
});
test("object typing", () => {
const stringToNumber = z.string().transform((arg) => Number.parseFloat(arg));
const t1 = z.object({
stringToNumber,
});
type t1 = z.input<typeof t1>;
type t2 = z.output<typeof t1>;
expectTypeOf<t1>().toEqualTypeOf<{ stringToNumber: string }>();
expectTypeOf<t2>().toEqualTypeOf<{ stringToNumber: number }>();
});
test("transform method overloads", () => {
const t1 = z.string().transform((val) => val.toUpperCase());
expect(t1.parse("asdf")).toEqual("ASDF");
const t2 = z.string().transform((val) => val.length);
expect(t2.parse("asdf")).toEqual(4);
});
test("multiple transformers", () => {
const stringToNumber = z.string().transform((arg) => Number.parseFloat(arg));
const doubler = stringToNumber.transform((val) => {
return val * 2;
});
expect(doubler.parse("5")).toEqual(10);
});
test("short circuit on dirty", () => {
const schema = z
.string()
.refine(() => false)
.transform((val) => val.toUpperCase());
const result = schema.safeParse("asdf");
expect(result.success).toEqual(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
const result2 = schema.safeParse(1234);
expect(result2.success).toEqual(false);
if (!result2.success) {
expect(result2.error.issues[0].code).toEqual(z.ZodIssueCode.invalid_type);
}
});
test("async short circuit on dirty", async () => {
const schema = z
.string()
.refine(() => false)
.transform((val) => val.toUpperCase());
const result = await schema.spa("asdf");
expect(result.success).toEqual(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
const result2 = await schema.spa(1234);
expect(result2.success).toEqual(false);
expect(result2.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]]
`);
});

View File

@ -0,0 +1,163 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("successful validation", () => {
const testTuple = z.tuple([z.string(), z.number()]);
expectTypeOf<typeof testTuple._output>().toEqualTypeOf<[string, number]>();
const val = testTuple.parse(["asdf", 1234]);
expect(val).toEqual(val);
const r1 = testTuple.safeParse(["asdf", "asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
1
],
"message": "Invalid input: expected number, received string"
}
]]
`);
const r2 = testTuple.safeParse(["asdf", 1234, true]);
expect(r2.success).toEqual(false);
expect(r2.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "array",
"code": "too_big",
"maximum": 2,
"path": [],
"message": "Too big: expected array to have <2 items"
}
]]
`);
const r3 = testTuple.safeParse({});
expect(r3.success).toEqual(false);
expect(r3.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "tuple",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected tuple, received object"
}
]]
`);
});
test("async validation", async () => {
const testTuple = z
.tuple([z.string().refine(async () => true), z.number().refine(async () => true)])
.refine(async () => true);
expectTypeOf<typeof testTuple._output>().toEqualTypeOf<[string, number]>();
const val = await testTuple.parseAsync(["asdf", 1234]);
expect(val).toEqual(val);
const r1 = await testTuple.safeParseAsync(["asdf", "asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
1
],
"message": "Invalid input: expected number, received string"
}
]]
`);
const r2 = await testTuple.safeParseAsync(["asdf", 1234, true]);
expect(r2.success).toEqual(false);
expect(r2.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "array",
"code": "too_big",
"maximum": 2,
"path": [],
"message": "Too big: expected array to have <2 items"
}
]]
`);
const r3 = await testTuple.safeParseAsync({});
expect(r3.success).toEqual(false);
expect(r3.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "tuple",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected tuple, received object"
}
]]
`);
});
test("tuple with optional elements", () => {
const myTuple = z.tuple([z.string(), z.number().optional(), z.string().optional()]).rest(z.boolean());
expectTypeOf<typeof myTuple._output>().toEqualTypeOf<[string, number?, string?, ...boolean[]]>();
const goodData = [["asdf"], ["asdf", 1234], ["asdf", 1234, "asdf"], ["asdf", 1234, "asdf", true, false, true]];
for (const data of goodData) {
expect(myTuple.parse(data)).toEqual(data);
}
const badData = [
["asdf", "asdf"],
["asdf", 1234, "asdf", "asdf"],
["asdf", 1234, "asdf", true, false, "asdf"],
];
for (const data of badData) {
expect(() => myTuple.parse(data)).toThrow();
}
});
test("tuple with optional elements followed by required", () => {
const myTuple = z.tuple([z.string(), z.number().optional(), z.string()]).rest(z.boolean());
expectTypeOf<typeof myTuple._output>().toEqualTypeOf<[string, number | undefined, string, ...boolean[]]>();
const goodData = [
["asdf", 1234, "asdf"],
["asdf", 1234, "asdf", true, false, true],
];
for (const data of goodData) {
expect(myTuple.parse(data)).toEqual(data);
}
const badData = [
["asdf"],
["asdf", 1234],
["asdf", 1234, "asdf", "asdf"],
["asdf", 1234, "asdf", true, false, "asdf"],
];
for (const data of badData) {
expect(() => myTuple.parse(data)).toThrow();
}
});
test("tuple with rest schema", () => {
const myTuple = z.tuple([z.string(), z.number()]).rest(z.boolean());
expect(myTuple.parse(["asdf", 1234, true, false, true])).toEqual(["asdf", 1234, true, false, true]);
expect(myTuple.parse(["asdf", 1234])).toEqual(["asdf", 1234]);
expect(() => myTuple.parse(["asdf", 1234, "asdf"])).toThrow();
type t1 = z.output<typeof myTuple>;
expectTypeOf<t1>().toEqualTypeOf<[string, number, ...boolean[]]>();
});
test("sparse array input", () => {
const schema = z.tuple([z.string(), z.number()]);
expect(() => schema.parse(new Array(2))).toThrow();
});

View File

@ -0,0 +1,94 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("function parsing", () => {
const schema = z.union([z.string().refine(() => false), z.number().refine(() => false)]);
const result = schema.safeParse("asdf");
expect(result.success).toEqual(false);
});
test("union 2", () => {
const result = z.union([z.number(), z.string().refine(() => false)]).safeParse("a");
expect(result.success).toEqual(false);
});
test("return valid over invalid", () => {
const schema = z.union([
z.object({
email: z.string().email(),
}),
z.string(),
]);
expect(schema.parse("asdf")).toEqual("asdf");
expect(schema.parse({ email: "asdlkjf@lkajsdf.com" })).toEqual({
email: "asdlkjf@lkajsdf.com",
});
});
test("return errors from both union arms", () => {
const result = z.union([z.number(), z.string().refine(() => false)]).safeParse("a");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_union",
"errors": [
[
{
"code": "invalid_type",
"expected": "number",
"message": "Invalid input: expected number, received string",
"path": [],
},
],
[
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
],
],
"message": "Invalid input",
"path": [],
},
]
`);
}
});
test("options getter", async () => {
const union = z.union([z.string(), z.number()]);
union.options[0].parse("asdf");
union.options[1].parse(1234);
await union.options[0].parseAsync("asdf");
await union.options[1].parseAsync(1234);
});
test("readonly union", async () => {
const options = [z.string(), z.number()] as const;
const union = z.union(options);
union.parse("asdf");
union.parse(12);
});
test("union inferred types", () => {
const test = z.object({}).or(z.array(z.object({})));
type Test = z.output<typeof test>; // <— any
expectTypeOf<Test>().toEqualTypeOf<Record<string, never> | Array<Record<string, never>>>();
});
test("union values", () => {
const schema = z.union([z.literal("a"), z.literal("b"), z.literal("c")]);
expect(schema._zod.values).toMatchInlineSnapshot(`
Set {
"a",
"b",
"c",
}
`);
});

View File

@ -0,0 +1,283 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("string length", async () => {
try {
await z.string().length(4).parseAsync("asd");
} catch (err) {
// ("String must contain exactly 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"exact": true,
"inclusive": true,
"message": "Too small: expected string to have >=4 characters",
"minimum": 4,
"origin": "string",
"path": [],
},
]
`);
}
try {
await z.string().length(4).parseAsync("asdaa");
} catch (err) {
// ("String must contain exactly 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"exact": true,
"inclusive": true,
"maximum": 4,
"message": "Too big: expected string to have <=4 characters",
"origin": "string",
"path": [],
},
]
`);
}
});
test("string min/max", async () => {
try {
await z.string().min(4).parseAsync("asd");
} catch (err) {
// ("String must contain at least 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected string to have >=4 characters",
"minimum": 4,
"origin": "string",
"path": [],
},
]
`);
}
});
test("string max", async () => {
try {
await z.string().max(4).parseAsync("aasdfsdfsd");
} catch (err) {
// ("String must contain at most 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 4,
"message": "Too big: expected string to have <=4 characters",
"origin": "string",
"path": [],
},
]
`);
}
});
test("number min", async () => {
try {
await z.number().min(3).parseAsync(2);
} catch (err) {
// ("Number must be greater than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected number to be >=3",
"minimum": 3,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number gte", async () => {
try {
await z.number().gte(3).parseAsync(2);
} catch (err) {
// ("Number must be greater than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected number to be >=3",
"minimum": 3,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number gt", async () => {
try {
await z.number().gt(3).parseAsync(3);
} catch (err) {
// ("Number must be greater than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": false,
"message": "Too small: expected number to be >3",
"minimum": 3,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number max", async () => {
try {
await z.number().max(3).parseAsync(4);
} catch (err) {
// ("Number must be less than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 3,
"message": "Too big: expected number to be <=3",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number lte", async () => {
try {
await z.number().lte(3).parseAsync(4);
} catch (err) {
// ("Number must be less than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 3,
"message": "Too big: expected number to be <=3",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number lt", async () => {
try {
await z.number().lt(3).parseAsync(3);
} catch (err) {
// ("Number must be less than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": false,
"maximum": 3,
"message": "Too big: expected number to be <3",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number nonnegative", async () => {
try {
await z.number().nonnegative().parseAsync(-1);
} catch (err) {
// ("Number must be greater than or equal to 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected number to be >=0",
"minimum": 0,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number nonpositive", async () => {
try {
await z.number().nonpositive().parseAsync(1);
} catch (err) {
// ("Number must be less than or equal to 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 0,
"message": "Too big: expected number to be <=0",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number negative", async () => {
try {
await z.number().negative().parseAsync(1);
} catch (err) {
// ("Number must be less than 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": false,
"maximum": 0,
"message": "Too big: expected number to be <0",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number positive", async () => {
try {
await z.number().positive().parseAsync(-1);
} catch (err) {
// ("Number must be greater than 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": false,
"message": "Too small: expected number to be >0",
"minimum": 0,
"origin": "number",
"path": [],
},
]
`);
}
});

View File

@ -0,0 +1,12 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("void", () => {
const v = z.void();
v.parse(undefined);
expect(() => v.parse(null)).toThrow();
expect(() => v.parse("")).toThrow();
type v = z.infer<typeof v>;
expectTypeOf<v>().toEqualTypeOf<void>();
});