Move SuspenseList to experimental channel (#22765)

There's more work to be done to implement this correctly on the server,
so we're going to wait to release it until an 18.x minor.
This commit is contained in:
Andrew Clark 2021-11-15 13:12:56 -05:00 committed by GitHub
parent 489b4bdcca
commit 83564712b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 89 additions and 23 deletions

View File

@ -42,7 +42,9 @@ describe('ReactDOMFizzServer', () => {
}
Stream = require('stream');
Suspense = React.Suspense;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
PropTypes = require('prop-types');
@ -656,6 +658,7 @@ describe('ReactDOMFizzServer', () => {
expect(ref.current).toBe(b);
});
// @gate enableSuspenseList
// @gate experimental
it('shows inserted items before pending in a SuspenseList as fallbacks while hydrating', async () => {
const ref = React.createRef();

View File

@ -84,7 +84,9 @@ describe('ReactDOMServerPartialHydration', () => {
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
Suspense = React.Suspense;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
});
@ -1545,6 +1547,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
});
// @gate enableSuspenseList
it('shows inserted items in a SuspenseList before content is hydrated', async () => {
let suspend = false;
let resolve;
@ -1630,6 +1633,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(spanB);
});
// @gate enableSuspenseList
it('shows is able to hydrate boundaries even if others in a list are pending', async () => {
let suspend = false;
let resolve;
@ -1704,7 +1708,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('ALoading B');
});
// @gate experimental || www
// @gate enableSuspenseList
it('clears server boundaries when SuspenseList runs out of time hydrating', async () => {
let suspend = false;
let resolve;
@ -1807,6 +1811,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(b);
});
// @gate enableSuspenseList
it('clears server boundaries when SuspenseList suspends last row hydrating', async () => {
let suspend = false;
let resolve;

View File

@ -16,6 +16,7 @@ let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
let act;
let SuspenseList;
function initModules() {
// Reset warning cache.
@ -26,6 +27,9 @@ function initModules() {
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
act = require('jest-react').act;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
// Make them available to the helpers.
return {
@ -137,16 +141,17 @@ describe('ReactDOMServerSuspense', () => {
);
});
// @gate enableSuspenseList
it('server renders a SuspenseList component and its children', async () => {
const example = (
<React.SuspenseList>
<SuspenseList>
<React.Suspense fallback="Loading A">
<div>A</div>
</React.Suspense>
<React.Suspense fallback="Loading B">
<div>B</div>
</React.Suspense>
</React.SuspenseList>
</SuspenseList>
);
const element = await serverRender(example);
const parent = element.parentNode;

View File

@ -24,7 +24,9 @@ beforeEach(() => {
act = require('jest-react').act;
Suspense = React.Suspense;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
getCacheForType = React.unstable_getCacheForType;
@ -197,6 +199,7 @@ test('warns in DEV if return pointer is inconsistent', async () => {
});
// @gate enableCache
// @gate enableSuspenseList
test('regression (#20932): return pointer is correct before entering deleted tree', async () => {
// Based on a production bug. Designed to trigger a very specific
// implementation path.

View File

@ -12,6 +12,7 @@
let React;
let ReactDOM;
let ReactIs;
let SuspenseList;
describe('ReactIs', () => {
beforeEach(() => {
@ -20,6 +21,10 @@ describe('ReactIs', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactIs = require('react-is');
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
});
it('should return undefined for unknown/invalid types', () => {
@ -186,10 +191,11 @@ describe('ReactIs', () => {
expect(ReactIs.isSuspense(<div />)).toBe(false);
});
// @gate enableSuspenseList
it('should identify suspense list', () => {
expect(ReactIs.isValidElementType(React.SuspenseList)).toBe(true);
expect(ReactIs.typeOf(<React.SuspenseList />)).toBe(ReactIs.SuspenseList);
expect(ReactIs.isSuspenseList(<React.SuspenseList />)).toBe(true);
expect(ReactIs.isValidElementType(SuspenseList)).toBe(true);
expect(ReactIs.typeOf(<SuspenseList />)).toBe(ReactIs.SuspenseList);
expect(ReactIs.isSuspenseList(<SuspenseList />)).toBe(true);
expect(ReactIs.isSuspenseList({type: ReactIs.SuspenseList})).toBe(false);
expect(ReactIs.isSuspenseList('React.SuspenseList')).toBe(false);
expect(ReactIs.isSuspenseList(<div />)).toBe(false);

View File

@ -21,7 +21,9 @@ describe('ReactLazyContextPropagation', () => {
useState = React.useState;
useContext = React.useContext;
Suspense = React.Suspense;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
getCacheForType = React.unstable_getCacheForType;
@ -651,6 +653,7 @@ describe('ReactLazyContextPropagation', () => {
expect(root).toMatchRenderedOutput('BBB');
});
// @gate enableSuspenseList
test('contexts are propagated through SuspenseList', async () => {
// This kinda tests an implementation detail. SuspenseList has an early
// bailout that doesn't use `bailoutOnAlreadyFinishedWork`. It probably

View File

@ -34,6 +34,7 @@ let forwardRef;
let memo;
let act;
let ContinuousEventPriority;
let SuspenseList;
describe('ReactHooksWithNoopRenderer', () => {
beforeEach(() => {
@ -60,6 +61,9 @@ describe('ReactHooksWithNoopRenderer', () => {
Suspense = React.Suspense;
ContinuousEventPriority = require('react-reconciler/constants')
.ContinuousEventPriority;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
textCache = new Map();
@ -4291,9 +4295,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
});
// @gate enableSuspenseList
it('regression: SuspenseList causes unmounts to be dropped on deletion', async () => {
const SuspenseList = React.SuspenseList;
function Row({label}) {
useEffect(() => {
Scheduler.unstable_yieldValue('Mount ' + label);

View File

@ -16,7 +16,9 @@ describe('ReactSuspenseList', () => {
act = require('jest-react').act;
Profiler = React.Profiler;
Suspense = React.Suspense;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
});
function Text(props) {
@ -42,6 +44,7 @@ describe('ReactSuspenseList', () => {
return Component;
}
// @gate enableSuspenseList
it('warns if an unsupported revealOrder option is used', () => {
function Foo() {
return (
@ -61,6 +64,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('warns if a upper case revealOrder option is used', () => {
function Foo() {
return (
@ -80,6 +84,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('warns if a misspelled revealOrder option is used', () => {
function Foo() {
return (
@ -100,6 +105,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('warns if a single element is passed to a "forwards" list', () => {
function Foo({children}) {
return <SuspenseList revealOrder="forwards">{children}</SuspenseList>;
@ -132,6 +138,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('warns if a single fragment is passed to a "backwards" list', () => {
function Foo() {
return (
@ -152,6 +159,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('warns if a nested array is passed to a "forwards" list', () => {
function Foo({items}) {
return (
@ -179,6 +187,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('shows content independently by default', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -245,6 +254,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('shows content independently in legacy mode regardless of option', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -315,6 +325,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays all "together"', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -384,6 +395,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays all "together" even when nested as siblings', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -469,6 +481,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays all "together" in nested SuspenseLists', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -530,6 +543,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays all "together" in nested SuspenseLists where the inner is default', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -589,6 +603,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays all "together" during an update', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -673,6 +688,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('avoided boundaries can be coordinate with SuspenseList', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -771,6 +787,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('boundaries without fallbacks can be coordinate with SuspenseList', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -854,6 +871,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays each items in "forwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -919,6 +937,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays each items in "backwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -984,6 +1003,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays added row at the top "together" and the bottom in "forwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1138,6 +1158,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('displays added row at the top "together" and the bottom in "backwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1322,6 +1343,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('switches to rendering fallbacks if the tail takes long CPU time', async () => {
function Foo() {
return (
@ -1390,6 +1412,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('only shows one loading state at a time for "collapsed" tail insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1459,6 +1482,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('warns if an unsupported tail option is used', () => {
function Foo() {
return (
@ -1479,6 +1503,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('warns if a tail option is used with "together"', () => {
function Foo() {
return (
@ -1499,6 +1524,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate enableSuspenseList
it('renders one "collapsed" fallback even if CPU time elapsed', async () => {
function Foo() {
return (
@ -1571,6 +1597,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('adding to the middle does not collapse insertions (forwards)', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1713,6 +1740,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('adding to the middle does not collapse insertions (backwards)', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1860,6 +1888,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('adding to the middle of committed tail does not collapse insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -2017,6 +2046,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('only shows no initial loading state "hidden" tail insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -2080,6 +2110,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('eventually resolves a nested forwards suspense list', async () => {
const B = createAsyncText('B');
@ -2142,6 +2173,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('eventually resolves a nested forwards suspense list with a hidden tail', async () => {
const B = createAsyncText('B');
@ -2188,6 +2220,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('eventually resolves two nested forwards suspense lists with a hidden tail', async () => {
const B = createAsyncText('B');
@ -2255,6 +2288,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('can do unrelated adjacent updates', async () => {
let updateAdjacent;
function Adjacent() {
@ -2301,6 +2335,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('is able to re-suspend the last rows during an update with hidden', async () => {
const AsyncB = createAsyncText('B');
@ -2389,6 +2424,7 @@ describe('ReactSuspenseList', () => {
expect(previousInst).toBe(setAsyncB);
});
// @gate enableSuspenseList
it('is able to re-suspend the last rows during an update with hidden', async () => {
const AsyncB = createAsyncText('B');
@ -2477,6 +2513,7 @@ describe('ReactSuspenseList', () => {
expect(previousInst).toBe(setAsyncB);
});
// @gate enableSuspenseList
it('is able to interrupt a partially rendered tree and continue later', async () => {
const AsyncA = createAsyncText('A');
@ -2555,6 +2592,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('can resume class components when revealed together', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -2617,6 +2655,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('should be able to progressively show CPU expensive rows with two pass rendering', async () => {
function TwoPass({text}) {
const [pass, setPass] = React.useState(0);
@ -2687,6 +2726,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate enableSuspenseList
it('should be able to progressively show rows with two pass rendering and visible', async () => {
function TwoPass({text}) {
const [pass, setPass] = React.useState(0);
@ -2769,6 +2809,7 @@ describe('ReactSuspenseList', () => {
});
// @gate enableProfilerTimer
// @gate enableSuspenseList
it('counts the actual duration when profiling a SuspenseList', async () => {
// Order of parameters: id, phase, actualDuration, treeBaseDuration
const onRender = jest.fn();

View File

@ -13,6 +13,7 @@ let ReactDOM;
let JSResourceReference;
let ReactDOMFlightRelayServer;
let ReactDOMFlightRelayClient;
let SuspenseList;
describe('ReactFlightDOMRelay', () => {
beforeEach(() => {
@ -24,6 +25,9 @@ describe('ReactFlightDOMRelay', () => {
ReactDOMFlightRelayServer = require('react-server-dom-relay/server');
ReactDOMFlightRelayClient = require('react-server-dom-relay');
JSResourceReference = require('JSResourceReference');
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
});
function readThrough(data) {
@ -104,16 +108,9 @@ describe('ReactFlightDOMRelay', () => {
expect(container.innerHTML).toEqual('<span>Hello, Seb Smith</span>');
});
// @gate enableSuspenseList
it('can reasonably handle different element types', () => {
const {
forwardRef,
memo,
Fragment,
StrictMode,
Profiler,
Suspense,
SuspenseList,
} = React;
const {forwardRef, memo, Fragment, StrictMode, Profiler, Suspense} = React;
const Inner = memo(
forwardRef((props, ref) => {

View File

@ -17,7 +17,6 @@ export {
PureComponent,
StrictMode,
Suspense,
SuspenseList,
cloneElement,
createContext,
createElement,

View File

@ -86,6 +86,7 @@ function getTestFlags() {
// This isn't a flag, just a useful alias for tests.
enableUseSyncExternalStoreShim: !__VARIANT__,
enableSuspenseList: releaseChannel === 'experimental' || www,
// If there's a naming conflict between scheduler and React feature flags, the
// React ones take precedence.