@@ -22,6 +22,9 @@ describe('useSubscription', () => {
2222 jest . resetModules ( ) ;
2323 jest . mock ( 'scheduler' , ( ) => require ( 'scheduler/unstable_mock' ) ) ;
2424
25+ const ReactFeatureFlags = require ( 'shared/ReactFeatureFlags' ) ;
26+ ReactFeatureFlags . debugRenderPhaseSideEffectsForStrictMode = false ;
27+
2528 useSubscription = require ( 'use-subscription' ) . useSubscription ;
2629 React = require ( 'react' ) ;
2730 ReactTestRenderer = require ( 'react-test-renderer' ) ;
@@ -560,4 +563,65 @@ describe('useSubscription', () => {
560563 act ( ( ) => renderer . update ( < Subscription subscription = { subscription2 } /> ) ) ;
561564 Scheduler . unstable_flushAll ( ) ;
562565 } ) ;
566+
567+ it ( 'should not tear if a mutation occurs during a concurrent update' , ( ) => {
568+ const input = document . createElement ( 'input' ) ;
569+
570+ const mutate = value => {
571+ input . value = value ;
572+ input . dispatchEvent ( new Event ( 'change' ) ) ;
573+ } ;
574+
575+ const subscription = {
576+ getCurrentValue : ( ) => input . value ,
577+ subscribe : callback => {
578+ input . addEventListener ( 'change' , callback ) ;
579+ return ( ) => input . removeEventListener ( 'change' , callback ) ;
580+ } ,
581+ } ;
582+
583+ const Subscriber = ( { id} ) => {
584+ const value = useSubscription ( subscription ) ;
585+ Scheduler . unstable_yieldValue ( `render:${ id } :${ value } ` ) ;
586+ return value ;
587+ } ;
588+
589+ act ( ( ) => {
590+ // Initial render of "A"
591+ mutate ( 'A' ) ;
592+ ReactTestRenderer . create (
593+ < React . Fragment >
594+ < Subscriber id = "first" />
595+ < Subscriber id = "second" />
596+ </ React . Fragment > ,
597+ { unstable_isConcurrent : true } ,
598+ ) ;
599+ expect ( Scheduler ) . toFlushAndYield ( [ 'render:first:A' , 'render:second:A' ] ) ;
600+
601+ // Update state "A" -> "B"
602+ // This update will be eagerly evaluated,
603+ // so the tearing case this test is guarding against would not happen.
604+ mutate ( 'B' ) ;
605+ expect ( Scheduler ) . toFlushAndYield ( [ 'render:first:B' , 'render:second:B' ] ) ;
606+
607+ // No more pending updates
608+ jest . runAllTimers ( ) ;
609+
610+ // Partial update "B" -> "C"
611+ // Interrupt with a second mutation "C" -> "D".
612+ // This update will not be eagerly evaluated,
613+ // but useSubscription() should eagerly close over the updated value to avoid tearing.
614+ mutate ( 'C' ) ;
615+ expect ( Scheduler ) . toFlushAndYieldThrough ( [ 'render:first:C' ] ) ;
616+ mutate ( 'D' ) ;
617+ expect ( Scheduler ) . toFlushAndYield ( [
618+ 'render:second:C' ,
619+ 'render:first:D' ,
620+ 'render:second:D' ,
621+ ] ) ;
622+
623+ // No more pending updates
624+ jest . runAllTimers ( ) ;
625+ } ) ;
626+ } ) ;
563627} ) ;
0 commit comments