Skip to content

Commit aa1c675

Browse files
committed
Add File value support
1 parent bc1ad8c commit aa1c675

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

packages/react-zorm/__tests__/parse-form.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,22 @@ describe("with any", () => {
155155
things: [undefined, { ding: "dong" }],
156156
});
157157
});
158+
159+
test("can handle files ", () => {
160+
const form = new FormData();
161+
const file = new File(["(⌐□_□)"], "chucknorris.txt", {
162+
type: "text/plain",
163+
});
164+
form.append("myFile", file);
165+
166+
const res = parseFormAny(form);
167+
168+
expect(res).toEqual({
169+
myFile: file,
170+
});
171+
172+
expect(res.myFile).toBe(file);
173+
});
158174
});
159175

160176
describe("combine chains with parsing", () => {

packages/react-zorm/__tests__/use-zorm.test.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,3 +904,105 @@ test.skip("[TYPE ONLY] can narrow validation type to success", () => {
904904
}
905905
}
906906
});
907+
908+
test("can validate files", async () => {
909+
const spy = jest.fn();
910+
911+
const Schema = z.object({
912+
myFile: z.instanceof(File).refine((file) => {
913+
spy(file.type);
914+
return file.type === "image/png";
915+
}, "Only .png images are allowed"),
916+
});
917+
918+
function Test() {
919+
const zo = useZorm("form", Schema);
920+
921+
return (
922+
<form ref={zo.ref} data-testid="form">
923+
<input
924+
data-testid="file"
925+
type="file"
926+
name={zo.fields.myFile()}
927+
/>
928+
929+
{zo.errors.myFile((e) => (
930+
<div data-testid="error">{e.message}</div>
931+
))}
932+
</form>
933+
);
934+
}
935+
936+
render(<Test />);
937+
938+
const file = new File(["(⌐□_□)"], "chucknorris.txt", {
939+
type: "text/plain",
940+
});
941+
942+
const fileInput = screen.getByTestId("file") as HTMLInputElement;
943+
await userEvent.upload(fileInput, file);
944+
fireEvent.submit(screen.getByTestId("form"));
945+
946+
{
947+
// TEMP TESTS
948+
const form = screen.getByTestId("form") as HTMLFormElement;
949+
expect(fileInput.files?.[0]?.name).toBe("chucknorris.txt");
950+
const formData = new FormData(form);
951+
expect(formData.get("myFile")).toBeInstanceOf(File);
952+
const formFile = formData.get("myFile") as File;
953+
954+
// XXX Bug in jsdom or react testing lib?
955+
// FormData cannot read file from the form.
956+
expect(formFile.name).toBe("chucknorris.txt");
957+
}
958+
959+
expect(spy).toHaveBeenCalledWith("text/plain");
960+
961+
expect(screen.queryByTestId("error")).toHaveTextContent(
962+
"Only .png images are allowed",
963+
);
964+
});
965+
966+
test("can submit files", async () => {
967+
const spy = jest.fn();
968+
969+
const Schema = z.object({
970+
myFile: z.instanceof(File).refine((file) => {
971+
return file.type === "image/png";
972+
}, "Only .png images are allowed"),
973+
});
974+
975+
function Test() {
976+
const zo = useZorm("form", Schema, {
977+
onValidSubmit(e) {
978+
spy(e.data.myFile.name);
979+
},
980+
});
981+
982+
return (
983+
<form ref={zo.ref} data-testid="form">
984+
<input
985+
data-testid="file"
986+
type="file"
987+
name={zo.fields.myFile()}
988+
/>
989+
990+
{zo.errors.myFile((e) => (
991+
<div data-testid="error">{e.message}</div>
992+
))}
993+
</form>
994+
);
995+
}
996+
997+
render(<Test />);
998+
999+
const file = new File(["(⌐□_□)"], "chucknorris.png", {
1000+
type: "image/png",
1001+
});
1002+
1003+
const fileInput = screen.getByTestId("file") as HTMLInputElement;
1004+
await userEvent.upload(fileInput, file);
1005+
fireEvent.submit(screen.getByTestId("form"));
1006+
1007+
expect(spy).toHaveBeenCalledWith("text/plain");
1008+
});

packages/react-zorm/src/types.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SafeParseReturnType, ZodCustomIssue, ZodIssue, ZodType } from "zod";
22

33
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
44

5-
export type DeepNonNullable<T> = T extends Primitive | Date
5+
export type DeepNonNullable<T> = T extends Primitive | Date | File
66
? NonNullable<T>
77
: T extends {}
88
? { [K in keyof T]-?: DeepNonNullable<T[K]> }
@@ -43,6 +43,8 @@ export type FieldChain<T extends object> = {
4343
: FieldChain<T[P][0]>
4444
: T[P] extends Date
4545
? FieldGetter
46+
: T[P] extends File
47+
? FieldGetter
4648
: T[P] extends object
4749
? FieldChain<T[P]>
4850
: FieldGetter;

0 commit comments

Comments
 (0)