Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:

- name: Lint
run: bun lint

- name: Test
run: bun run test

- name: TS
run: bun lint:types
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Here is a quick guide to doing code contributions to the library.

4. If you've added a code that should be tested, ensure the test suite still passes.

> bun test
> bun run test

5. Try to write some unit tests to cover as much of your code as possible.

Expand Down
22 changes: 11 additions & 11 deletions arktype/src/__tests__/__snapshots__/arktype.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,41 @@ exports[`arktypeResolver > should return a single error from arktypeResolver whe
"accessToken": {
"message": "accessToken must be a number or a string (was missing)",
"ref": undefined,
"type": "required",
"type": "",
},
"birthYear": {
"message": "birthYear must be a number (was a string)",
"ref": undefined,
"type": "domain",
"type": "",
},
"dateStr": {
"message": "dateStr must be a Date (was missing)",
"ref": undefined,
"type": "required",
"type": "",
},
"email": {
"message": "email must be an email address (was "")",
"ref": {
"name": "email",
},
"type": "pattern",
"type": "",
},
"enabled": {
"message": "enabled must be boolean (was missing)",
"ref": undefined,
"type": "required",
"type": "",
},
"like": [
{
"id": {
"message": "like[0].id must be a number (was a string)",
"ref": undefined,
"type": "domain",
"type": "",
},
"name": {
"message": "like[0].name must be a string (was missing)",
"ref": undefined,
"type": "required",
"type": "",
},
},
],
Expand All @@ -49,24 +49,24 @@ exports[`arktypeResolver > should return a single error from arktypeResolver whe
"ref": {
"name": "password",
},
"type": "union",
"type": "",
},
"repeatPassword": {
"message": "repeatPassword must be a string (was missing)",
"ref": undefined,
"type": "required",
"type": "",
},
"tags": {
"message": "tags must be an array (was missing)",
"ref": undefined,
"type": "required",
"type": "",
},
"username": {
"message": "username must be a string (was missing)",
"ref": {
"name": "username",
},
"type": "required",
"type": "",
},
},
"values": {},
Expand Down
21 changes: 15 additions & 6 deletions arktype/src/__tests__/arktype.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { renderHook } from '@testing-library/react';
import { type } from 'arktype';
import { Resolver, useForm } from 'react-hook-form';
import { SubmitHandler } from 'react-hook-form';
Expand Down Expand Up @@ -51,9 +52,13 @@ describe('arktypeResolver', () => {
it('should correctly infer the output type from a arktype schema for the handleSubmit function in useForm', () => {
const schema = type({ id: 'number' });

const form = useForm({
resolver: arktypeResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: arktypeResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand All @@ -67,9 +72,13 @@ describe('arktypeResolver', () => {
it('should correctly infer the output type from a arktype schema with a transform for the handleSubmit function in useForm', () => {
const schema = type({ id: type('string').pipe((s) => Number.parseInt(s)) });

const form = useForm({
resolver: arktypeResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: arktypeResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<string>();

Expand Down
1 change: 1 addition & 0 deletions bun.lock

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

21 changes: 15 additions & 6 deletions computed-types/src/__tests__/computed-types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { renderHook } from '@testing-library/react';
import Schema, { number } from 'computed-types';
import { Resolver, SubmitHandler, useForm } from 'react-hook-form';
import { computedTypesResolver } from '..';
Expand Down Expand Up @@ -65,9 +66,13 @@ describe('computedTypesResolver', () => {
it('should correctly infer the output type from a computedTypes schema for the handleSubmit function in useForm', () => {
const schema = Schema({ id: number });

const form = useForm({
resolver: computedTypesResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: computedTypesResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand All @@ -81,9 +86,13 @@ describe('computedTypesResolver', () => {
it('should correctly infer the output type from a computedTypes schema with a transform for the handleSubmit function in useForm', () => {
const schema = Schema({ id: number.transform((val) => String(val)) });

const form = useForm({
resolver: computedTypesResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: computedTypesResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand Down
21 changes: 15 additions & 6 deletions effect-ts/src/__tests__/effect-ts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { renderHook } from '@testing-library/react';
import { Schema } from 'effect';
import { Resolver, useForm } from 'react-hook-form';
import { SubmitHandler } from 'react-hook-form';
Expand Down Expand Up @@ -114,9 +115,13 @@ describe('effectTsResolver', () => {
it('should correctly infer the output type from a effectTs schema for the handleSubmit function in useForm', () => {
const schema = Schema.Struct({ id: Schema.Number });

const form = useForm({
resolver: effectTsResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: effectTsResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand All @@ -133,9 +138,13 @@ describe('effectTsResolver', () => {
}),
});

const form = useForm({
resolver: effectTsResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: effectTsResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand Down
21 changes: 15 additions & 6 deletions io-ts/src/__tests__/io-ts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { renderHook } from '@testing-library/react';
import * as t from 'io-ts';
import * as tt from 'io-ts-types';
import { Resolver, SubmitHandler, useForm } from 'react-hook-form';
Expand Down Expand Up @@ -60,9 +61,13 @@ describe('ioTsResolver', () => {
it('should correctly infer the output type from a io-ts schema for the handleSubmit function in useForm', () => {
const schema = t.type({ id: t.number });

const form = useForm({
resolver: ioTsResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: ioTsResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand All @@ -76,9 +81,13 @@ describe('ioTsResolver', () => {
it('should correctly infer the output type from a io-ts schema with a transform for the handleSubmit function in useForm', () => {
const schema = t.type({ id: tt.NumberFromString });

const form = useForm({
resolver: ioTsResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: ioTsResolver(schema),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<string>();

Expand Down
34 changes: 21 additions & 13 deletions standard-schema/src/__tests__/standard-schema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { renderHook } from '@testing-library/react';
import { Resolver, SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod/v3';
import { standardSchemaResolver } from '..';
Expand Down Expand Up @@ -113,13 +114,16 @@ describe('standardSchemaResolver', () => {

it('should correctly infer the output type from a standardSchema schema for the handleSubmit function in useForm', () => {
const schema = z.object({ id: z.number() });

const form = useForm({
resolver: standardSchemaResolver(schema),
defaultValues: {
id: 3,
},
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: standardSchemaResolver(schema),
defaultValues: {
id: 3,
},
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand All @@ -133,12 +137,16 @@ describe('standardSchemaResolver', () => {
it('should correctly infer the output type from a standardSchema schema with a transform for the handleSubmit function in useForm', () => {
const schema = z.object({ id: z.number().transform((val) => String(val)) });

const form = useForm({
resolver: standardSchemaResolver(schema),
defaultValues: {
id: 3,
},
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: standardSchemaResolver(schema),
defaultValues: {
id: 3,
},
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand Down
30 changes: 22 additions & 8 deletions superstruct/src/__tests__/superstruct.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { renderHook } from '@testing-library/react';
import { Resolver, SubmitHandler, useForm } from 'react-hook-form';
import * as s from 'superstruct';
import { superstructResolver } from '..';
Expand All @@ -18,8 +19,9 @@ describe('superstructResolver', () => {
it('should return values from superstructResolver with coerced values', async () => {
const result = await superstructResolver(
s.object({
id: s.coerce(s.number(), s.string(), (val) => String(val)),
id: s.coerce(s.string(), s.number(), (val) => String(val)),
}),
{ coerce: true },
)({ id: 1 }, undefined, {
fields,
shouldUseNativeValidation,
Expand Down Expand Up @@ -64,9 +66,13 @@ describe('superstructResolver', () => {
it('should correctly infer the output type from a superstruct schema for the handleSubmit function in useForm', () => {
const schema = s.object({ id: s.number() });

const form = useForm({
resolver: superstructResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
resolver: superstructResolver(schema, {}),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

Expand All @@ -82,11 +88,19 @@ describe('superstructResolver', () => {
id: s.coerce(s.string(), s.number(), (val) => String(val)),
});

const form = useForm({
resolver: superstructResolver(schema),
});
const {
result: { current: form },
} = renderHook(() =>
useForm({
// With coerce, there is no way to infer the input type of the schema
resolver: superstructResolver<{ id: number }, unknown, { id: string }>(
schema,
{ coerce: true },
),
}),
);

expectTypeOf(form.watch('id')).toEqualTypeOf<string>();
expectTypeOf(form.watch('id')).toEqualTypeOf<number>();

expectTypeOf(form.handleSubmit).parameter(0).toEqualTypeOf<
SubmitHandler<{
Expand Down
13 changes: 13 additions & 0 deletions superstruct/src/superstruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ function parseErrorSchema(error: StructError) {
);
}

// This is required to correctly type the input of the returned function when coerce is true
// superstruct does not store the input type of a schema alongside it
// Infer<typeof schema> gives the output type of the coercion
export function superstructResolver<Input extends FieldValues, Context, Output>(
schema: Struct<Output, any>,
schemaOptions: Omit<Parameters<typeof validate>[2], 'coerce'> & {
coerce: true;
},
resolverOptions?: {
raw?: boolean;
},
): Resolver<Input, Context, Output>;

export function superstructResolver<Input extends FieldValues, Context, Output>(
schema: Struct<Input, any>,
schemaOptions?: Parameters<typeof validate>[2],
Expand Down
Loading