@@ -195,4 +195,169 @@ describe('ReactDOMFizzForm', () => {
195195 'Prop `action` did not match. Server: "action" Client: "function action(formData) {}"' ,
196196 ) ;
197197 } ) ;
198+
199+ // @gate enableFormActions
200+ it ( 'should reset form fields after you update away from hydrated function' , async ( ) => {
201+ const formRef = React . createRef ( ) ;
202+ const inputRef = React . createRef ( ) ;
203+ const buttonRef = React . createRef ( ) ;
204+ function action ( formData ) { }
205+ function App ( { isUpdate} ) {
206+ return (
207+ < form
208+ action = { isUpdate ? 'action' : action }
209+ ref = { formRef }
210+ method = { isUpdate ? 'POST' : null } >
211+ < input
212+ type = "submit"
213+ formAction = { isUpdate ? 'action' : action }
214+ ref = { inputRef }
215+ formTarget = { isUpdate ? 'elsewhere' : null }
216+ />
217+ < button
218+ formAction = { isUpdate ? 'action' : action }
219+ ref = { buttonRef }
220+ formEncType = { isUpdate ? 'multipart/form-data' : null }
221+ />
222+ </ form >
223+ ) ;
224+ }
225+
226+ const stream = await ReactDOMServer . renderToReadableStream ( < App /> ) ;
227+ await readIntoContainer ( stream ) ;
228+ let root ;
229+ await act ( async ( ) => {
230+ root = ReactDOMClient . hydrateRoot ( container , < App /> ) ;
231+ } ) ;
232+ await act ( async ( ) => {
233+ root . render ( < App isUpdate = { true } /> ) ;
234+ } ) ;
235+ expect ( formRef . current . getAttribute ( 'action' ) ) . toBe ( 'action' ) ;
236+ expect ( formRef . current . hasAttribute ( 'encType' ) ) . toBe ( false ) ;
237+ expect ( formRef . current . getAttribute ( 'method' ) ) . toBe ( 'POST' ) ;
238+ expect ( formRef . current . hasAttribute ( 'target' ) ) . toBe ( false ) ;
239+
240+ expect ( inputRef . current . getAttribute ( 'formAction' ) ) . toBe ( 'action' ) ;
241+ expect ( inputRef . current . hasAttribute ( 'name' ) ) . toBe ( false ) ;
242+ expect ( inputRef . current . hasAttribute ( 'formEncType' ) ) . toBe ( false ) ;
243+ expect ( inputRef . current . hasAttribute ( 'formMethod' ) ) . toBe ( false ) ;
244+ expect ( inputRef . current . getAttribute ( 'formTarget' ) ) . toBe ( 'elsewhere' ) ;
245+
246+ expect ( buttonRef . current . getAttribute ( 'formAction' ) ) . toBe ( 'action' ) ;
247+ expect ( buttonRef . current . hasAttribute ( 'name' ) ) . toBe ( false ) ;
248+ expect ( buttonRef . current . getAttribute ( 'formEncType' ) ) . toBe (
249+ 'multipart/form-data' ,
250+ ) ;
251+ expect ( buttonRef . current . hasAttribute ( 'formMethod' ) ) . toBe ( false ) ;
252+ expect ( buttonRef . current . hasAttribute ( 'formTarget' ) ) . toBe ( false ) ;
253+ } ) ;
254+
255+ // @gate enableFormActions
256+ it ( 'should reset form fields after you remove a hydrated function' , async ( ) => {
257+ const formRef = React . createRef ( ) ;
258+ const inputRef = React . createRef ( ) ;
259+ const buttonRef = React . createRef ( ) ;
260+ function action ( formData ) { }
261+ function App ( { isUpdate} ) {
262+ return (
263+ < form action = { isUpdate ? undefined : action } ref = { formRef } >
264+ < input
265+ type = "submit"
266+ formAction = { isUpdate ? undefined : action }
267+ ref = { inputRef }
268+ />
269+ < button formAction = { isUpdate ? undefined : action } ref = { buttonRef } />
270+ </ form >
271+ ) ;
272+ }
273+
274+ const stream = await ReactDOMServer . renderToReadableStream ( < App /> ) ;
275+ await readIntoContainer ( stream ) ;
276+ let root ;
277+ await act ( async ( ) => {
278+ root = ReactDOMClient . hydrateRoot ( container , < App /> ) ;
279+ } ) ;
280+ await act ( async ( ) => {
281+ root . render ( < App isUpdate = { true } /> ) ;
282+ } ) ;
283+ expect ( formRef . current . hasAttribute ( 'action' ) ) . toBe ( false ) ;
284+ expect ( formRef . current . hasAttribute ( 'encType' ) ) . toBe ( false ) ;
285+ expect ( formRef . current . hasAttribute ( 'method' ) ) . toBe ( false ) ;
286+ expect ( formRef . current . hasAttribute ( 'target' ) ) . toBe ( false ) ;
287+
288+ expect ( inputRef . current . hasAttribute ( 'formAction' ) ) . toBe ( false ) ;
289+ expect ( inputRef . current . hasAttribute ( 'name' ) ) . toBe ( false ) ;
290+ expect ( inputRef . current . hasAttribute ( 'formEncType' ) ) . toBe ( false ) ;
291+ expect ( inputRef . current . hasAttribute ( 'formMethod' ) ) . toBe ( false ) ;
292+ expect ( inputRef . current . hasAttribute ( 'formTarget' ) ) . toBe ( false ) ;
293+
294+ expect ( buttonRef . current . hasAttribute ( 'formAction' ) ) . toBe ( false ) ;
295+ expect ( buttonRef . current . hasAttribute ( 'name' ) ) . toBe ( false ) ;
296+ expect ( buttonRef . current . hasAttribute ( 'formEncType' ) ) . toBe ( false ) ;
297+ expect ( buttonRef . current . hasAttribute ( 'formMethod' ) ) . toBe ( false ) ;
298+ expect ( buttonRef . current . hasAttribute ( 'formTarget' ) ) . toBe ( false ) ;
299+ } ) ;
300+
301+ // @gate enableFormActions
302+ it ( 'should restore the form fields even if they were incorrectly set' , async ( ) => {
303+ const formRef = React . createRef ( ) ;
304+ const inputRef = React . createRef ( ) ;
305+ const buttonRef = React . createRef ( ) ;
306+ function action ( formData ) { }
307+ function App ( { isUpdate} ) {
308+ return (
309+ < form
310+ action = { isUpdate ? 'action' : action }
311+ ref = { formRef }
312+ method = "DELETE" >
313+ < input
314+ type = "submit"
315+ formAction = { isUpdate ? 'action' : action }
316+ ref = { inputRef }
317+ formTarget = "elsewhere"
318+ />
319+ < button
320+ formAction = { isUpdate ? 'action' : action }
321+ ref = { buttonRef }
322+ formEncType = "text/plain"
323+ />
324+ </ form >
325+ ) ;
326+ }
327+
328+ // Specifying the extra form fields are a DEV error, but we expect it
329+ // to eventually still be patched up after an update.
330+ await expect ( async ( ) => {
331+ const stream = await ReactDOMServer . renderToReadableStream ( < App /> ) ;
332+ await readIntoContainer ( stream ) ;
333+ } ) . toErrorDev ( [
334+ 'Cannot specify a encType or method for a form that specifies a function as the action.' ,
335+ 'Cannot specify a formTarget for a button that specifies a function as a formAction.' ,
336+ ] ) ;
337+ let root ;
338+ await expect ( async ( ) => {
339+ await act ( async ( ) => {
340+ root = ReactDOMClient . hydrateRoot ( container , < App /> ) ;
341+ } ) ;
342+ } ) . toErrorDev ( [ 'Prop `formTarget` did not match.' ] ) ;
343+ await act ( async ( ) => {
344+ root . render ( < App isUpdate = { true } /> ) ;
345+ } ) ;
346+ expect ( formRef . current . getAttribute ( 'action' ) ) . toBe ( 'action' ) ;
347+ expect ( formRef . current . hasAttribute ( 'encType' ) ) . toBe ( false ) ;
348+ expect ( formRef . current . getAttribute ( 'method' ) ) . toBe ( 'DELETE' ) ;
349+ expect ( formRef . current . hasAttribute ( 'target' ) ) . toBe ( false ) ;
350+
351+ expect ( inputRef . current . getAttribute ( 'formAction' ) ) . toBe ( 'action' ) ;
352+ expect ( inputRef . current . hasAttribute ( 'name' ) ) . toBe ( false ) ;
353+ expect ( inputRef . current . hasAttribute ( 'formEncType' ) ) . toBe ( false ) ;
354+ expect ( inputRef . current . hasAttribute ( 'formMethod' ) ) . toBe ( false ) ;
355+ expect ( inputRef . current . getAttribute ( 'formTarget' ) ) . toBe ( 'elsewhere' ) ;
356+
357+ expect ( buttonRef . current . getAttribute ( 'formAction' ) ) . toBe ( 'action' ) ;
358+ expect ( buttonRef . current . hasAttribute ( 'name' ) ) . toBe ( false ) ;
359+ expect ( buttonRef . current . getAttribute ( 'formEncType' ) ) . toBe ( 'text/plain' ) ;
360+ expect ( buttonRef . current . hasAttribute ( 'formMethod' ) ) . toBe ( false ) ;
361+ expect ( buttonRef . current . hasAttribute ( 'formTarget' ) ) . toBe ( false ) ;
362+ } ) ;
198363} ) ;
0 commit comments