1836
});
1837
});
1838
});
1839
+
1840
+ describe('scrollIntoView', () => {
1841
+ function expectLast(arr, test) {
1842
+ expect(arr[arr.length - 1]).toBe(test);
1843
+ }
1844
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
1845
+ it('does not yet support options', async () => {
1846
+ const fragmentRef = React.createRef();
1847
+ const root = ReactDOMClient.createRoot(container);
1848
+ await act(() => {
1849
+ root.render(<Fragment ref={fragmentRef} />);
1850
+ });
1851
+
1852
+ expect(() => {
1853
+ fragmentRef.current.experimental_scrollIntoView({block: 'start'});
1854
+ }).toThrowError(
1855
+ 'FragmentInstance.experimental_scrollIntoView() does not support ' +
1856
+ 'scrollIntoViewOptions. Use the alignToTop boolean instead.',
1857
+ );
1858
+ });
1859
+
1860
+ describe('with children', () => {
1861
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
1862
+ it('settles scroll on the first child by default, or if alignToTop=true', async () => {
1863
+ const fragmentRef = React.createRef();
1864
+ const childARef = React.createRef();
1865
+ const childBRef = React.createRef();
1866
+ const root = ReactDOMClient.createRoot(container);
1867
+ await act(() => {
1868
+ root.render(
1869
+ <React.Fragment ref={fragmentRef}>
1870
+ <div ref={childARef} id="a">
1871
+ A
1872
+ </div>
1873
+ <div ref={childBRef} id="b">
1874
+ B
1875
+ </div>
1876
+ </React.Fragment>,
1877
+ );
1878
+ });
1879
+
1880
+ let logs = [];
1881
+ childARef.current.scrollIntoView = jest.fn().mockImplementation(() => {
1882
+ logs.push('childA');
1883
+ });
1884
+ childBRef.current.scrollIntoView = jest.fn().mockImplementation(() => {
1885
+ logs.push('childB');
1886
+ });
1887
+
1888
+ // Default call
1889
+ fragmentRef.current.experimental_scrollIntoView();
1890
+ expectLast(logs, 'childA');
1891
+ logs = [];
1892
+ // alignToTop=true
1893
+ fragmentRef.current.experimental_scrollIntoView(true);
1894
+ expectLast(logs, 'childA');
1895
+ });
1896
+
1897
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
1898
+ it('calls scrollIntoView on the last child if alignToTop is false', async () => {
1899
+ const fragmentRef = React.createRef();
1900
+ const childARef = React.createRef();
1901
+ const childBRef = React.createRef();
1902
+ const root = ReactDOMClient.createRoot(container);
1903
+ await act(() => {
1904
+ root.render(
1905
+ <Fragment ref={fragmentRef}>
1906
+ <div ref={childARef}>A</div>
1907
+ <div ref={childBRef}>B</div>
1908
+ </Fragment>,
1909
+ );
1910
+ });
1911
+
1912
+ const logs = [];
1913
+ childARef.current.scrollIntoView = jest.fn().mockImplementation(() => {
1914
+ logs.push('childA');
1915
+ });
1916
+ childBRef.current.scrollIntoView = jest.fn().mockImplementation(() => {
1917
+ logs.push('childB');
1918
+ });
1919
+
1920
+ fragmentRef.current.experimental_scrollIntoView(false);
1921
+ expectLast(logs, 'childB');
1922
+ });
1923
+
1924
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
1925
+ it('handles portaled elements -- same scroll container', async () => {
1926
+ const fragmentRef = React.createRef();
1927
+ const childARef = React.createRef();
1928
+ const childBRef = React.createRef();
1929
+ const root = ReactDOMClient.createRoot(container);
1930
+
1931
+ function Test() {
1932
+ return (
1933
+ <Fragment ref={fragmentRef}>
1934
+ {createPortal(
1935
+ <div ref={childARef} id="child-a">
1936
+ A
1937
+ </div>,
1938
+ document.body,
1939
+ )}
1940
+
1941
+ <div ref={childBRef} id="child-b">
1942
+ B
1943
+ </div>
1944
+ </Fragment>
1945
+ );
1946
+ }
1947
+
1948
+ await act(() => {
1949
+ root.render(<Test />);
1950
+ });
1951
+
1952
+ const logs = [];
1953
+ childARef.current.scrollIntoView = jest.fn().mockImplementation(() => {
1954
+ logs.push('childA');
1955
+ });
1956
+ childBRef.current.scrollIntoView = jest.fn().mockImplementation(() => {
1957
+ logs.push('childB');
1958
+ });
1959
+
1960
+ // Default call
1961
+ fragmentRef.current.experimental_scrollIntoView();
1962
+ expectLast(logs, 'childA');
1963
+ });
1964
+
1965
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
1966
+ it('handles portaled elements -- different scroll container', async () => {
1967
+ const fragmentRef = React.createRef();
1968
+ const headerChildRef = React.createRef();
1969
+ const childARef = React.createRef();
1970
+ const childBRef = React.createRef();
1971
+ const childCRef = React.createRef();
1972
+ const scrollContainerRef = React.createRef();
1973
+ const scrollContainerNestedRef = React.createRef();
1974
+ const root = ReactDOMClient.createRoot(container);
1975
+
1976
+ function Test({mountFragment}) {
1977
+ return (
1978
+ <>
1979
+ <div id="header" style={{position: 'fixed'}}>
1980
+ <div id="parent-a" />
1981
+ </div>
1982
+ <div id="parent-b" />
1983
+ <div
1984
+ id="scroll-container"
1985
+ ref={scrollContainerRef}
1986
+ style={{overflow: 'scroll'}}>
1987
+ <div id="parent-c" />
1988
+ <div
1989
+ id="scroll-container-nested"
1990
+ ref={scrollContainerNestedRef}
1991
+ style={{overflow: 'scroll'}}>
1992
+ <div id="parent-d" />
1993
+ </div>
1994
+ </div>
1995
+ {mountFragment && (
1996
+ <Fragment ref={fragmentRef}>
1997
+ {createPortal(
1998
+ <div ref={headerChildRef} id="header-content">
1999
+ Header
2000
+ </div>,
2001
+ document.querySelector('#parent-a'),
2002
+ )}
2003
+ {createPortal(
2004
+ <div ref={childARef} id="child-a">
2005
+ A
2006
+ </div>,
2007
+ document.querySelector('#parent-b'),
2008
+ )}
2009
+ {createPortal(
2010
+ <div ref={childBRef} id="child-b">
2011
+ B
2012
+ </div>,
2013
+ document.querySelector('#parent-b'),
2014
+ )}
2015
+ {createPortal(
2016
+ <div ref={childCRef} id="child-c">
2017
+ C
2018
+ </div>,
2019
+ document.querySelector('#parent-c'),
2020
+ )}
2021
+ </Fragment>
2022
+ )}
2023
+ </>
2024
+ );
2025
+ }
2026
+
2027
+ await act(() => {
2028
+ root.render(<Test mountFragment={false} />);
2029
+ });
2030
+ // Now that the portal locations exist, mount the fragment
2031
+ await act(() => {
2032
+ root.render(<Test mountFragment={true} />);
2033
+ });
2034
+
2035
+ let logs = [];
2036
+ headerChildRef.current.scrollIntoView = jest.fn(() => {
2037
+ logs.push('header');
2038
+ });
2039
+ childARef.current.scrollIntoView = jest.fn(() => {
2040
+ logs.push('A');
2041
+ });
2042
+ childBRef.current.scrollIntoView = jest.fn(() => {
2043
+ logs.push('B');
2044
+ });
2045
+ childCRef.current.scrollIntoView = jest.fn(() => {
2046
+ logs.push('C');
2047
+ });
2048
+
2049
+ // Default call
2050
+ fragmentRef.current.experimental_scrollIntoView();
2051
+ expectLast(logs, 'header');
2052
+
2053
+ childARef.current.scrollIntoView.mockClear();
2054
+ childBRef.current.scrollIntoView.mockClear();
2055
+ childCRef.current.scrollIntoView.mockClear();
2056
+
2057
+ logs = [];
2058
+
2059
+ // // alignToTop=false
2060
+ fragmentRef.current.experimental_scrollIntoView(false);
2061
+ expectLast(logs, 'C');
2062
+ });
2063
+ });
2064
+
2065
+ describe('without children', () => {
2066
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
2067
+ it('calls scrollIntoView on the next sibling by default, or if alignToTop=true', async () => {
2068
+ const fragmentRef = React.createRef();
2069
+ const siblingARef = React.createRef();
2070
+ const siblingBRef = React.createRef();
2071
+ const root = ReactDOMClient.createRoot(container);
2072
+ await act(() => {
2073
+ root.render(
2074
+ <div>
2075
+ <Wrapper>
2076
+ <div ref={siblingARef} />
2077
+ </Wrapper>
2078
+ <Fragment ref={fragmentRef} />
2079
+ <div ref={siblingBRef} />
2080
+ </div>,
2081
+ );
2082
+ });
2083
+
2084
+ siblingARef.current.scrollIntoView = jest.fn();
2085
+ siblingBRef.current.scrollIntoView = jest.fn();
2086
+
2087
+ // Default call
2088
+ fragmentRef.current.experimental_scrollIntoView();
2089
+ expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
2090
+ expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
2091
+
2092
+ siblingBRef.current.scrollIntoView.mockClear();
2093
+
2094
+ // alignToTop=true
2095
+ fragmentRef.current.experimental_scrollIntoView(true);
2096
+ expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(0);
2097
+ expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
2098
+ });
2099
+
2100
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
2101
+ it('calls scrollIntoView on the prev sibling if alignToTop is false', async () => {
2102
+ const fragmentRef = React.createRef();
2103
+ const siblingARef = React.createRef();
2104
+ const siblingBRef = React.createRef();
2105
+ const root = ReactDOMClient.createRoot(container);
2106
+ function C() {
2107
+ return (
2108
+ <Wrapper>
2109
+ <div id="C" ref={siblingARef} />
2110
+ </Wrapper>
2111
+ );
2112
+ }
2113
+ function Test() {
2114
+ return (
2115
+ <div id="A">
2116
+ <div id="B" />
2117
+ <C />
2118
+ <Fragment ref={fragmentRef} />
2119
+ <div id="D" ref={siblingBRef} />
2120
+ <div id="E" />
2121
+ </div>
2122
+ );
2123
+ }
2124
+ await act(() => {
2125
+ root.render(<Test />);
2126
+ });
2127
+
2128
+ siblingARef.current.scrollIntoView = jest.fn();
2129
+ siblingBRef.current.scrollIntoView = jest.fn();
2130
+
2131
+ // alignToTop=false
2132
+ fragmentRef.current.experimental_scrollIntoView(false);
2133
+ expect(siblingARef.current.scrollIntoView).toHaveBeenCalledTimes(1);
2134
+ expect(siblingBRef.current.scrollIntoView).toHaveBeenCalledTimes(0);
2135
+ });
2136
+
2137
+ // @gate enableFragmentRefs && enableFragmentRefsScrollIntoView
2138
+ it('calls scrollIntoView on the parent if there are no siblings', async () => {
2139
+ const fragmentRef = React.createRef();
2140
+ const parentRef = React.createRef();
2141
+ const root = ReactDOMClient.createRoot(container);
2142
+ await act(() => {
2143
+ root.render(
2144
+ <div ref={parentRef}>
2145
+ <Wrapper>
2146
+ <Fragment ref={fragmentRef} />
2147
+ </Wrapper>
2148
+ </div>,
2149
+ );
2150
+ });
2151
+
2152
+ parentRef.current.scrollIntoView = jest.fn();
2153
+ fragmentRef.current.experimental_scrollIntoView();
2154
+ expect(parentRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
2155
+ });
2156
+ });
2157
+ });
2158
});