@@ -25,6 +25,7 @@ describe('ReactDOMServerPartialHydration', () => {
2525 ReactFeatureFlags = require ( 'shared/ReactFeatureFlags' ) ;
2626 ReactFeatureFlags . enableSuspenseServerRenderer = true ;
2727 ReactFeatureFlags . enableSuspenseCallback = true ;
28+ ReactFeatureFlags . enableFlareAPI = true ;
2829
2930 React = require ( 'react' ) ;
3031 ReactDOM = require ( 'react-dom' ) ;
@@ -1729,4 +1730,169 @@ describe('ReactDOMServerPartialHydration', () => {
17291730 // patched up the tree, which might mean we haven't patched the className.
17301731 expect ( newSpan . className ) . toBe ( 'hi' ) ;
17311732 } ) ;
1733+
1734+ it ( 'does not invoke an event on a hydrated node until it commits' , async ( ) => {
1735+ let suspend = false ;
1736+ let resolve ;
1737+ let promise = new Promise ( resolvePromise => ( resolve = resolvePromise ) ) ;
1738+
1739+ function Sibling ( { text} ) {
1740+ if ( suspend ) {
1741+ throw promise ;
1742+ } else {
1743+ return 'Hello' ;
1744+ }
1745+ }
1746+
1747+ let clicks = 0 ;
1748+
1749+ function Button ( ) {
1750+ let [ clicked , setClicked ] = React . useState ( false ) ;
1751+ if ( clicked ) {
1752+ return null ;
1753+ }
1754+ return (
1755+ < a
1756+ onClick = { ( ) => {
1757+ setClicked ( true ) ;
1758+ clicks ++ ;
1759+ } } >
1760+ Click me
1761+ </ a >
1762+ ) ;
1763+ }
1764+
1765+ function App ( ) {
1766+ return (
1767+ < div >
1768+ < Suspense fallback = "Loading..." >
1769+ < Button />
1770+ < Sibling />
1771+ </ Suspense >
1772+ </ div >
1773+ ) ;
1774+ }
1775+
1776+ suspend = false ;
1777+ let finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
1778+ let container = document . createElement ( 'div' ) ;
1779+ container . innerHTML = finalHTML ;
1780+
1781+ // We need this to be in the document since we'll dispatch events on it.
1782+ document . body . appendChild ( container ) ;
1783+
1784+ let a = container . getElementsByTagName ( 'a' ) [ 0 ] ;
1785+
1786+ // On the client we don't have all data yet but we want to start
1787+ // hydrating anyway.
1788+ suspend = true ;
1789+ let root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
1790+ root . render ( < App /> ) ;
1791+ Scheduler . unstable_flushAll ( ) ;
1792+ jest . runAllTimers ( ) ;
1793+
1794+ expect ( container . textContent ) . toBe ( 'Click meHello' ) ;
1795+
1796+ // We're now partially hydrated.
1797+ a . click ( ) ;
1798+ expect ( clicks ) . toBe ( 0 ) ;
1799+
1800+ // Resolving the promise so that rendering can complete.
1801+ suspend = false ;
1802+ resolve ( ) ;
1803+ await promise ;
1804+
1805+ Scheduler . unstable_flushAll ( ) ;
1806+ jest . runAllTimers ( ) ;
1807+
1808+ // TODO: With selective hydration the event should've been replayed
1809+ // but for now we'll have to issue it again.
1810+ act ( ( ) => {
1811+ a . click ( ) ;
1812+ } ) ;
1813+
1814+ expect ( clicks ) . toBe ( 1 ) ;
1815+
1816+ expect ( container . textContent ) . toBe ( 'Hello' ) ;
1817+
1818+ document . body . removeChild ( container ) ;
1819+ } ) ;
1820+
1821+ it ( 'does not invoke an event on a hydrated EventResponder until it commits' , async ( ) => {
1822+ let suspend = false ;
1823+ let resolve ;
1824+ let promise = new Promise ( resolvePromise => ( resolve = resolvePromise ) ) ;
1825+
1826+ function Sibling ( { text} ) {
1827+ if ( suspend ) {
1828+ throw promise ;
1829+ } else {
1830+ return 'Hello' ;
1831+ }
1832+ }
1833+
1834+ const onEvent = jest . fn ( ) ;
1835+ const TestResponder = React . unstable_createResponder ( 'TestEventResponder' , {
1836+ targetEventTypes : [ 'click' ] ,
1837+ onEvent,
1838+ } ) ;
1839+
1840+ function Button ( ) {
1841+ let listener = React . unstable_useResponder ( TestResponder , { } ) ;
1842+ return < a listeners = { listener } > Click me</ a > ;
1843+ }
1844+
1845+ function App ( ) {
1846+ return (
1847+ < div >
1848+ < Suspense fallback = "Loading..." >
1849+ < Button />
1850+ < Sibling />
1851+ </ Suspense >
1852+ </ div >
1853+ ) ;
1854+ }
1855+
1856+ suspend = false ;
1857+ let finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
1858+ let container = document . createElement ( 'div' ) ;
1859+ container . innerHTML = finalHTML ;
1860+
1861+ // We need this to be in the document since we'll dispatch events on it.
1862+ document . body . appendChild ( container ) ;
1863+
1864+ let a = container . getElementsByTagName ( 'a' ) [ 0 ] ;
1865+
1866+ // On the client we don't have all data yet but we want to start
1867+ // hydrating anyway.
1868+ suspend = true ;
1869+ let root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
1870+ root . render ( < App /> ) ;
1871+ Scheduler . unstable_flushAll ( ) ;
1872+ jest . runAllTimers ( ) ;
1873+
1874+ // We're now partially hydrated.
1875+ a . click ( ) ;
1876+ // We should not have invoked the event yet because we're not
1877+ // yet hydrated.
1878+ expect ( onEvent ) . toHaveBeenCalledTimes ( 0 ) ;
1879+
1880+ // Resolving the promise so that rendering can complete.
1881+ suspend = false ;
1882+ resolve ( ) ;
1883+ await promise ;
1884+
1885+ Scheduler . unstable_flushAll ( ) ;
1886+ jest . runAllTimers ( ) ;
1887+
1888+ // TODO: With selective hydration the event should've been replayed
1889+ // but for now we'll have to issue it again.
1890+ act ( ( ) => {
1891+ a . click ( ) ;
1892+ } ) ;
1893+
1894+ expect ( onEvent ) . toHaveBeenCalledTimes ( 1 ) ;
1895+
1896+ document . body . removeChild ( container ) ;
1897+ } ) ;
17321898} ) ;
0 commit comments