@@ -2424,6 +2424,60 @@ describe('DOMPluginEventSystem', () => {
24242424 expect ( log ) . toEqual ( [ 'beforeblur' , 'afterblur' ] ) ;
24252425 } ) ;
24262426
2427+ // @gate experimental
2428+ it ( 'beforeblur should skip handlers from a deleted subtree after the focused element is unmounted' , ( ) => {
2429+ const onBeforeBlur = jest . fn ( ) ;
2430+ const innerRef = React . createRef ( ) ;
2431+ const innerRef2 = React . createRef ( ) ;
2432+ const setBeforeBlurHandle = ReactDOM . unstable_createEventHandle (
2433+ 'beforeblur' ,
2434+ ) ;
2435+ const ref2 = React . createRef ( ) ;
2436+
2437+ const Component = ( { show} ) => {
2438+ const ref = React . useRef ( null ) ;
2439+
2440+ React . useEffect ( ( ) => {
2441+ const clear1 = setBeforeBlurHandle ( ref . current , onBeforeBlur ) ;
2442+ let clear2 ;
2443+ if ( ref2 . current ) {
2444+ clear2 = setBeforeBlurHandle ( ref2 . current , onBeforeBlur ) ;
2445+ }
2446+
2447+ return ( ) => {
2448+ clear1 ( ) ;
2449+ if ( clear2 ) {
2450+ clear2 ( ) ;
2451+ }
2452+ } ;
2453+ } ) ;
2454+
2455+ return (
2456+ < div ref = { ref } >
2457+ { show && (
2458+ < div ref = { ref2 } >
2459+ < input ref = { innerRef } />
2460+ </ div >
2461+ ) }
2462+ < div ref = { innerRef2 } />
2463+ </ div >
2464+ ) ;
2465+ } ;
2466+
2467+ ReactDOM . render ( < Component show = { true } /> , container ) ;
2468+ Scheduler . unstable_flushAll ( ) ;
2469+
2470+ const inner = innerRef . current ;
2471+ const target = createEventTarget ( inner ) ;
2472+ target . focus ( ) ;
2473+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 0 ) ;
2474+
2475+ ReactDOM . render ( < Component show = { false } /> , container ) ;
2476+ Scheduler . unstable_flushAll ( ) ;
2477+
2478+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 1 ) ;
2479+ } ) ;
2480+
24272481 // @gate experimental
24282482 it ( 'beforeblur and afterblur are called after a focused element is suspended' , ( ) => {
24292483 const log = [ ] ;
@@ -2510,6 +2564,87 @@ describe('DOMPluginEventSystem', () => {
25102564 document . body . removeChild ( container2 ) ;
25112565 } ) ;
25122566
2567+ // @gate experimental
2568+ it ( 'beforeblur should skip handlers from a deleted subtree after the focused element is suspended' , ( ) => {
2569+ const onBeforeBlur = jest . fn ( ) ;
2570+ const innerRef = React . createRef ( ) ;
2571+ const innerRef2 = React . createRef ( ) ;
2572+ const setBeforeBlurHandle = ReactDOM . unstable_createEventHandle (
2573+ 'beforeblur' ,
2574+ ) ;
2575+ const ref2 = React . createRef ( ) ;
2576+ const Suspense = React . Suspense ;
2577+ let suspend = false ;
2578+ let resolve ;
2579+ const promise = new Promise (
2580+ resolvePromise => ( resolve = resolvePromise ) ,
2581+ ) ;
2582+
2583+ function Child ( ) {
2584+ if ( suspend ) {
2585+ throw promise ;
2586+ } else {
2587+ return < input ref = { innerRef } /> ;
2588+ }
2589+ }
2590+
2591+ const Component = ( ) => {
2592+ const ref = React . useRef ( null ) ;
2593+
2594+ React . useEffect ( ( ) => {
2595+ const clear1 = setBeforeBlurHandle ( ref . current , onBeforeBlur ) ;
2596+ let clear2 ;
2597+ if ( ref2 . current ) {
2598+ clear2 = setBeforeBlurHandle ( ref2 . current , onBeforeBlur ) ;
2599+ }
2600+
2601+ return ( ) => {
2602+ clear1 ( ) ;
2603+ if ( clear2 ) {
2604+ clear2 ( ) ;
2605+ }
2606+ } ;
2607+ } ) ;
2608+
2609+ return (
2610+ < div ref = { ref } >
2611+ < Suspense fallback = "Loading..." >
2612+ < div ref = { ref2 } >
2613+ < Child />
2614+ </ div >
2615+ </ Suspense >
2616+ < div ref = { innerRef2 } />
2617+ </ div >
2618+ ) ;
2619+ } ;
2620+
2621+ const container2 = document . createElement ( 'div' ) ;
2622+ document . body . appendChild ( container2 ) ;
2623+
2624+ const root = ReactDOM . createRoot ( container2 ) ;
2625+
2626+ act ( ( ) => {
2627+ root . render ( < Component /> ) ;
2628+ } ) ;
2629+ jest . runAllTimers ( ) ;
2630+
2631+ const inner = innerRef . current ;
2632+ const target = createEventTarget ( inner ) ;
2633+ target . focus ( ) ;
2634+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 0 ) ;
2635+
2636+ suspend = true ;
2637+ act ( ( ) => {
2638+ root . render ( < Component /> ) ;
2639+ } ) ;
2640+ jest . runAllTimers ( ) ;
2641+
2642+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 1 ) ;
2643+ resolve ( ) ;
2644+
2645+ document . body . removeChild ( container2 ) ;
2646+ } ) ;
2647+
25132648 // @gate experimental
25142649 it ( 'regression: does not fire beforeblur/afterblur if target is already hidden' , ( ) => {
25152650 const Suspense = React . Suspense ;
0 commit comments