diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js index 0bfe0fe630c4..9f8deed495d3 100644 --- a/fixtures/flight/src/App.js +++ b/fixtures/flight/src/App.js @@ -2,7 +2,7 @@ import * as React from 'react'; import {renderToReadableStream} from 'react-server-dom-unbundled/server'; import {createFromReadableStream} from 'react-server-dom-webpack/client'; import {PassThrough, Readable} from 'stream'; - +import {ClientContext, ClientReadContext} from './ClientContext.js'; import Container from './Container.js'; import {Counter} from './Counter.js'; @@ -235,6 +235,11 @@ export default async function App({prerender, noCache}) { {dedupedChild} {Promise.resolve([dedupedChild])} + +
+ +
+
{prerender ? null : ( // TODO: prerender is broken for large content for some reason. diff --git a/fixtures/flight/src/ClientContext.js b/fixtures/flight/src/ClientContext.js new file mode 100644 index 000000000000..7d9340e62a8a --- /dev/null +++ b/fixtures/flight/src/ClientContext.js @@ -0,0 +1,12 @@ +'use client'; + +import {createContext, use} from 'react'; + +const ClientContext = createContext(null); + +function ClientReadContext() { + const value = use(ClientContext); + return

{value}

; +} + +export {ClientContext, ClientReadContext}; diff --git a/packages/react-devtools-shared/src/__tests__/profilerContext-test.js b/packages/react-devtools-shared/src/__tests__/profilerContext-test.js index a1e47defa2a6..9688adebf8be 100644 --- a/packages/react-devtools-shared/src/__tests__/profilerContext-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilerContext-test.js @@ -776,6 +776,84 @@ describe('ProfilerContext', () => { document.body.removeChild(profilerContainer); }); + it('should reset commit index when switching to a different root', async () => { + const Parent = () => ; + const Child = () => null; + + const containerA = document.createElement('div'); + const containerB = document.createElement('div'); + + const rootA = ReactDOMClient.createRoot(containerA); + const rootB = ReactDOMClient.createRoot(containerB); + + utils.act(() => rootA.render()); + utils.act(() => rootB.render()); + + // Profile and record different numbers of commits for each root + // Root A: 5 commits, Root B: 2 commits + await utils.actAsync(() => store.profilerStore.startProfiling()); + await utils.actAsync(() => rootA.render()); // Root A commit 1 + await utils.actAsync(() => rootA.render()); // Root A commit 2 + await utils.actAsync(() => rootA.render()); // Root A commit 3 + await utils.actAsync(() => rootA.render()); // Root A commit 4 + await utils.actAsync(() => rootA.render()); // Root A commit 5 + await utils.actAsync(() => rootB.render()); // Root B commit 1 + await utils.actAsync(() => rootB.render()); // Root B commit 2 + await utils.actAsync(() => store.profilerStore.stopProfiling()); + + let context: Context = ((null: any): Context); + function ContextReader() { + context = React.useContext(ProfilerContext); + return null; + } + + await utils.actAsync(() => + TestRenderer.create( + + + , + ), + ); + + // Verify we have profiling data for both roots + expect(context.didRecordCommits).toBe(true); + expect(context.profilingData).not.toBeNull(); + + const rootIDs = Array.from(context.profilingData.dataForRoots.keys()); + expect(rootIDs.length).toBe(2); + + const [rootAID, rootBID] = rootIDs; + const rootAData = context.profilingData.dataForRoots.get(rootAID); + const rootBData = context.profilingData.dataForRoots.get(rootBID); + + expect(rootAData.commitData.length).toBe(5); + expect(rootBData.commitData.length).toBe(2); + + // Select root A and navigate to commit 4 (index 3) + await utils.actAsync(() => context.setRootID(rootAID)); + expect(context.rootID).toBe(rootAID); + expect(context.selectedCommitIndex).toBe(0); + + await utils.actAsync(() => context.selectCommitIndex(3)); + expect(context.selectedCommitIndex).toBe(3); + + // Switch to root B which only has 2 commits + // The commit index should be reset to 0, not stay at 3 (which would be invalid) + await utils.actAsync(() => context.setRootID(rootBID)); + expect(context.rootID).toBe(rootBID); + // Should be reset to 0 since commit 3 doesn't exist in root B + expect(context.selectedCommitIndex).toBe(0); + + // Verify we can still navigate in root B + await utils.actAsync(() => context.selectCommitIndex(1)); + expect(context.selectedCommitIndex).toBe(1); + + // Switch back to root A - should reset to 0 + await utils.actAsync(() => context.setRootID(rootAID)); + expect(context.rootID).toBe(rootAID); + expect(context.selectedCommitIndex).toBe(0); + }); + it('should handle commit selection edge cases when filtering commits', async () => { const Scheduler = require('scheduler'); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js b/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js index e309e6a3cf39..d3369b4f9538 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js @@ -45,6 +45,14 @@ export function useCommitFilteringAndNavigation( null, ); + // Reset commit index when commitData changes (e.g., when switching roots). + const [previousCommitData, setPreviousCommitData] = + useState>(commitData); + if (previousCommitData !== commitData) { + setPreviousCommitData(commitData); + selectCommitIndex(commitData.length > 0 ? 0 : null); + } + const calculateFilteredIndices = useCallback( (enabled: boolean, minDuration: number): Array => { return commitData.reduce((reduced: Array, commitDatum, index) => { diff --git a/packages/react-dom/src/__tests__/ReactDOMActivity-test.js b/packages/react-dom/src/__tests__/ReactDOMActivity-test.js index ec2048ec434f..abaca64da260 100644 --- a/packages/react-dom/src/__tests__/ReactDOMActivity-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMActivity-test.js @@ -54,7 +54,6 @@ describe('ReactDOMActivity', () => { return {props.children}; } - // @gate enableActivity it( 'hiding an Activity boundary also hides the direct children of any ' + 'portals it contains, regardless of how deeply nested they are', @@ -100,7 +99,6 @@ describe('ReactDOMActivity', () => { }, ); - // @gate enableActivity it( 'revealing an Activity boundary inside a portal does not reveal the ' + 'portal contents if has a hidden Activity parent', @@ -151,7 +149,6 @@ describe('ReactDOMActivity', () => { }, ); - // @gate enableActivity it('hides new portals added to an already hidden tree', async () => { function Child() { return ; @@ -218,7 +215,6 @@ describe('ReactDOMActivity', () => { ); }); - // @gate enableActivity it('hides new insertions inside an already hidden portal', async () => { function Child({text}) { useLayoutEffect(() => { @@ -289,7 +285,6 @@ describe('ReactDOMActivity', () => { ); }); - // @gate enableActivity it('reveal an inner Suspense boundary without revealing an outer Activity on the same host child', async () => { const promise = new Promise(() => {}); @@ -390,7 +385,6 @@ describe('ReactDOMActivity', () => { ); }); - // @gate enableActivity it('mounts/unmounts layout effects in portal when visibility changes (starting visible)', async () => { function Child() { useLayoutEffect(() => { @@ -440,7 +434,6 @@ describe('ReactDOMActivity', () => { ); }); - // @gate enableActivity it('mounts/unmounts layout effects in portal when visibility changes (starting hidden)', async () => { function Child() { useLayoutEffect(() => { diff --git a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js index 7ff088d7d312..603af9072b6e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js @@ -763,7 +763,7 @@ describe('FragmentRefs', () => { }); describe('with activity', () => { - // @gate enableFragmentRefs && enableActivity + // @gate enableFragmentRefs it('does not apply event listeners to hidden trees', async () => { const parentRef = React.createRef(); const fragmentRef = React.createRef(); @@ -799,7 +799,7 @@ describe('FragmentRefs', () => { expect(logs).toEqual(['Child 1', 'Child 3']); }); - // @gate enableFragmentRefs && enableActivity + // @gate enableFragmentRefs it('applies event listeners to visible trees', async () => { const parentRef = React.createRef(); const fragmentRef = React.createRef(); @@ -835,7 +835,7 @@ describe('FragmentRefs', () => { expect(logs).toEqual(['Child 1', 'Child 2', 'Child 3']); }); - // @gate enableFragmentRefs && enableActivity + // @gate enableFragmentRefs it('handles Activity modes switching', async () => { const fragmentRef = React.createRef(); const fragmentRef2 = React.createRef(); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 502ac8c326c1..3e6af7bc9744 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -3668,7 +3668,6 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current.innerHTML).toBe('Hidden child'); }); - // @gate enableActivity it('a visible Activity component is surrounded by comment markers', async () => { const ref = React.createRef(); @@ -3706,7 +3705,6 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(span); }); - // @gate enableActivity it('a hidden Activity component is skipped over during server rendering', async () => { const visibleRef = React.createRef(); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydrationActivity-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydrationActivity-test.internal.js index cf9e5d1d0399..0921920cb55d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydrationActivity-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydrationActivity-test.internal.js @@ -113,7 +113,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { IdleEventPriority = require('react-reconciler/constants').IdleEventPriority; }); - // @gate enableActivity it('hydrates a parent even if a child Activity boundary is blocked', async () => { let suspend = false; let resolve; @@ -169,7 +168,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(ref.current).toBe(span); }); - // @gate enableActivity it('can hydrate siblings of a suspended component without errors', async () => { let suspend = false; let resolve; @@ -228,7 +226,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.textContent).toBe('HelloHello'); }); - // @gate enableActivity it('falls back to client rendering boundary on mismatch', async () => { let client = false; let suspend = false; @@ -321,7 +318,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { ); }); - // @gate enableActivity it('handles if mismatch is after suspending', async () => { let client = false; let suspend = false; @@ -401,7 +397,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.innerHTML).toBe('Hello
Mismatch
'); }); - // @gate enableActivity it('handles if mismatch is child of suspended component', async () => { let client = false; let suspend = false; @@ -482,7 +477,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.innerHTML).toBe('
Mismatch
'); }); - // @gate enableActivity it('handles if mismatch is parent and first child suspends', async () => { let client = false; let suspend = false; @@ -575,7 +569,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { ); }); - // @gate enableActivity it('does show a parent fallback if mismatch is parent and second child suspends', async () => { let client = false; let suspend = false; @@ -687,7 +680,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { ); }); - // @gate enableActivity it('does show a parent fallback if mismatch is in parent element only', async () => { let client = false; let suspend = false; @@ -787,7 +779,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { ); }); - // @gate enableActivity it('does show a parent fallback if mismatch is before suspending', async () => { let client = false; let suspend = false; @@ -886,7 +877,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { ); }); - // @gate enableActivity it('does show a parent fallback if mismatch is before suspending in a child', async () => { let client = false; let suspend = false; @@ -987,7 +977,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { ); }); - // @gate enableActivity it('calls the hydration callbacks after hydration or deletion', async () => { let suspend = false; let resolve; @@ -1079,7 +1068,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(deleted.length).toBe(1); }); - // @gate enableActivity it('hydrates an empty activity boundary', async () => { function App() { return ( @@ -1101,7 +1089,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.innerHTML).toContain('
Sibling
'); }); - // @gate enableActivity it('recovers with client render when server rendered additional nodes at suspense root', async () => { function CheckIfHydrating({children}) { // This is a trick to check whether we're hydrating or not, since React @@ -1171,7 +1158,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(ref.current).not.toBe(span); }); - // @gate enableActivity it('recovers with client render when server rendered additional nodes at suspense root after unsuspending', async () => { const ref = React.createRef(); let shouldSuspend = false; @@ -1236,7 +1222,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(ref.current).not.toBe(span); }); - // @gate enableActivity it('recovers with client render when server rendered additional nodes deep inside suspense root', async () => { const ref = React.createRef(); function App({hasB}) { @@ -1283,7 +1268,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(ref.current).not.toBe(span); }); - // @gate enableActivity it('calls the onDeleted hydration callback if the parent gets deleted', async () => { let suspend = false; const promise = new Promise(() => {}); @@ -1337,7 +1321,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(deleted.length).toBe(1); }); - // @gate enableActivity it('can insert siblings before the dehydrated boundary', async () => { let suspend = false; const promise = new Promise(() => {}); @@ -1395,7 +1378,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.firstChild.firstChild.textContent).toBe('First'); }); - // @gate enableActivity it('can delete the dehydrated boundary before it is hydrated', async () => { let suspend = false; const promise = new Promise(() => {}); @@ -1451,7 +1433,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.firstChild.children[1].textContent).toBe('After'); }); - // @gate enableActivity it('blocks updates to hydrate the content first if props have changed', async () => { let suspend = false; let resolve; @@ -1523,7 +1504,7 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(span.className).toBe('hi'); }); - // @gate enableActivity && www + // @gate www it('blocks updates to hydrate the content first if props changed at idle priority', async () => { let suspend = false; let resolve; @@ -1597,7 +1578,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(span.className).toBe('hi'); }); - // @gate enableActivity it('shows the fallback of the parent if props have changed before hydration completes and is still suspended', async () => { let suspend = false; let resolve; @@ -1684,7 +1664,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.textContent).toBe('Hi'); }); - // @gate enableActivity it('clears nested activity boundaries if they did not hydrate yet', async () => { let suspend = false; const promise = new Promise(() => {}); @@ -1752,7 +1731,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.textContent).toBe('Hi Hi'); }); - // @gate enableActivity it('hydrates first if props changed but we are able to resolve within a timeout', async () => { let suspend = false; let resolve; @@ -1826,7 +1804,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(span.className).toBe('hi'); }); - // @gate enableActivity it('warns but works if setState is called before commit in a dehydrated component', async () => { let suspend = false; let resolve; @@ -1901,7 +1878,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.textContent).toBe('Hello'); }); - // @gate enableActivity it('blocks the update to hydrate first if context has changed', async () => { let suspend = false; let resolve; @@ -1984,7 +1960,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(span.className).toBe('hi'); }); - // @gate enableActivity it('shows the parent fallback if context has changed before hydration completes and is still suspended', async () => { let suspend = false; let resolve; @@ -2075,7 +2050,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(container.textContent).toBe('Hi'); }); - // @gate enableActivity it('can hydrate TWO activity boundaries', async () => { const ref1 = React.createRef(); const ref2 = React.createRef(); @@ -2113,7 +2087,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(ref2.current).toBe(span2); }); - // @gate enableActivity it('regenerates if it cannot hydrate before changes to props/context expire', async () => { let suspend = false; const promise = new Promise(resolvePromise => {}); @@ -2201,7 +2174,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(newSpan.className).toBe('hi'); }); - // @gate enableActivity it('does not invoke an event on a hydrated node until it commits', async () => { let suspend = false; let resolve; @@ -2281,7 +2253,7 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity && www + // @gate www it('does not invoke an event on a hydrated event handle until it commits', async () => { const setClick = ReactDOM.unstable_createEventHandle('click'); let suspend = false; @@ -2363,7 +2335,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('invokes discrete events on nested activity boundaries in a root (legacy system)', async () => { let suspend = false; let resolve; @@ -2444,7 +2415,7 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity && www + // @gate www it('invokes discrete events on nested activity boundaries in a root (createEventHandle)', async () => { let suspend = false; let isServerRendering = true; @@ -2529,7 +2500,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('does not invoke the parent of dehydrated boundary event', async () => { let suspend = false; let resolve; @@ -2602,7 +2572,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('does not invoke an event on a parent tree when a subtree is dehydrated', async () => { let suspend = false; let resolve; @@ -2675,7 +2644,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(parentContainer); }); - // @gate enableActivity it('blocks only on the last continuous event (legacy system)', async () => { let suspend1 = false; let resolve1; @@ -2776,7 +2744,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('finishes normal pri work before continuing to hydrate a retry', async () => { let suspend = false; let resolve; @@ -2860,7 +2827,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { expect(ref.current).not.toBe(null); }); - // @gate enableActivity it('regression test: does not overfire non-bubbling browser events', async () => { let suspend = false; let resolve; @@ -2945,7 +2911,6 @@ describe('ReactDOMServerPartialHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('fallback to client render on hydration mismatch at root', async () => { let suspend = true; let resolve; diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydrationActivity-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydrationActivity-test.internal.js index c2a856a01818..1892b915be8e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydrationActivity-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydrationActivity-test.internal.js @@ -152,7 +152,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { require('react-reconciler/constants').ContinuousEventPriority; }); - // @gate enableActivity it('hydrates the target boundary synchronously during a click', async () => { function Child({text}) { Scheduler.log(text); @@ -214,7 +213,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('hydrates at higher pri if sync did not work first time', async () => { let suspend = false; let resolve; @@ -300,7 +298,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('hydrates at higher pri for secondary discrete events', async () => { let suspend = false; let resolve; @@ -388,7 +385,7 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity && www + // @gate www it('hydrates the target boundary synchronously during a click (createEventHandle)', async () => { const setClick = ReactDOM.unstable_createEventHandle('click'); let isServerRendering = true; @@ -455,7 +452,7 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity && www + // @gate www it('hydrates at higher pri if sync did not work first time (createEventHandle)', async () => { let suspend = false; let isServerRendering = true; @@ -544,7 +541,7 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity && www + // @gate www it('hydrates at higher pri for secondary discrete events (createEventHandle)', async () => { const setClick = ReactDOM.unstable_createEventHandle('click'); let suspend = false; @@ -636,7 +633,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('hydrates the hovered targets as higher priority for continuous events', async () => { let suspend = false; let resolve; @@ -729,7 +725,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('replays capture phase for continuous events and respects stopPropagation', async () => { let suspend = false; let resolve; @@ -883,7 +878,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('replays event with null target when tree is dismounted', async () => { let suspend = false; let resolve; @@ -957,7 +951,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('hydrates the last target path first for continuous events', async () => { let suspend = false; let resolve; @@ -1044,7 +1037,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('hydrates the last explicitly hydrated target at higher priority', async () => { function Child({text}) { Scheduler.log(text); @@ -1092,7 +1084,7 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { await waitForAll(['App', 'C', 'B', 'A']); }); - // @gate enableActivity && www + // @gate www it('hydrates before an update even if hydration moves away from it', async () => { function Child({text}) { Scheduler.log(text); @@ -1200,7 +1192,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('fires capture event handlers and native events if content is hydratable during discrete event', async () => { spyOnDev(console, 'error'); function Child({text}) { @@ -1272,7 +1263,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { document.body.removeChild(container); }); - // @gate enableActivity it('does not propagate discrete event if it cannot be synchronously hydrated', async () => { let triggeredParent = false; let triggeredChild = false; @@ -1335,7 +1325,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { expect(triggeredChild).toBe(false); }); - // @gate enableActivity it('can force hydration in response to sync update', async () => { function Child({text}) { Scheduler.log(`Child ${text}`); @@ -1371,7 +1360,7 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { expect(initialSpan).toBe(spanRef); }); - // @gate enableActivity && www + // @gate www it('can force hydration in response to continuous update', async () => { function Child({text}) { Scheduler.log(`Child ${text}`); @@ -1408,7 +1397,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { expect(initialSpan).toBe(spanRef); }); - // @gate enableActivity it('can force hydration in response to default update', async () => { function Child({text}) { Scheduler.log(`Child ${text}`); @@ -1441,7 +1429,7 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { expect(initialSpan).toBe(spanRef); }); - // @gate enableActivity && www + // @gate www it('regression test: can unwind context on selective hydration interruption', async () => { const Context = React.createContext('DefaultContext'); @@ -1500,7 +1488,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { }); }); - // @gate enableActivity it('regression test: can unwind context on selective hydration interruption for sync updates', async () => { const Context = React.createContext('DefaultContext'); @@ -1552,7 +1539,6 @@ describe('ReactDOMServerSelectiveHydrationActivity', () => { }); }); - // @gate enableActivity it('regression: selective hydration does not contribute to "maximum update limit" count', async () => { const outsideRef = React.createRef(null); const insideRef = React.createRef(null); diff --git a/packages/react-markup/src/ReactMarkupServer.js b/packages/react-markup/src/ReactMarkupServer.js index 5d22f1a4c94f..95a5ce51c3e1 100644 --- a/packages/react-markup/src/ReactMarkupServer.js +++ b/packages/react-markup/src/ReactMarkupServer.js @@ -184,6 +184,7 @@ export function experimental_renderToHTML( handleFlightError, options ? options.identifierPrefix : undefined, undefined, + undefined, 'Markup', undefined, false, diff --git a/packages/react-noop-renderer/src/ReactNoopFlightServer.js b/packages/react-noop-renderer/src/ReactNoopFlightServer.js index 469e5465f308..752652259768 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightServer.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightServer.js @@ -73,6 +73,7 @@ type Options = { signal?: AbortSignal, debugChannel?: {onMessage?: (message: string) => void}, onError?: (error: mixed) => void, + startTime?: number, }; function render(model: ReactClientValue, options?: Options): Destination { @@ -84,6 +85,7 @@ function render(model: ReactClientValue, options?: Options): Destination { options ? options.onError : undefined, options ? options.identifierPrefix : undefined, undefined, + options ? options.startTime : undefined, __DEV__ && options ? options.environmentName : undefined, __DEV__ && options ? options.filterStackFrame : undefined, __DEV__ && options && options.debugChannel !== undefined, diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 4ea1a1526370..48ced588730b 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -128,6 +128,7 @@ import { REACT_LAZY_TYPE, REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE, + REACT_CONTEXT_TYPE, } from 'shared/ReactSymbols'; import {setCurrentFiber} from './ReactCurrentFiber'; import { @@ -2140,6 +2141,10 @@ function mountLazyComponent( props, renderLanes, ); + } else if ($$typeof === REACT_CONTEXT_TYPE) { + workInProgress.tag = ContextProvider; + workInProgress.type = Component; + return updateContextProvider(null, workInProgress, renderLanes); } } diff --git a/packages/react-reconciler/src/__tests__/Activity-test.js b/packages/react-reconciler/src/__tests__/Activity-test.js index 2fdcc16701db..a27e62e585fb 100644 --- a/packages/react-reconciler/src/__tests__/Activity-test.js +++ b/packages/react-reconciler/src/__tests__/Activity-test.js @@ -196,7 +196,6 @@ describe('Activity', () => { ); }); - // @gate enableActivity it('mounts without layout effects when hidden', async () => { function Child({text}) { useLayoutEffect(() => { @@ -234,7 +233,6 @@ describe('Activity', () => { expect(root).toMatchRenderedOutput(); }); - // @gate enableActivity it('mounts/unmounts layout effects when visibility changes (starting visible)', async () => { function Child({text}) { useLayoutEffect(() => { @@ -280,7 +278,6 @@ describe('Activity', () => { expect(root).toMatchRenderedOutput(); }); - // @gate enableActivity it('nested offscreen does not call componentWillUnmount when hidden', async () => { // This is a bug that appeared during production test of . // It is a very specific scenario with nested Offscreens. The inner offscreen @@ -384,7 +381,6 @@ describe('Activity', () => { assertLog(['child']); }); - // @gate enableActivity it('mounts/unmounts layout effects when visibility changes (starting hidden)', async () => { function Child({text}) { useLayoutEffect(() => { @@ -431,7 +427,6 @@ describe('Activity', () => { expect(root).toMatchRenderedOutput(