diff --git a/packages/legacy-events/EventPluginHub.js b/packages/legacy-events/EventPluginHub.js index 297d0cbdcc44..1ab4bc3fa456 100644 --- a/packages/legacy-events/EventPluginHub.js +++ b/packages/legacy-events/EventPluginHub.js @@ -22,6 +22,7 @@ import type {ReactSyntheticEvent} from './ReactSyntheticEventType'; import type {Fiber} from 'react-reconciler/src/ReactFiber'; import type {AnyNativeEvent} from './PluginModuleType'; import type {TopLevelType} from './TopLevelEventTypes'; +import type {EventSystemFlags} from 'legacy-events/EventSystemFlags'; function isInteractive(tag) { return ( @@ -131,6 +132,7 @@ export function getListener(inst: Fiber, registrationName: string) { */ function extractPluginEvents( topLevelType: TopLevelType, + eventSystemFlags: EventSystemFlags, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, @@ -142,6 +144,7 @@ function extractPluginEvents( if (possiblePlugin) { const extractedEvents = possiblePlugin.extractEvents( topLevelType, + eventSystemFlags, targetInst, nativeEvent, nativeEventTarget, @@ -156,12 +159,14 @@ function extractPluginEvents( export function runExtractedPluginEventsInBatch( topLevelType: TopLevelType, + eventSystemFlags: EventSystemFlags, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, ) { const events = extractPluginEvents( topLevelType, + eventSystemFlags, targetInst, nativeEvent, nativeEventTarget, diff --git a/packages/legacy-events/EventSystemFlags.js b/packages/legacy-events/EventSystemFlags.js index be5e3544a2ea..1ad8aa01c251 100644 --- a/packages/legacy-events/EventSystemFlags.js +++ b/packages/legacy-events/EventSystemFlags.js @@ -14,3 +14,4 @@ export const RESPONDER_EVENT_SYSTEM = 1 << 1; export const IS_PASSIVE = 1 << 2; export const IS_ACTIVE = 1 << 3; export const PASSIVE_NOT_SUPPORTED = 1 << 4; +export const IS_REPLAYED = 1 << 5; diff --git a/packages/legacy-events/PluginModuleType.js b/packages/legacy-events/PluginModuleType.js index cd7a07661ab4..c1cf5fc6783e 100644 --- a/packages/legacy-events/PluginModuleType.js +++ b/packages/legacy-events/PluginModuleType.js @@ -13,6 +13,7 @@ import type { ReactSyntheticEvent, } from './ReactSyntheticEventType'; import type {TopLevelType} from './TopLevelEventTypes'; +import type {EventSystemFlags} from 'legacy-events/EventSystemFlags'; export type EventTypes = {[key: string]: DispatchConfig}; @@ -24,6 +25,7 @@ export type PluginModule = { eventTypes: EventTypes, extractEvents: ( topLevelType: TopLevelType, + eventSystemFlags: EventSystemFlags, targetInst: null | Fiber, nativeTarget: NativeEvent, nativeEventTarget: EventTarget, diff --git a/packages/legacy-events/ResponderEventPlugin.js b/packages/legacy-events/ResponderEventPlugin.js index 235e68829947..aea49578a397 100644 --- a/packages/legacy-events/ResponderEventPlugin.js +++ b/packages/legacy-events/ResponderEventPlugin.js @@ -504,6 +504,7 @@ const ResponderEventPlugin = { */ extractEvents: function( topLevelType, + eventSystemFlags, targetInst, nativeEvent, nativeEventTarget, diff --git a/packages/legacy-events/__tests__/ResponderEventPlugin-test.internal.js b/packages/legacy-events/__tests__/ResponderEventPlugin-test.internal.js index c1959659a1db..c235578b2875 100644 --- a/packages/legacy-events/__tests__/ResponderEventPlugin-test.internal.js +++ b/packages/legacy-events/__tests__/ResponderEventPlugin-test.internal.js @@ -10,6 +10,7 @@ 'use strict'; const {HostComponent} = require('shared/ReactWorkTags'); +const {PLUGIN_EVENT_SYSTEM} = require('legacy-events/EventSystemFlags'); let EventBatching; let EventPluginUtils; @@ -313,6 +314,7 @@ const run = function(config, hierarchyConfig, nativeEventConfig) { // Trigger the event const extractedEvents = ResponderEventPlugin.extractEvents( nativeEventConfig.topLevelType, + PLUGIN_EVENT_SYSTEM, nativeEventConfig.targetInst, nativeEventConfig.nativeEvent, nativeEventConfig.target, diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index f4e465ea4d39..dffe284c69b2 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -17,6 +17,58 @@ let ReactFeatureFlags; let Suspense; let SuspenseList; let act; +let useHover; + +function dispatchMouseEvent(to, from) { + if (!to) { + to = null; + } + if (!from) { + from = null; + } + if (from) { + const mouseOutEvent = document.createEvent('MouseEvents'); + mouseOutEvent.initMouseEvent( + 'mouseout', + true, + true, + window, + 0, + 50, + 50, + 50, + 50, + false, + false, + false, + false, + 0, + to, + ); + from.dispatchEvent(mouseOutEvent); + } + if (to) { + const mouseOverEvent = document.createEvent('MouseEvents'); + mouseOverEvent.initMouseEvent( + 'mouseover', + true, + true, + window, + 0, + 50, + 50, + 50, + 50, + false, + false, + false, + false, + 0, + from, + ); + to.dispatchEvent(mouseOverEvent); + } +} describe('ReactDOMServerPartialHydration', () => { beforeEach(() => { @@ -34,6 +86,8 @@ describe('ReactDOMServerPartialHydration', () => { Scheduler = require('scheduler'); Suspense = React.Suspense; SuspenseList = React.unstable_SuspenseList; + + useHover = require('react-interactions/events/hover').useHover; }); it('hydrates a parent even if a child Suspense boundary is blocked', async () => { @@ -1805,12 +1859,6 @@ describe('ReactDOMServerPartialHydration', () => { Scheduler.unstable_flushAll(); jest.runAllTimers(); - // TODO: With selective hydration the event should've been replayed - // but for now we'll have to issue it again. - act(() => { - a.click(); - }); - expect(clicks).toBe(1); expect(container.textContent).toBe('Hello'); @@ -1868,6 +1916,12 @@ describe('ReactDOMServerPartialHydration', () => { suspend = true; let root = ReactDOM.unstable_createRoot(container, {hydrate: true}); root.render(); + + // We'll do one click before hydrating. + a.click(); + // This should be delayed. + expect(onEvent).toHaveBeenCalledTimes(0); + Scheduler.unstable_flushAll(); jest.runAllTimers(); @@ -1885,13 +1939,165 @@ describe('ReactDOMServerPartialHydration', () => { Scheduler.unstable_flushAll(); jest.runAllTimers(); - // TODO: With selective hydration the event should've been replayed - // but for now we'll have to issue it again. - act(() => { - a.click(); + expect(onEvent).toHaveBeenCalledTimes(2); + + document.body.removeChild(container); + }); + + it('invokes discrete events on nested suspense boundaries in a root (legacy system)', async () => { + let suspend = false; + let resolve; + let promise = new Promise(resolvePromise => (resolve = resolvePromise)); + + let clicks = 0; + + function Button() { + return ( + { + clicks++; + }}> + Click me + + ); + } + + function Child() { + if (suspend) { + throw promise; + } else { + return ( + +