@@ -491,5 +491,191 @@ describe('ReactDOMFiberAsync', () => {
491491 expect ( container . textContent ) . toEqual ( '1' ) ;
492492 expect ( returnValue ) . toBe ( undefined ) ;
493493 } ) ;
494+
495+ it ( 'ignores discrete events on a pending removed element' , ( ) => {
496+ const disableButtonRef = React . createRef ( ) ;
497+ const submitButtonRef = React . createRef ( ) ;
498+
499+ let formSubmitted = false ;
500+
501+ class Form extends React . Component {
502+ state = { active : true } ;
503+ disableForm = ( ) => {
504+ this . setState ( { active : false } ) ;
505+ } ;
506+ submitForm = ( ) => {
507+ formSubmitted = true ; // This should not get invoked
508+ } ;
509+ render ( ) {
510+ return (
511+ < div >
512+ < button onClick = { this . disableForm } ref = { disableButtonRef } >
513+ Disable
514+ </ button >
515+ { this . state . active ? (
516+ < button onClick = { this . submitForm } ref = { submitButtonRef } >
517+ Submit
518+ </ button >
519+ ) : null }
520+ </ div >
521+ ) ;
522+ }
523+ }
524+
525+ const root = ReactDOM . unstable_createRoot ( container ) ;
526+ root . render ( < Form /> ) ;
527+ // Flush
528+ jest . runAllTimers ( ) ;
529+
530+ let disableButton = disableButtonRef . current ;
531+ expect ( disableButton . tagName ) . toBe ( 'BUTTON' ) ;
532+
533+ // Dispatch a click event on the Disable-button.
534+ let firstEvent = document . createEvent ( 'Event' ) ;
535+ firstEvent . initEvent ( 'click' , true , true ) ;
536+ disableButton . dispatchEvent ( firstEvent ) ;
537+
538+ // There should now be a pending update to disable the form.
539+
540+ // This should not have flushed yet since it's in concurrent mode.
541+ let submitButton = submitButtonRef . current ;
542+ expect ( submitButton . tagName ) . toBe ( 'BUTTON' ) ;
543+
544+ // In the meantime, we can dispatch a new client event on the submit button.
545+ let secondEvent = document . createEvent ( 'Event' ) ;
546+ secondEvent . initEvent ( 'click' , true , true ) ;
547+ // This should force the pending update to flush which disables the submit button before the event is invoked.
548+ submitButton . dispatchEvent ( secondEvent ) ;
549+
550+ // Therefore the form should never have been submitted.
551+ expect ( formSubmitted ) . toBe ( false ) ;
552+
553+ expect ( submitButtonRef . current ) . toBe ( null ) ;
554+ } ) ;
555+
556+ it ( 'ignores discrete events on a pending removed event listener' , ( ) => {
557+ const disableButtonRef = React . createRef ( ) ;
558+ const submitButtonRef = React . createRef ( ) ;
559+
560+ let formSubmitted = false ;
561+
562+ class Form extends React . Component {
563+ state = { active : true } ;
564+ disableForm = ( ) => {
565+ this . setState ( { active : false } ) ;
566+ } ;
567+ submitForm = ( ) => {
568+ formSubmitted = true ; // This should not get invoked
569+ } ;
570+ disabledSubmitForm = ( ) => {
571+ // The form is disabled.
572+ } ;
573+ render ( ) {
574+ return (
575+ < div >
576+ < button onClick = { this . disableForm } ref = { disableButtonRef } >
577+ Disable
578+ </ button >
579+ < button
580+ onClick = {
581+ this . state . active ? this . submitForm : this . disabledSubmitForm
582+ }
583+ ref = { submitButtonRef } >
584+ Submit
585+ </ button > { ' ' }
586+ : null}
587+ </ div >
588+ ) ;
589+ }
590+ }
591+
592+ const root = ReactDOM . unstable_createRoot ( container ) ;
593+ root . render ( < Form /> ) ;
594+ // Flush
595+ jest . runAllTimers ( ) ;
596+
597+ let disableButton = disableButtonRef . current ;
598+ expect ( disableButton . tagName ) . toBe ( 'BUTTON' ) ;
599+
600+ // Dispatch a click event on the Disable-button.
601+ let firstEvent = document . createEvent ( 'Event' ) ;
602+ firstEvent . initEvent ( 'click' , true , true ) ;
603+ disableButton . dispatchEvent ( firstEvent ) ;
604+
605+ // There should now be a pending update to disable the form.
606+
607+ // This should not have flushed yet since it's in concurrent mode.
608+ let submitButton = submitButtonRef . current ;
609+ expect ( submitButton . tagName ) . toBe ( 'BUTTON' ) ;
610+
611+ // In the meantime, we can dispatch a new client event on the submit button.
612+ let secondEvent = document . createEvent ( 'Event' ) ;
613+ secondEvent . initEvent ( 'click' , true , true ) ;
614+ // This should force the pending update to flush which disables the submit button before the event is invoked.
615+ submitButton . dispatchEvent ( secondEvent ) ;
616+
617+ // Therefore the form should never have been submitted.
618+ expect ( formSubmitted ) . toBe ( false ) ;
619+ } ) ;
620+
621+ it ( 'uses the newest discrete events on a pending changed event listener' , ( ) => {
622+ const enableButtonRef = React . createRef ( ) ;
623+ const submitButtonRef = React . createRef ( ) ;
624+
625+ let formSubmitted = false ;
626+
627+ class Form extends React . Component {
628+ state = { active : false } ;
629+ enableForm = ( ) => {
630+ this . setState ( { active : true } ) ;
631+ } ;
632+ submitForm = ( ) => {
633+ formSubmitted = true ; // This should happen
634+ } ;
635+ render ( ) {
636+ return (
637+ < div >
638+ < button onClick = { this . enableForm } ref = { enableButtonRef } >
639+ Enable
640+ </ button >
641+ < button
642+ onClick = { this . state . active ? this . submitForm : null }
643+ ref = { submitButtonRef } >
644+ Submit
645+ </ button > { ' ' }
646+ : null}
647+ </ div >
648+ ) ;
649+ }
650+ }
651+
652+ const root = ReactDOM . unstable_createRoot ( container ) ;
653+ root . render ( < Form /> ) ;
654+ // Flush
655+ jest . runAllTimers ( ) ;
656+
657+ let enableButton = enableButtonRef . current ;
658+ expect ( enableButton . tagName ) . toBe ( 'BUTTON' ) ;
659+
660+ // Dispatch a click event on the Enable-button.
661+ let firstEvent = document . createEvent ( 'Event' ) ;
662+ firstEvent . initEvent ( 'click' , true , true ) ;
663+ enableButton . dispatchEvent ( firstEvent ) ;
664+
665+ // There should now be a pending update to enable the form.
666+
667+ // This should not have flushed yet since it's in concurrent mode.
668+ let submitButton = submitButtonRef . current ;
669+ expect ( submitButton . tagName ) . toBe ( 'BUTTON' ) ;
670+
671+ // In the meantime, we can dispatch a new client event on the submit button.
672+ let secondEvent = document . createEvent ( 'Event' ) ;
673+ secondEvent . initEvent ( 'click' , true , true ) ;
674+ // This should force the pending update to flush which enables the submit button before the event is invoked.
675+ submitButton . dispatchEvent ( secondEvent ) ;
676+
677+ // Therefore the form should have been submitted.
678+ expect ( formSubmitted ) . toBe ( true ) ;
679+ } ) ;
494680 } ) ;
495681} ) ;
0 commit comments