@@ -1398,6 +1398,101 @@ describe('useMutableSource', () => {
13981398 expect ( root . getChildrenAsJSX ( ) ) . toEqual ( 'first: a1, second: a1' ) ;
13991399 } ) ;
14001400
1401+ // @gate experimental
1402+ it (
1403+ 'if source is mutated after initial read but before subscription is set ' +
1404+ 'up, should still entangle all pending mutations even if snapshot of ' +
1405+ 'new subscription happens to match' ,
1406+ async ( ) => {
1407+ const source = createSource ( {
1408+ a : 'a0' ,
1409+ b : 'b0' ,
1410+ } ) ;
1411+ const mutableSource = createMutableSource ( source ) ;
1412+
1413+ const getSnapshotA = ( ) => source . value . a ;
1414+ const getSnapshotB = ( ) => source . value . b ;
1415+
1416+ function mutateA ( newA ) {
1417+ source . value = {
1418+ ...source . value ,
1419+ a : newA ,
1420+ } ;
1421+ }
1422+
1423+ function mutateB ( newB ) {
1424+ source . value = {
1425+ ...source . value ,
1426+ b : newB ,
1427+ } ;
1428+ }
1429+
1430+ function Read ( { getSnapshot} ) {
1431+ const value = useMutableSource (
1432+ mutableSource ,
1433+ getSnapshot ,
1434+ defaultSubscribe ,
1435+ ) ;
1436+ Scheduler . unstable_yieldValue ( value ) ;
1437+ return value ;
1438+ }
1439+
1440+ function Text ( { text} ) {
1441+ Scheduler . unstable_yieldValue ( text ) ;
1442+ return text ;
1443+ }
1444+
1445+ const root = ReactNoop . createRoot ( ) ;
1446+ await act ( async ( ) => {
1447+ root . render (
1448+ < >
1449+ < Read getSnapshot = { getSnapshotA } />
1450+ </ > ,
1451+ ) ;
1452+ } ) ;
1453+ expect ( Scheduler ) . toHaveYielded ( [ 'a0' ] ) ;
1454+ expect ( root ) . toMatchRenderedOutput ( 'a0' ) ;
1455+
1456+ await act ( async ( ) => {
1457+ root . render (
1458+ < >
1459+ < Read getSnapshot = { getSnapshotA } />
1460+ < Read getSnapshot = { getSnapshotB } />
1461+ < Text text = "c" />
1462+ </ > ,
1463+ ) ;
1464+
1465+ expect ( Scheduler ) . toFlushAndYieldThrough ( [ 'a0' , 'b0' ] ) ;
1466+ // Mutate in an event. This schedules a subscription update on a, which
1467+ // already mounted, but not b, which hasn't subscribed yet.
1468+ mutateA ( 'a1' ) ;
1469+ mutateB ( 'b1' ) ;
1470+
1471+ // Mutate again at lower priority. This will schedule another subscription
1472+ // update on a, but not b. When b mounts and subscriptions, the value it
1473+ // read during render will happen to match the latest value. But it should
1474+ // still entangle the updates to prevent the previous update (a1) from
1475+ // rendering by itself.
1476+ Scheduler . unstable_runWithPriority (
1477+ Scheduler . unstable_IdlePriority ,
1478+ ( ) => {
1479+ mutateA ( 'a0' ) ;
1480+ mutateB ( 'b0' ) ;
1481+ } ,
1482+ ) ;
1483+ // Finish the current render
1484+ expect ( Scheduler ) . toFlushUntilNextPaint ( [ 'c' ] ) ;
1485+ // a0 will re-render because of the mutation update. But it should show
1486+ // the latest value, not the intermediate one, to avoid tearing with b.
1487+ expect ( Scheduler ) . toFlushUntilNextPaint ( [ 'a0' ] ) ;
1488+ expect ( root ) . toMatchRenderedOutput ( 'a0b0c' ) ;
1489+ // We should be done.
1490+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
1491+ expect ( root ) . toMatchRenderedOutput ( 'a0b0c' ) ;
1492+ } ) ;
1493+ } ,
1494+ ) ;
1495+
14011496 // @gate experimental
14021497 it ( 'getSnapshot changes and then source is mutated during interleaved event' , async ( ) => {
14031498 const { useEffect} = React ;
0 commit comments