@@ -20,11 +20,12 @@ import {
20
20
renderHookToSnapshotStream ,
21
21
useTrackRenders ,
22
22
} from "@testing-library/react-render-stream" ;
23
- import { spyOnConsole } from "../../../testing/internal" ;
24
- import { renderHook } from "@testing-library/react" ;
23
+ import { renderAsync , spyOnConsole } from "../../../testing/internal" ;
24
+ import { act , renderHook , screen , waitFor } from "@testing-library/react" ;
25
25
import { InvariantError } from "ts-invariant" ;
26
- import { MockedProvider , wait } from "../../../testing" ;
26
+ import { MockedProvider , MockSubscriptionLink , wait } from "../../../testing" ;
27
27
import { expectTypeOf } from "expect-type" ;
28
+ import userEvent from "@testing-library/user-event" ;
28
29
29
30
function createDefaultRenderStream < TData = unknown > ( ) {
30
31
return createRenderStream ( {
@@ -1566,6 +1567,220 @@ test("tears down all watches when rendering multiple records", async () => {
1566
1567
expect ( cache [ "watches" ] . size ) . toBe ( 0 ) ;
1567
1568
} ) ;
1568
1569
1570
+ test ( "tears down watches after default autoDisposeTimeoutMs if component never renders again after suspending" , async ( ) => {
1571
+ jest . useFakeTimers ( ) ;
1572
+ interface ItemFragment {
1573
+ __typename : "Item" ;
1574
+ id : number ;
1575
+ text : string ;
1576
+ }
1577
+
1578
+ const fragment : TypedDocumentNode < ItemFragment > = gql `
1579
+ fragment ItemFragment on Item {
1580
+ id
1581
+ text
1582
+ }
1583
+ ` ;
1584
+
1585
+ const cache = new InMemoryCache ( ) ;
1586
+ const user = userEvent . setup ( { advanceTimers : jest . advanceTimersByTime } ) ;
1587
+ const link = new MockSubscriptionLink ( ) ;
1588
+ const client = new ApolloClient ( { link, cache } ) ;
1589
+
1590
+ function App ( ) {
1591
+ const [ showItem , setShowItem ] = React . useState ( true ) ;
1592
+
1593
+ return (
1594
+ < ApolloProvider client = { client } >
1595
+ < button onClick = { ( ) => setShowItem ( false ) } > Hide item</ button >
1596
+ { showItem && (
1597
+ < Suspense fallback = "Loading item..." >
1598
+ < Item />
1599
+ </ Suspense >
1600
+ ) }
1601
+ </ ApolloProvider >
1602
+ ) ;
1603
+ }
1604
+
1605
+ function Item ( ) {
1606
+ const { data } = useSuspenseFragment ( {
1607
+ fragment,
1608
+ from : { __typename : "Item" , id : 1 } ,
1609
+ } ) ;
1610
+
1611
+ return < span > { data . text } </ span > ;
1612
+ }
1613
+
1614
+ await renderAsync ( < App /> ) ;
1615
+
1616
+ // Ensure <Greeting /> suspends immediately
1617
+ expect ( screen . getByText ( "Loading item..." ) ) . toBeInTheDocument ( ) ;
1618
+
1619
+ // Hide the greeting before it finishes loading data
1620
+ await act ( ( ) => user . click ( screen . getByText ( "Hide item" ) ) ) ;
1621
+
1622
+ expect ( screen . queryByText ( "Loading item..." ) ) . not . toBeInTheDocument ( ) ;
1623
+
1624
+ client . writeFragment ( {
1625
+ fragment,
1626
+ data : { __typename : "Item" , id : 1 , text : "Item #1" } ,
1627
+ } ) ;
1628
+
1629
+ // clear the microtask queue
1630
+ await act ( ( ) => Promise . resolve ( ) ) ;
1631
+
1632
+ expect ( cache [ "watches" ] . size ) . toBe ( 1 ) ;
1633
+
1634
+ jest . advanceTimersByTime ( 30_000 ) ;
1635
+
1636
+ expect ( cache [ "watches" ] . size ) . toBe ( 0 ) ;
1637
+
1638
+ jest . useRealTimers ( ) ;
1639
+ } ) ;
1640
+
1641
+ test ( "tears down watches after configured autoDisposeTimeoutMs if component never renders again after suspending" , async ( ) => {
1642
+ jest . useFakeTimers ( ) ;
1643
+ interface ItemFragment {
1644
+ __typename : "Item" ;
1645
+ id : number ;
1646
+ text : string ;
1647
+ }
1648
+
1649
+ const fragment : TypedDocumentNode < ItemFragment > = gql `
1650
+ fragment ItemFragment on Item {
1651
+ id
1652
+ text
1653
+ }
1654
+ ` ;
1655
+
1656
+ const user = userEvent . setup ( { advanceTimers : jest . advanceTimersByTime } ) ;
1657
+ const link = new MockSubscriptionLink ( ) ;
1658
+ const cache = new InMemoryCache ( ) ;
1659
+ const client = new ApolloClient ( {
1660
+ link,
1661
+ cache,
1662
+ defaultOptions : {
1663
+ react : {
1664
+ suspense : {
1665
+ autoDisposeTimeoutMs : 5000 ,
1666
+ } ,
1667
+ } ,
1668
+ } ,
1669
+ } ) ;
1670
+
1671
+ function App ( ) {
1672
+ const [ showItem , setShowItem ] = React . useState ( true ) ;
1673
+
1674
+ return (
1675
+ < ApolloProvider client = { client } >
1676
+ < button onClick = { ( ) => setShowItem ( false ) } > Hide item</ button >
1677
+ { showItem && (
1678
+ < Suspense fallback = "Loading item..." >
1679
+ < Item />
1680
+ </ Suspense >
1681
+ ) }
1682
+ </ ApolloProvider >
1683
+ ) ;
1684
+ }
1685
+
1686
+ function Item ( ) {
1687
+ const { data } = useSuspenseFragment ( {
1688
+ fragment,
1689
+ from : { __typename : "Item" , id : 1 } ,
1690
+ } ) ;
1691
+
1692
+ return < span > { data . text } </ span > ;
1693
+ }
1694
+
1695
+ await renderAsync ( < App /> ) ;
1696
+
1697
+ // Ensure <Greeting /> suspends immediately
1698
+ expect ( screen . getByText ( "Loading item..." ) ) . toBeInTheDocument ( ) ;
1699
+
1700
+ // Hide the greeting before it finishes loading data
1701
+ await act ( ( ) => user . click ( screen . getByText ( "Hide item" ) ) ) ;
1702
+
1703
+ expect ( screen . queryByText ( "Loading item..." ) ) . not . toBeInTheDocument ( ) ;
1704
+
1705
+ client . writeFragment ( {
1706
+ fragment,
1707
+ data : { __typename : "Item" , id : 1 , text : "Item #1" } ,
1708
+ } ) ;
1709
+
1710
+ // clear the microtask queue
1711
+ await act ( ( ) => Promise . resolve ( ) ) ;
1712
+
1713
+ expect ( cache [ "watches" ] . size ) . toBe ( 1 ) ;
1714
+
1715
+ jest . advanceTimersByTime ( 5000 ) ;
1716
+
1717
+ expect ( cache [ "watches" ] . size ) . toBe ( 0 ) ;
1718
+
1719
+ jest . useRealTimers ( ) ;
1720
+ } ) ;
1721
+
1722
+ test ( "cancels autoDisposeTimeoutMs if the component renders before timer finishes" , async ( ) => {
1723
+ jest . useFakeTimers ( ) ;
1724
+ interface ItemFragment {
1725
+ __typename : "Item" ;
1726
+ id : number ;
1727
+ text : string ;
1728
+ }
1729
+
1730
+ const fragment : TypedDocumentNode < ItemFragment > = gql `
1731
+ fragment ItemFragment on Item {
1732
+ id
1733
+ text
1734
+ }
1735
+ ` ;
1736
+
1737
+ const link = new MockSubscriptionLink ( ) ;
1738
+ const cache = new InMemoryCache ( ) ;
1739
+ const client = new ApolloClient ( { link, cache } ) ;
1740
+
1741
+ function App ( ) {
1742
+ return (
1743
+ < ApolloProvider client = { client } >
1744
+ < Suspense fallback = "Loading item..." >
1745
+ < Item />
1746
+ </ Suspense >
1747
+ </ ApolloProvider >
1748
+ ) ;
1749
+ }
1750
+
1751
+ function Item ( ) {
1752
+ const { data } = useSuspenseFragment ( {
1753
+ fragment,
1754
+ from : { __typename : "Item" , id : 1 } ,
1755
+ } ) ;
1756
+
1757
+ return < span > { data . text } </ span > ;
1758
+ }
1759
+
1760
+ await renderAsync ( < App /> ) ;
1761
+
1762
+ // Ensure <Greeting /> suspends immediately
1763
+ expect ( screen . getByText ( "Loading item..." ) ) . toBeInTheDocument ( ) ;
1764
+
1765
+ client . writeFragment ( {
1766
+ fragment,
1767
+ data : { __typename : "Item" , id : 1 , text : "Item #1" } ,
1768
+ } ) ;
1769
+
1770
+ // clear the microtask queue
1771
+ await act ( ( ) => Promise . resolve ( ) ) ;
1772
+
1773
+ await waitFor ( ( ) => {
1774
+ expect ( screen . getByText ( "Item #1" ) ) . toBeInTheDocument ( ) ;
1775
+ } ) ;
1776
+
1777
+ jest . advanceTimersByTime ( 30_000 ) ;
1778
+
1779
+ expect ( cache [ "watches" ] . size ) . toBe ( 1 ) ;
1780
+
1781
+ jest . useRealTimers ( ) ;
1782
+ } ) ;
1783
+
1569
1784
describe . skip ( "type tests" , ( ) => {
1570
1785
test ( "returns TData when from is a non-null value" , ( ) => {
1571
1786
const fragment : TypedDocumentNode < { foo : string } > = gql `` ;
0 commit comments