@@ -287,14 +287,59 @@ function testDomRouter(
287287
288288 function Boundary ( ) {
289289 let error = useRouteError ( ) ;
290- return isRouteErrorResponse ( error ) ? < h1 > Yes!</ h1 > : < h2 > No :(</ h2 > ;
290+ return isRouteErrorResponse ( error ) ? (
291+ < pre > { JSON . stringify ( error ) } </ pre >
292+ ) : (
293+ < p > No :(</ p >
294+ ) ;
291295 }
292296
293297 expect ( getHtml ( container ) ) . toMatchInlineSnapshot ( `
294298 "<div>
295- <h1>
296- Yes!
297- </h1>
299+ <pre>
300+ {\\"status\\":404,\\"statusText\\":\\"Not Found\\",\\"internal\\":false,\\"data\\":{\\"not\\":\\"found\\"}}
301+ </pre>
302+ </div>"
303+ ` ) ;
304+ } ) ;
305+
306+ it ( "deserializes Error instances from the window" , async ( ) => {
307+ window . __staticRouterHydrationData = {
308+ loaderData : { } ,
309+ actionData : null ,
310+ errors : {
311+ "0" : {
312+ message : "error message" ,
313+ __type : "Error" ,
314+ } ,
315+ } ,
316+ } ;
317+ let { container } = render (
318+ < TestDataRouter window = { getWindow ( "/" ) } >
319+ < Route path = "/" element = { < h1 > Nope</ h1 > } errorElement = { < Boundary /> } />
320+ </ TestDataRouter >
321+ ) ;
322+
323+ function Boundary ( ) {
324+ let error = useRouteError ( ) ;
325+ return error instanceof Error ? (
326+ < >
327+ < pre > { error . toString ( ) } </ pre >
328+ < pre > stack:{ error . stack } </ pre >
329+ </ >
330+ ) : (
331+ < p > No :(</ p >
332+ ) ;
333+ }
334+
335+ expect ( getHtml ( container ) ) . toMatchInlineSnapshot ( `
336+ "<div>
337+ <pre>
338+ Error: error message
339+ </pre>
340+ <pre>
341+ stack:
342+ </pre>
298343 </div>"
299344 ` ) ;
300345 } ) ;
@@ -1523,6 +1568,157 @@ function testDomRouter(
15231568 ` ) ;
15241569 } ) ;
15251570
1571+ it ( "allows a button to override the <form method>" , async ( ) => {
1572+ let loaderDefer = createDeferred ( ) ;
1573+
1574+ let { container } = render (
1575+ < TestDataRouter window = { getWindow ( "/" ) } hydrationData = { { } } >
1576+ < Route
1577+ path = "/"
1578+ action = { async ( { request } ) => {
1579+ throw new Error ( "Should not hit this" ) ;
1580+ } }
1581+ loader = { ( ) => loaderDefer . promise }
1582+ element = { < Home /> }
1583+ />
1584+ </ TestDataRouter >
1585+ ) ;
1586+
1587+ function Home ( ) {
1588+ let data = useLoaderData ( ) ;
1589+ let navigation = useNavigation ( ) ;
1590+ return (
1591+ < div >
1592+ < Form
1593+ method = "post"
1594+ onSubmit = { ( e ) => {
1595+ // jsdom doesn't handle submitter so we add it here
1596+ // See https://github.com/jsdom/jsdom/issues/3117
1597+ // @ts -expect-error
1598+ e . nativeEvent . submitter =
1599+ e . currentTarget . querySelector ( "button" ) ;
1600+ } }
1601+ >
1602+ < input name = "test" value = "value" />
1603+ < button type = "submit" formMethod = "get" >
1604+ Submit Form
1605+ </ button >
1606+ </ Form >
1607+ < div id = "output" >
1608+ < p > { navigation . state } </ p >
1609+ < p > { data } </ p >
1610+ </ div >
1611+ < Outlet />
1612+ </ div >
1613+ ) ;
1614+ }
1615+
1616+ expect ( getHtml ( container . querySelector ( "#output" ) ) )
1617+ . toMatchInlineSnapshot ( `
1618+ "<div
1619+ id=\\"output\\"
1620+ >
1621+ <p>
1622+ idle
1623+ </p>
1624+ <p />
1625+ </div>"
1626+ ` ) ;
1627+
1628+ fireEvent . click ( screen . getByText ( "Submit Form" ) ) ;
1629+ await waitFor ( ( ) => screen . getByText ( "loading" ) ) ;
1630+ expect ( getHtml ( container . querySelector ( "#output" ) ) )
1631+ . toMatchInlineSnapshot ( `
1632+ "<div
1633+ id=\\"output\\"
1634+ >
1635+ <p>
1636+ loading
1637+ </p>
1638+ <p />
1639+ </div>"
1640+ ` ) ;
1641+
1642+ loaderDefer . resolve ( "Loader Data" ) ;
1643+ await waitFor ( ( ) => screen . getByText ( "idle" ) ) ;
1644+ expect ( getHtml ( container . querySelector ( "#output" ) ) )
1645+ . toMatchInlineSnapshot ( `
1646+ "<div
1647+ id=\\"output\\"
1648+ >
1649+ <p>
1650+ idle
1651+ </p>
1652+ <p>
1653+ Loader Data
1654+ </p>
1655+ </div>"
1656+ ` ) ;
1657+ } ) ;
1658+
1659+ it ( "supports uppercase form method attributes" , async ( ) => {
1660+ let loaderDefer = createDeferred ( ) ;
1661+ let actionDefer = createDeferred ( ) ;
1662+
1663+ let { container } = render (
1664+ < TestDataRouter window = { getWindow ( "/" ) } hydrationData = { { } } >
1665+ < Route
1666+ path = "/"
1667+ action = { async ( { request } ) => {
1668+ let resolvedValue = await actionDefer . promise ;
1669+ let formData = await request . formData ( ) ;
1670+ return `${ resolvedValue } :${ formData . get ( "test" ) } ` ;
1671+ } }
1672+ loader = { ( ) => loaderDefer . promise }
1673+ element = { < Home /> }
1674+ />
1675+ </ TestDataRouter >
1676+ ) ;
1677+
1678+ function Home ( ) {
1679+ let data = useLoaderData ( ) ;
1680+ let actionData = useActionData ( ) ;
1681+ let navigation = useNavigation ( ) ;
1682+ return (
1683+ < div >
1684+ < Form method = "POST" >
1685+ < input name = "test" value = "value" />
1686+ < button type = "submit" > Submit Form</ button >
1687+ </ Form >
1688+ < div id = "output" >
1689+ < p > { navigation . state } </ p >
1690+ < p > { data } </ p >
1691+ < p > { actionData } </ p >
1692+ </ div >
1693+ < Outlet />
1694+ </ div >
1695+ ) ;
1696+ }
1697+
1698+ fireEvent . click ( screen . getByText ( "Submit Form" ) ) ;
1699+ await waitFor ( ( ) => screen . getByText ( "submitting" ) ) ;
1700+ actionDefer . resolve ( "Action Data" ) ;
1701+ await waitFor ( ( ) => screen . getByText ( "loading" ) ) ;
1702+ loaderDefer . resolve ( "Loader Data" ) ;
1703+ await waitFor ( ( ) => screen . getByText ( "idle" ) ) ;
1704+ expect ( getHtml ( container . querySelector ( "#output" ) ) )
1705+ . toMatchInlineSnapshot ( `
1706+ "<div
1707+ id=\\"output\\"
1708+ >
1709+ <p>
1710+ idle
1711+ </p>
1712+ <p>
1713+ Loader Data
1714+ </p>
1715+ <p>
1716+ Action Data:value
1717+ </p>
1718+ </div>"
1719+ ` ) ;
1720+ } ) ;
1721+
15261722 describe ( "<Form action>" , ( ) => {
15271723 function NoActionComponent ( ) {
15281724 return (
0 commit comments