Skip to content

Commit

Permalink
Upgrade to Zod 3.22, add support for readonly schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidTimms committed Oct 14, 2023
1 parent aba467e commit 8583132
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 14 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"prettier": "^2.2.1",
"ts-jest": "^26.4.4",
"typescript": "^4.7.4",
"zod": "^3.20.5"
"zod": "^3.22.4"
},
"peerDependencies": {
"fast-check": ">2.23.0 <4.0.0",
Expand Down
24 changes: 20 additions & 4 deletions src/zod-fast-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ZodPipeline,
ZodPromise,
ZodRawShape,
ZodReadonly,
ZodRecord,
ZodSchema,
ZodSet,
Expand All @@ -36,6 +37,9 @@ import {

const MIN_SUCCESS_RATE = 0.01;

const ZOD_EMAIL_REGEX =
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;

type UnknownZodSchema = ZodSchema<unknown, ZodTypeDef, unknown>;

type SchemaToArbitrary = <Schema extends UnknownZodSchema>(
Expand All @@ -55,9 +59,12 @@ type ArbitraryBuilders = {
>;
};

// ZodSymbol is missing from the union for first-party types, so we use this
// type instead which includes it.
type AllFirstPartySchemaTypes = ZodFirstPartySchemaTypes | ZodSymbol;
// ZodSymbol and ZodReadonly are missing from the union for first-party types,
// so we use this type instead which includes it.
type AllFirstPartySchemaTypes =
| ZodFirstPartySchemaTypes
| ZodSymbol
| ZodReadonly<UnknownZodSchema>;

type ExtractFirstPartySchemaType<TypeName extends ZodFirstPartyTypeKind> =
Extract<AllFirstPartySchemaTypes, { _def: { typeName: TypeName } }>;
Expand Down Expand Up @@ -251,7 +258,9 @@ const arbitraryBuilders: ArbitraryBuilders = {
case "uuid":
return fc.uuid();
case "email":
return fc.emailAddress();
return fc
.emailAddress()
.filter((email) => ZOD_EMAIL_REGEX.test(email));
case "url":
return fc.webUrl();
case "datetime":
Expand Down Expand Up @@ -565,6 +574,13 @@ const arbitraryBuilders: ArbitraryBuilders = {
ZodSymbol() {
return fc.string().map((s) => Symbol(s));
},
ZodReadonly(
schema: ZodReadonly<UnknownZodSchema>,
path: string,
recurse: SchemaToArbitrary
) {
return recurse(schema._def.innerType, path);
},
};

export class ZodFastCheckError extends Error {}
Expand Down
20 changes: 19 additions & 1 deletion tests/zod-fast-check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ describe("Generate arbitraries for Zod schema input types", () => {
"record of objects": z.record(z.object({ name: z.string() })),
"record of strings": z.record(z.string()),
"record of strings with min-length values": z.record(z.string().min(1)),
"record of strings with min-length keys": z.record(z.string().min(1), z.string()),
"record of strings with min-length keys": z.record(
z.string().min(1),
z.string()
),
"map with string keys": z.map(z.string(), z.number()),
"map with object keys": z.map(
z.object({ id: z.number() }),
Expand Down Expand Up @@ -156,6 +159,10 @@ describe("Generate arbitraries for Zod schema input types", () => {
"Coerced date": z.coerce.date(),
"string with catch": z.string().catch("fallback"),
symbol: z.symbol(),
"readonly array": z.array(z.unknown()).readonly(),
"readonly tuple": z.tuple([z.string(), z.number()]).readonly(),
"readonly map": z.map(z.string(), z.date()).readonly(),
"readonly set": z.set(z.string()).readonly(),
};

for (const [name, schema] of Object.entries(schemas)) {
Expand Down Expand Up @@ -307,6 +314,17 @@ describe("Generate arbitraries for Zod schema output types", () => {
})
);
});

test("readonly outputs cannot be modified", () => {
const schema = z.array(z.number()).readonly();
const arbitrary = ZodFastCheck().outputOf(schema);

return fc.assert(
fc.property(arbitrary, (value) => {
expect(() => (value as number[]).push(1)).toThrow();
})
);
});
});

describe("Override the arbitrary for a particular schema type", () => {
Expand Down

0 comments on commit 8583132

Please sign in to comment.