Skip to content

Commit

Permalink
test(typing) add support for testing types
Browse files Browse the repository at this point in the history
  • Loading branch information
oberbeck committed Nov 19, 2024
1 parent 207205c commit 18a97a4
Show file tree
Hide file tree
Showing 6 changed files with 767 additions and 216 deletions.
6 changes: 5 additions & 1 deletion configs/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ export default defineConfig({
alias: {
"@jest/globals": "vitest",
},
include: ["src/**/*.test.ts"],
include: ["src/**/*.test.ts", "src/**/*.test-d.ts"],
isolate: false,
watch: false,
typecheck: {
enabled: true,
include: ["src/**/*.test-d.ts"],
},
},
});
140 changes: 140 additions & 0 deletions deno/lib/__tests__/convert.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { assertType, describe, expectTypeOf, test } from "vitest";
import { SafeParseReturnType, z } from "../index.ts";

const stringToNumber = z.string().transform((arg) => parseFloat(arg));
const numberToString = z.number().transform((n) => String(n));

const asyncNumberToString = z.number().transform(async (n) => String(n));
const asyncStringToNumber = z.string().transform(async (n) => parseFloat(n));

describe("convert", () => {
test("input and output types to be enforced in", () => {
expectTypeOf(stringToNumber.convert).toBeFunction();
expectTypeOf(stringToNumber.convert).parameter(0).toMatchTypeOf<string>();
expectTypeOf(stringToNumber.convert).returns.toMatchTypeOf<number>();

expectTypeOf(numberToString.convert).toBeFunction();
expectTypeOf(numberToString.convert).parameter(0).toMatchTypeOf<number>();
expectTypeOf(numberToString.convert).returns.toMatchTypeOf<string>();
});

test("todo", () => {
const userSchema = z.object({
id: z.string(),
name: z.string(),
age: z.string().transform((age) => parseInt(age, 10)),
});

const validInput: z.input<typeof userSchema> = {
id: "123",
name: "Alice",
age: "25",
};

const user = userSchema.convert(validInput);
expectTypeOf(user).toMatchTypeOf<z.infer<typeof userSchema>>();

// Input not matching the schema's input type
const invalidInput = {
name: "Alice",
age: "25",
};

expectTypeOf(numberToString.convert)
.parameter(0)
.not.toMatchTypeOf<typeof invalidInput>();

try {
// @ts-expect-error - compile error
userSchema.convert(invalidInput);
} catch {}
});
});

describe("safeConvert", () => {
test("input and output types to be enforced in", () => {
expectTypeOf(stringToNumber.safeConvert).toBeFunction();
expectTypeOf(stringToNumber.safeConvert)
.parameter(0)
.toMatchTypeOf<string>();
expectTypeOf(stringToNumber.safeConvert).returns.toMatchTypeOf<
SafeParseReturnType<string, number>
>();

const resA = stringToNumber.safeConvert(""); // valid input but invlaid output
if (resA.success) {
assertType<number>(resA.data);
} else {
assertType<z.ZodError>(resA.error);
}

expectTypeOf(numberToString.safeConvert).toBeFunction();
expectTypeOf(numberToString.safeConvert)
.parameter(0)
.toMatchTypeOf<number>();
expectTypeOf(numberToString.safeConvert).returns.toMatchTypeOf<
SafeParseReturnType<number, string>
>();

const resB = numberToString.safeConvert(321);
if (resB.success) {
assertType<string>(resB.data);
} else {
assertType<z.ZodError>(resB.error);
}
});
});

describe("convertAsync", () => {
test("input and output types to be enforced in", () => {
expectTypeOf(asyncStringToNumber.convertAsync).toBeFunction();
expectTypeOf(asyncStringToNumber.convertAsync)
.parameter(0)
.toMatchTypeOf<string>();
expectTypeOf(asyncStringToNumber.convertAsync).returns.toMatchTypeOf<
Promise<number>
>();

expectTypeOf(asyncNumberToString.convertAsync).toBeFunction();
expectTypeOf(asyncNumberToString.convertAsync)
.parameter(0)
.toMatchTypeOf<number>();
expectTypeOf(asyncNumberToString.convertAsync).returns.toMatchTypeOf<
Promise<string>
>();
});
});

describe("safeConvertAsync", () => {
test("input and output types to be enforced in", async () => {
expectTypeOf(asyncStringToNumber.safeConvertAsync).toBeFunction();
expectTypeOf(asyncStringToNumber.safeConvertAsync)
.parameter(0)
.toMatchTypeOf<string>();
expectTypeOf(asyncStringToNumber.safeConvertAsync).returns.toMatchTypeOf<
Promise<SafeParseReturnType<string, number>>
>();

const resA = await asyncStringToNumber.safeConvertAsync(""); // valid input but invlaid output
if (resA.success) {
assertType<number>(resA.data);
} else {
assertType<z.ZodError>(resA.error);
}

expectTypeOf(asyncNumberToString.safeConvertAsync).toBeFunction();
expectTypeOf(asyncNumberToString.safeConvertAsync)
.parameter(0)
.toMatchTypeOf<number>();
expectTypeOf(asyncNumberToString.safeConvertAsync).returns.toMatchTypeOf<
Promise<SafeParseReturnType<number, string>>
>();

const resB = await asyncNumberToString.safeConvertAsync(321);
if (resB.success) {
assertType<string>(resB.data);
} else {
assertType<z.ZodError>(resB.error);
}
});
});
108 changes: 108 additions & 0 deletions deno/lib/__tests__/convert.test-d.ts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { assertType, describe, expectTypeOf, test } from "vitest";
import { SafeParseReturnType, z } from "../index.ts";

const stringToNumber = z.string().transform((arg) => parseFloat(arg));
const numberToString = z.number().transform((n) => String(n));

const asyncNumberToString = z.number().transform(async (n) => String(n));
const asyncStringToNumber = z.string().transform(async (n) => parseFloat(n));

describe("convert", () => {
test("input and output types to be enforced in", () => {
expectTypeOf(stringToNumber.convert).toBeFunction();
expectTypeOf(stringToNumber.convert).parameter(0).toMatchTypeOf<string>();
expectTypeOf(stringToNumber.convert).returns.toMatchTypeOf<number>();

expectTypeOf(numberToString.convert).toBeFunction();
expectTypeOf(numberToString.convert).parameter(0).toMatchTypeOf<number>();
expectTypeOf(numberToString.convert).returns.toMatchTypeOf<string>();
});
});

describe("safeConvert", () => {
test("input and output types to be enforced in", () => {
expectTypeOf(stringToNumber.safeConvert).toBeFunction();
expectTypeOf(stringToNumber.safeConvert)
.parameter(0)
.toMatchTypeOf<string>();
expectTypeOf(stringToNumber.safeConvert).returns.toMatchTypeOf<
SafeParseReturnType<string, number>
>();

const resA = stringToNumber.safeConvert(""); // valid input but invlaid output
if (resA.success) {
assertType<number>(resA.data);
} else {
assertType<z.ZodError>(resA.error);
}

expectTypeOf(numberToString.safeConvert).toBeFunction();
expectTypeOf(numberToString.safeConvert)
.parameter(0)
.toMatchTypeOf<number>();
expectTypeOf(numberToString.safeConvert).returns.toMatchTypeOf<
SafeParseReturnType<number, string>
>();

const resB = numberToString.safeConvert(321);
if (resB.success) {
assertType<string>(resB.data);
} else {
assertType<z.ZodError>(resB.error);
}
});
});

describe("convertAsync", () => {
test("input and output types to be enforced in", () => {
expectTypeOf(asyncStringToNumber.convertAsync).toBeFunction();
expectTypeOf(asyncStringToNumber.convertAsync)
.parameter(0)
.toMatchTypeOf<string>();
expectTypeOf(asyncStringToNumber.convertAsync).returns.toMatchTypeOf<
Promise<number>
>();

expectTypeOf(asyncNumberToString.convertAsync).toBeFunction();
expectTypeOf(asyncNumberToString.convertAsync)
.parameter(0)
.toMatchTypeOf<number>();
expectTypeOf(asyncNumberToString.convertAsync).returns.toMatchTypeOf<
Promise<string>
>();
});
});

describe("safeConvertAsync", () => {
test("input and output types to be enforced in", async () => {
expectTypeOf(asyncStringToNumber.safeConvertAsync).toBeFunction();
expectTypeOf(asyncStringToNumber.safeConvertAsync)
.parameter(0)
.toMatchTypeOf<string>();
expectTypeOf(asyncStringToNumber.safeConvertAsync).returns.toMatchTypeOf<
Promise<SafeParseReturnType<string, number>>
>();

const resA = await asyncStringToNumber.safeConvertAsync(""); // valid input but invlaid output
if (resA.success) {
assertType<number>(resA.data);
} else {
assertType<z.ZodError>(resA.error);
}

expectTypeOf(asyncNumberToString.safeConvertAsync).toBeFunction();
expectTypeOf(asyncNumberToString.safeConvertAsync)
.parameter(0)
.toMatchTypeOf<number>();
expectTypeOf(asyncNumberToString.safeConvertAsync).returns.toMatchTypeOf<
Promise<SafeParseReturnType<number, string>>
>();

const resB = await asyncNumberToString.safeConvertAsync(321);
if (resB.success) {
assertType<string>(resB.data);
} else {
assertType<z.ZodError>(resB.error);
}
});
});
22 changes: 22 additions & 0 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,28 @@ export abstract class ZodType<
isNullable(): boolean {
return this.safeParse(null).success;
}

convert(data: Input, params?: Partial<ParseParams>): Output {
return this.parse(data, params);
}

safeConvert(
data: Input,
params?: Partial<ParseParams>
): SafeParseReturnType<Input, Output> {
return this.safeParse(data, params);
}

convertAsync(data: Input, params?: Partial<ParseParams>): Promise<Output> {
return this.parseAsync(data, params);
}

safeConvertAsync(
data: Input,
params?: Partial<ParseParams>
): Promise<SafeParseReturnType<Input, Output>> {
return this.safeParseAsync(data, params);
}
}

/////////////////////////////////////////
Expand Down
26 changes: 20 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"tslib": "^2.3.1",
"tsx": "^3.8.0",
"typescript": "~4.5.5",
"vitest": "^0.32.2"
"vitest": "^2.1.5"
},
"exports": {
".": {
Expand All @@ -59,14 +59,28 @@
"url": "https://github.com/colinhacks/zod/issues"
},
"description": "TypeScript-first schema declaration and validation library with static type inference",
"files": ["/lib", "/index.d.ts"],
"files": [
"/lib",
"/index.d.ts"
],
"funding": "https://github.com/sponsors/colinhacks",
"homepage": "https://zod.dev",
"keywords": ["typescript", "schema", "validation", "type", "inference"],
"keywords": [
"typescript",
"schema",
"validation",
"type",
"inference"
],
"license": "MIT",
"lint-staged": {
"src/*.ts": ["eslint --cache --fix", "prettier --ignore-unknown --write"],
"*.md": ["prettier --ignore-unknown --write"]
"src/*.ts": [
"eslint --cache --fix",
"prettier --ignore-unknown --write"
],
"*.md": [
"prettier --ignore-unknown --write"
]
},
"scripts": {
"prettier:check": "prettier --check src/**/*.ts deno/lib/**/*.ts *.md --no-error-on-unmatched-pattern",
Expand Down Expand Up @@ -104,4 +118,4 @@
}
},
"types": "./index.d.ts"
}
}
Loading

0 comments on commit 18a97a4

Please sign in to comment.