@@ -1525,206 +1525,6 @@ describe('invalidate', () => {
15251525 ) . toBeInTheDocument ( )
15261526 expect ( screen . queryByTestId ( 'loader-route' ) ) . not . toBeInTheDocument ( )
15271527 } )
1528-
1529- /**
1530- * Regression test for HMR with inline arrow function components:
1531- * - When a route uses an inline arrow function for `component` (common in file-based routing),
1532- * React Refresh cannot register the component for HMR updates.
1533- * - The router's HMR handler calls `router.invalidate()` to trigger updates.
1534- * - The Match component must include `invalid` in its useRouterState selector so that
1535- * React detects the state change and re-renders the component.
1536- * - Without this, HMR updates are sent but the UI doesn't update because React
1537- * doesn't see any state change to trigger a re-render.
1538- *
1539- * This test simulates HMR by:
1540- * 1. Rendering a route with component v1
1541- * 2. Swapping to component v2 (simulating what HMR does to route.options.component)
1542- * 3. Calling router.invalidate()
1543- * 4. Verifying that the NEW component v2 is now rendered
1544- */
1545- it ( 'picks up new component after invalidate simulating HMR (HMR regression)' , async ( ) => {
1546- const history = createMemoryHistory ( {
1547- initialEntries : [ '/hmr-test' ] ,
1548- } )
1549-
1550- const rootRoute = createRootRoute ( {
1551- component : ( ) => < Outlet /> ,
1552- } )
1553-
1554- const hmrRoute = createRoute ( {
1555- getParentRoute : ( ) => rootRoute ,
1556- path : '/hmr-test' ,
1557- // Using inline arrow function - this is what React Refresh cannot track
1558- component : ( ) => {
1559- return < div data-testid = "hmr-component" > Version 1</ div >
1560- } ,
1561- } )
1562-
1563- const router = createRouter ( {
1564- routeTree : rootRoute . addChildren ( [ hmrRoute ] ) ,
1565- history,
1566- } )
1567-
1568- render ( < RouterProvider router = { router } /> )
1569-
1570- await act ( ( ) => router . load ( ) )
1571-
1572- // Verify initial component renders
1573- expect ( await screen . findByTestId ( 'hmr-component' ) ) . toHaveTextContent (
1574- 'Version 1' ,
1575- )
1576-
1577- // Simulate HMR: swap the component (this is what happens when Vite hot-reloads a module)
1578- hmrRoute . options . component = ( ) => {
1579- return < div data-testid = "hmr-component" > Version 2</ div >
1580- }
1581-
1582- // Simulate HMR invalidation - this is what the router's HMR handler does
1583- await act ( ( ) => router . invalidate ( ) )
1584-
1585- // The NEW component should now be rendered
1586- // Without the fix (invalid not in selector), this would still show "Version 1"
1587- expect ( await screen . findByTestId ( 'hmr-component' ) ) . toHaveTextContent (
1588- 'Version 2' ,
1589- )
1590- } )
1591-
1592- /**
1593- * Test to verify render count after invalidate (no loader).
1594- * The fix should cause minimal re-renders - ideally just enough to pick up the new component.
1595- */
1596- it ( 'renders minimal times after invalidate without loader (render count verification)' , async ( ) => {
1597- const history = createMemoryHistory ( {
1598- initialEntries : [ '/render-count-test' ] ,
1599- } )
1600-
1601- // Use a mock to track renders across component swaps
1602- const renderTracker = vi . fn ( )
1603-
1604- const rootRoute = createRootRoute ( {
1605- component : ( ) => < Outlet /> ,
1606- } )
1607-
1608- const testRoute = createRoute ( {
1609- getParentRoute : ( ) => rootRoute ,
1610- path : '/render-count-test' ,
1611- component : ( ) => {
1612- renderTracker ( 'v1' )
1613- return < div data-testid = "test-component" > Version 1</ div >
1614- } ,
1615- } )
1616-
1617- const router = createRouter ( {
1618- routeTree : rootRoute . addChildren ( [ testRoute ] ) ,
1619- history,
1620- } )
1621-
1622- render ( < RouterProvider router = { router } /> )
1623- await act ( ( ) => router . load ( ) )
1624-
1625- expect ( await screen . findByTestId ( 'test-component' ) ) . toHaveTextContent (
1626- 'Version 1' ,
1627- )
1628- const initialCallCount = renderTracker . mock . calls . length
1629-
1630- // Simulate HMR: swap component (keep using same tracker)
1631- testRoute . options . component = ( ) => {
1632- renderTracker ( 'v2' )
1633- return < div data-testid = "test-component" > Version 2</ div >
1634- }
1635-
1636- await act ( ( ) => router . invalidate ( ) )
1637-
1638- expect ( await screen . findByTestId ( 'test-component' ) ) . toHaveTextContent (
1639- 'Version 2' ,
1640- )
1641-
1642- // Count renders after invalidate
1643- const totalCalls = renderTracker . mock . calls . length
1644- const rendersAfterInvalidate = totalCalls - initialCallCount
1645-
1646- // We expect exactly 1 render to pick up new component
1647- expect ( rendersAfterInvalidate ) . toBe ( 1 )
1648- } )
1649-
1650- /**
1651- * Test to verify render count after invalidate WITH async loader.
1652- * Component consumes loader data and loader returns different data on each call.
1653- */
1654- it ( 'renders minimal times after invalidate with async loader (render count verification)' , async ( ) => {
1655- const history = createMemoryHistory ( {
1656- initialEntries : [ '/render-count-loader-test' ] ,
1657- } )
1658-
1659- const renderTracker = vi . fn ( )
1660- let loaderCallCount = 0
1661- const loader = vi . fn ( async ( ) => {
1662- await new Promise ( ( r ) => setTimeout ( r , 10 ) )
1663- loaderCallCount ++
1664- return { data : `loaded-${ loaderCallCount } ` }
1665- } )
1666-
1667- const rootRoute = createRootRoute ( {
1668- component : ( ) => < Outlet /> ,
1669- } )
1670-
1671- const testRoute = createRoute ( {
1672- getParentRoute : ( ) => rootRoute ,
1673- path : '/render-count-loader-test' ,
1674- loader,
1675- component : ( ) => {
1676- const loaderData = testRoute . useLoaderData ( )
1677- renderTracker ( 'v1' , loaderData )
1678- return (
1679- < div data-testid = "test-component" > Version 1 - { loaderData . data } </ div >
1680- )
1681- } ,
1682- } )
1683-
1684- const router = createRouter ( {
1685- routeTree : rootRoute . addChildren ( [ testRoute ] ) ,
1686- history,
1687- } )
1688-
1689- render ( < RouterProvider router = { router } /> )
1690- await act ( ( ) => router . load ( ) )
1691-
1692- expect ( await screen . findByTestId ( 'test-component' ) ) . toHaveTextContent (
1693- 'Version 1 - loaded-1' ,
1694- )
1695- const initialCallCount = renderTracker . mock . calls . length
1696- const initialLoaderCalls = loader . mock . calls . length
1697-
1698- // Simulate HMR: swap component to new version that also consumes loader data
1699- testRoute . options . component = ( ) => {
1700- const loaderData = testRoute . useLoaderData ( )
1701- renderTracker ( 'v2' , loaderData )
1702- return (
1703- < div data-testid = "test-component" > Version 2 - { loaderData . data } </ div >
1704- )
1705- }
1706-
1707- await act ( ( ) => router . invalidate ( ) )
1708-
1709- // Wait for new component with new loader data
1710- await waitFor ( ( ) => {
1711- expect ( screen . getByTestId ( 'test-component' ) ) . toHaveTextContent (
1712- 'Version 2 - loaded-2' ,
1713- )
1714- } )
1715-
1716- const rendersAfterInvalidate =
1717- renderTracker . mock . calls . length - initialCallCount
1718- const loaderCallsAfterInvalidate =
1719- loader . mock . calls . length - initialLoaderCalls
1720-
1721- // Loader should be called once
1722- expect ( loaderCallsAfterInvalidate ) . toBe ( 1 )
1723- // Component renders twice when consuming loader data that changes:
1724- // 1. Once for invalidation (new component picks up)
1725- // 2. Once when new loader data arrives
1726- expect ( rendersAfterInvalidate ) . toBe ( 2 )
1727- } )
17281528} )
17291529
17301530describe ( 'search params in URL' , ( ) => {
0 commit comments