diff --git a/package-lock.json b/package-lock.json index 0a604d0..dbb07a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "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", + "fast-check": ">2.23.0 <4.0.0", "zod": "^3.18.0" } }, @@ -6209,9 +6209,9 @@ } }, "node_modules/zod": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.5.tgz", - "integrity": "sha512-BTAAliwfoB9dWf2hC+TXlyWKk/YTqRGZjHQR0WLC2A2pzierWo7KuQ1ebjS4SNaFaxg/lDItzl9/QTgLjcHbgw==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -11046,9 +11046,9 @@ "dev": true }, "zod": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.5.tgz", - "integrity": "sha512-BTAAliwfoB9dWf2hC+TXlyWKk/YTqRGZjHQR0WLC2A2pzierWo7KuQ1ebjS4SNaFaxg/lDItzl9/QTgLjcHbgw==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dev": true } } diff --git a/package.json b/package.json index 4d52628..6e70421 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/zod-fast-check.ts b/src/zod-fast-check.ts index 7394825..060b690 100644 --- a/src/zod-fast-check.ts +++ b/src/zod-fast-check.ts @@ -24,6 +24,7 @@ import { ZodPipeline, ZodPromise, ZodRawShape, + ZodReadonly, ZodRecord, ZodSchema, ZodSet, @@ -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; type SchemaToArbitrary = ( @@ -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; type ExtractFirstPartySchemaType = Extract; @@ -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": @@ -565,6 +574,13 @@ const arbitraryBuilders: ArbitraryBuilders = { ZodSymbol() { return fc.string().map((s) => Symbol(s)); }, + ZodReadonly( + schema: ZodReadonly, + path: string, + recurse: SchemaToArbitrary + ) { + return recurse(schema._def.innerType, path); + }, }; export class ZodFastCheckError extends Error {} diff --git a/tests/zod-fast-check.test.ts b/tests/zod-fast-check.test.ts index a455a5b..0a53c6b 100644 --- a/tests/zod-fast-check.test.ts +++ b/tests/zod-fast-check.test.ts @@ -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() }), @@ -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)) { @@ -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", () => {