@@ -8,6 +8,49 @@ import { useZorm } from "../src";
88import { assertNotAny } from "./test-helpers" ;
99import { createCustomIssues } from "../src/chains" ;
1010
11+ /**
12+ * For https://github.com/testing-library/user-event/pull/1109
13+ */
14+ class WorkaroundFormData extends FormData {
15+ #formRef?: HTMLFormElement ;
16+ constructor ( ...args : ConstructorParameters < typeof FormData > ) {
17+ super ( ...args ) ;
18+ this . #formRef = args [ 0 ] ;
19+ }
20+
21+ // React Zorm only uses entries() so this is the only method we need to patch
22+ override * entries ( ) {
23+ for ( const [ name , value ] of super . entries ( ) ) {
24+ const entry : [ string , FormDataEntryValue ] = [ name , value ] ;
25+
26+ if ( value instanceof File && this . #formRef) {
27+ const input = this . #formRef. querySelector (
28+ `input[name="${ name } "]` ,
29+ ) ;
30+
31+ if ( input instanceof HTMLInputElement ) {
32+ const realFile = input ?. files ?. [ 0 ] ;
33+ if ( realFile ) {
34+ entry [ 1 ] = realFile ;
35+ }
36+ }
37+ }
38+
39+ yield entry ;
40+ }
41+ }
42+ }
43+
44+ const OrigFormData = globalThis . FormData ;
45+
46+ beforeAll ( ( ) => {
47+ globalThis . FormData = WorkaroundFormData ;
48+ } ) ;
49+
50+ afterAll ( ( ) => {
51+ globalThis . FormData = OrigFormData ;
52+ } ) ;
53+
1154test ( "single field validation" , ( ) => {
1255 const Schema = z . object ( {
1356 thing : z . string ( ) . min ( 1 ) ,
@@ -904,3 +947,92 @@ test.skip("[TYPE ONLY] can narrow validation type to success", () => {
904947 }
905948 }
906949} ) ;
950+
951+ test ( "can validate files" , async ( ) => {
952+ const refineSpy = jest . fn ( ) ;
953+
954+ const Schema = z . object ( {
955+ myFile : z . instanceof ( File ) . refine ( ( file ) => {
956+ refineSpy ( file . type ) ;
957+ return file . type === "image/png" ;
958+ } , "Only .png images are allowed" ) ,
959+ } ) ;
960+
961+ function Test ( ) {
962+ const zo = useZorm ( "form" , Schema ) ;
963+
964+ return (
965+ < form ref = { zo . ref } data-testid = "form" >
966+ < input
967+ data-testid = "file"
968+ type = "file"
969+ name = { zo . fields . myFile ( ) }
970+ />
971+
972+ { zo . errors . myFile ( ( e ) => (
973+ < div data-testid = "error" > { e . message } </ div >
974+ ) ) }
975+ </ form >
976+ ) ;
977+ }
978+
979+ render ( < Test /> ) ;
980+
981+ const file = new File ( [ "(⌐□_□)" ] , "chucknorris.txt" , {
982+ type : "text/plain" ,
983+ } ) ;
984+
985+ const fileInput = screen . getByTestId ( "file" ) as HTMLInputElement ;
986+ await userEvent . upload ( fileInput , file ) ;
987+ fireEvent . submit ( screen . getByTestId ( "form" ) ) ;
988+
989+ expect ( refineSpy ) . toHaveBeenCalledWith ( "text/plain" ) ;
990+
991+ expect ( screen . queryByTestId ( "error" ) ) . toHaveTextContent (
992+ "Only .png images are allowed" ,
993+ ) ;
994+ } ) ;
995+
996+ test ( "can submit files" , async ( ) => {
997+ const submitSpy = jest . fn ( ) ;
998+
999+ const Schema = z . object ( {
1000+ myFile : z . instanceof ( File ) . refine ( ( file ) => {
1001+ return file . type === "image/png" ;
1002+ } , "Only .png images are allowed" ) ,
1003+ } ) ;
1004+
1005+ function Test ( ) {
1006+ const zo = useZorm ( "form" , Schema , {
1007+ onValidSubmit ( e ) {
1008+ submitSpy ( e . data . myFile . name ) ;
1009+ } ,
1010+ } ) ;
1011+
1012+ return (
1013+ < form ref = { zo . ref } data-testid = "form" >
1014+ < input
1015+ data-testid = "file"
1016+ type = "file"
1017+ name = { zo . fields . myFile ( ) }
1018+ />
1019+
1020+ { zo . errors . myFile ( ( e ) => (
1021+ < div data-testid = "error" > { e . message } </ div >
1022+ ) ) }
1023+ </ form >
1024+ ) ;
1025+ }
1026+
1027+ render ( < Test /> ) ;
1028+
1029+ const file = new File ( [ "(⌐□_□)" ] , "chucknorris.png" , {
1030+ type : "image/png" ,
1031+ } ) ;
1032+
1033+ const fileInput = screen . getByTestId ( "file" ) as HTMLInputElement ;
1034+ await userEvent . upload ( fileInput , file ) ;
1035+ fireEvent . submit ( screen . getByTestId ( "form" ) ) ;
1036+
1037+ expect ( submitSpy ) . toHaveBeenCalledWith ( "chucknorris.png" ) ;
1038+ } ) ;
0 commit comments