diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
index 3eb2d0e617a0f..e89eebb491576 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
@@ -47,255 +47,254 @@ describe('ReactDOMFiberAsync', () => {
expect(ops).toEqual(['Hi', 'Bye']);
});
- if (__EXPERIMENTAL__) {
- describe('concurrent mode', () => {
- beforeEach(() => {
- jest.resetModules();
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
- ReactDOM = require('react-dom');
- Scheduler = require('scheduler');
- });
-
- it('does not perform deferred updates synchronously', () => {
- let inputRef = React.createRef();
- let asyncValueRef = React.createRef();
- let syncValueRef = React.createRef();
-
- class Counter extends React.Component {
- state = {asyncValue: '', syncValue: ''};
-
- handleChange = e => {
- const nextValue = e.target.value;
- requestIdleCallback(() => {
- this.setState({
- asyncValue: nextValue,
- });
- // It should not be flushed yet.
- expect(asyncValueRef.current.textContent).toBe('');
- });
- this.setState({
- syncValue: nextValue,
- });
- };
-
- render() {
- return (
-
-
-
{this.state.asyncValue}
-
{this.state.syncValue}
-
- );
- }
- }
- const root = ReactDOM.createRoot(container);
- root.render();
- Scheduler.unstable_flushAll();
- expect(asyncValueRef.current.textContent).toBe('');
- expect(syncValueRef.current.textContent).toBe('');
-
- setUntrackedInputValue.call(inputRef.current, 'hello');
- inputRef.current.dispatchEvent(
- new MouseEvent('input', {bubbles: true}),
- );
- // Should only flush non-deferred update.
- expect(asyncValueRef.current.textContent).toBe('');
- expect(syncValueRef.current.textContent).toBe('hello');
-
- // Should flush both updates now.
- jest.runAllTimers();
- Scheduler.unstable_flushAll();
- expect(asyncValueRef.current.textContent).toBe('hello');
- expect(syncValueRef.current.textContent).toBe('hello');
- });
-
- it('top-level updates are concurrent', () => {
- const root = ReactDOM.createRoot(container);
- root.render(Hi
);
- expect(container.textContent).toEqual('');
- Scheduler.unstable_flushAll();
- expect(container.textContent).toEqual('Hi');
-
- root.render(Bye
);
- expect(container.textContent).toEqual('Hi');
- Scheduler.unstable_flushAll();
- expect(container.textContent).toEqual('Bye');
- });
-
- it('deep updates (setState) are concurrent', () => {
- let instance;
- class Component extends React.Component {
- state = {step: 0};
- render() {
- instance = this;
- return {this.state.step}
;
- }
- }
-
- const root = ReactDOM.createRoot(container);
- root.render();
- expect(container.textContent).toEqual('');
- Scheduler.unstable_flushAll();
- expect(container.textContent).toEqual('0');
-
- instance.setState({step: 1});
- expect(container.textContent).toEqual('0');
- Scheduler.unstable_flushAll();
- expect(container.textContent).toEqual('1');
- });
-
- it('flushSync batches sync updates and flushes them at the end of the batch', () => {
- let ops = [];
- let instance;
-
- class Component extends React.Component {
- state = {text: ''};
- push(val) {
- this.setState(state => ({text: state.text + val}));
- }
- componentDidUpdate() {
- ops.push(this.state.text);
- }
- render() {
- instance = this;
- return {this.state.text};
- }
- }
-
- ReactDOM.render(, container);
-
- instance.push('A');
- expect(ops).toEqual(['A']);
- expect(container.textContent).toEqual('A');
+ it('flushSync batches sync updates and flushes them at the end of the batch', () => {
+ let ops = [];
+ let instance;
+
+ class Component extends React.Component {
+ state = {text: ''};
+ push(val) {
+ this.setState(state => ({text: state.text + val}));
+ }
+ componentDidUpdate() {
+ ops.push(this.state.text);
+ }
+ render() {
+ instance = this;
+ return {this.state.text};
+ }
+ }
+
+ ReactDOM.render(, container);
+
+ instance.push('A');
+ expect(ops).toEqual(['A']);
+ expect(container.textContent).toEqual('A');
+
+ ReactDOM.flushSync(() => {
+ instance.push('B');
+ instance.push('C');
+ // Not flushed yet
+ expect(container.textContent).toEqual('A');
+ expect(ops).toEqual(['A']);
+ });
+ expect(container.textContent).toEqual('ABC');
+ expect(ops).toEqual(['A', 'ABC']);
+ instance.push('D');
+ expect(container.textContent).toEqual('ABCD');
+ expect(ops).toEqual(['A', 'ABC', 'ABCD']);
+ });
- ReactDOM.flushSync(() => {
- instance.push('B');
- instance.push('C');
- // Not flushed yet
- expect(container.textContent).toEqual('A');
- expect(ops).toEqual(['A']);
- });
- expect(container.textContent).toEqual('ABC');
- expect(ops).toEqual(['A', 'ABC']);
+ it('flushSync flushes updates even if nested inside another flushSync', () => {
+ let ops = [];
+ let instance;
+
+ class Component extends React.Component {
+ state = {text: ''};
+ push(val) {
+ this.setState(state => ({text: state.text + val}));
+ }
+ componentDidUpdate() {
+ ops.push(this.state.text);
+ }
+ render() {
+ instance = this;
+ return {this.state.text};
+ }
+ }
+
+ ReactDOM.render(, container);
+
+ instance.push('A');
+ expect(ops).toEqual(['A']);
+ expect(container.textContent).toEqual('A');
+
+ ReactDOM.flushSync(() => {
+ instance.push('B');
+ instance.push('C');
+ // Not flushed yet
+ expect(container.textContent).toEqual('A');
+ expect(ops).toEqual(['A']);
+
+ ReactDOM.flushSync(() => {
instance.push('D');
- expect(container.textContent).toEqual('ABCD');
- expect(ops).toEqual(['A', 'ABC', 'ABCD']);
});
+ // The nested flushSync caused everything to flush.
+ expect(container.textContent).toEqual('ABCD');
+ expect(ops).toEqual(['A', 'ABCD']);
+ });
+ expect(container.textContent).toEqual('ABCD');
+ expect(ops).toEqual(['A', 'ABCD']);
+ });
- it('flushSync flushes updates even if nested inside another flushSync', () => {
- let ops = [];
- let instance;
-
- class Component extends React.Component {
- state = {text: ''};
- push(val) {
- this.setState(state => ({text: state.text + val}));
- }
- componentDidUpdate() {
- ops.push(this.state.text);
- }
- render() {
- instance = this;
- return {this.state.text};
- }
- }
+ it('flushSync throws if already performing work', () => {
+ class Component extends React.Component {
+ componentDidUpdate() {
+ ReactDOM.flushSync(() => {});
+ }
+ render() {
+ return null;
+ }
+ }
+
+ // Initial mount
+ ReactDOM.render(, container);
+ // Update
+ expect(() => ReactDOM.render(, container)).toThrow(
+ 'flushSync was called from inside a lifecycle method',
+ );
+ });
- ReactDOM.render(, container);
+ describe('concurrent mode', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
+ ReactDOM = require('react-dom');
+ Scheduler = require('scheduler');
+ });
- instance.push('A');
- expect(ops).toEqual(['A']);
- expect(container.textContent).toEqual('A');
+ it.experimental('does not perform deferred updates synchronously', () => {
+ let inputRef = React.createRef();
+ let asyncValueRef = React.createRef();
+ let syncValueRef = React.createRef();
- ReactDOM.flushSync(() => {
- instance.push('B');
- instance.push('C');
- // Not flushed yet
- expect(container.textContent).toEqual('A');
- expect(ops).toEqual(['A']);
+ class Counter extends React.Component {
+ state = {asyncValue: '', syncValue: ''};
- ReactDOM.flushSync(() => {
- instance.push('D');
+ handleChange = e => {
+ const nextValue = e.target.value;
+ requestIdleCallback(() => {
+ this.setState({
+ asyncValue: nextValue,
+ });
+ // It should not be flushed yet.
+ expect(asyncValueRef.current.textContent).toBe('');
});
- // The nested flushSync caused everything to flush.
- expect(container.textContent).toEqual('ABCD');
- expect(ops).toEqual(['A', 'ABCD']);
- });
- expect(container.textContent).toEqual('ABCD');
- expect(ops).toEqual(['A', 'ABCD']);
- });
-
- it('flushSync throws if already performing work', () => {
- class Component extends React.Component {
- componentDidUpdate() {
- ReactDOM.flushSync(() => {});
- }
- render() {
- return null;
- }
+ this.setState({
+ syncValue: nextValue,
+ });
+ };
+
+ render() {
+ return (
+
+
+
{this.state.asyncValue}
+
{this.state.syncValue}
+
+ );
}
+ }
+ const root = ReactDOM.createRoot(container);
+ root.render();
+ Scheduler.unstable_flushAll();
+ expect(asyncValueRef.current.textContent).toBe('');
+ expect(syncValueRef.current.textContent).toBe('');
+
+ setUntrackedInputValue.call(inputRef.current, 'hello');
+ inputRef.current.dispatchEvent(new MouseEvent('input', {bubbles: true}));
+ // Should only flush non-deferred update.
+ expect(asyncValueRef.current.textContent).toBe('');
+ expect(syncValueRef.current.textContent).toBe('hello');
+
+ // Should flush both updates now.
+ jest.runAllTimers();
+ Scheduler.unstable_flushAll();
+ expect(asyncValueRef.current.textContent).toBe('hello');
+ expect(syncValueRef.current.textContent).toBe('hello');
+ });
- // Initial mount
- ReactDOM.render(, container);
- // Update
- expect(() => ReactDOM.render(, container)).toThrow(
- 'flushSync was called from inside a lifecycle method',
- );
- });
-
- it('flushSync flushes updates before end of the tick', () => {
- let ops = [];
- let instance;
+ it.experimental('top-level updates are concurrent', () => {
+ const root = ReactDOM.createRoot(container);
+ root.render(Hi
);
+ expect(container.textContent).toEqual('');
+ Scheduler.unstable_flushAll();
+ expect(container.textContent).toEqual('Hi');
+
+ root.render(Bye
);
+ expect(container.textContent).toEqual('Hi');
+ Scheduler.unstable_flushAll();
+ expect(container.textContent).toEqual('Bye');
+ });
- class Component extends React.Component {
- state = {text: ''};
- push(val) {
- this.setState(state => ({text: state.text + val}));
- }
- componentDidUpdate() {
- ops.push(this.state.text);
- }
- render() {
- instance = this;
- return {this.state.text};
- }
+ it.experimental('deep updates (setState) are concurrent', () => {
+ let instance;
+ class Component extends React.Component {
+ state = {step: 0};
+ render() {
+ instance = this;
+ return {this.state.step}
;
}
+ }
+
+ const root = ReactDOM.createRoot(container);
+ root.render();
+ expect(container.textContent).toEqual('');
+ Scheduler.unstable_flushAll();
+ expect(container.textContent).toEqual('0');
+
+ instance.setState({step: 1});
+ expect(container.textContent).toEqual('0');
+ Scheduler.unstable_flushAll();
+ expect(container.textContent).toEqual('1');
+ });
- const root = ReactDOM.createRoot(container);
- root.render();
- Scheduler.unstable_flushAll();
+ it.experimental('flushSync flushes updates before end of the tick', () => {
+ let ops = [];
+ let instance;
- // Updates are async by default
- instance.push('A');
- expect(ops).toEqual([]);
- expect(container.textContent).toEqual('');
+ class Component extends React.Component {
+ state = {text: ''};
+ push(val) {
+ this.setState(state => ({text: state.text + val}));
+ }
+ componentDidUpdate() {
+ ops.push(this.state.text);
+ }
+ render() {
+ instance = this;
+ return {this.state.text};
+ }
+ }
- ReactDOM.flushSync(() => {
- instance.push('B');
- instance.push('C');
- // Not flushed yet
- expect(container.textContent).toEqual('');
- expect(ops).toEqual([]);
- });
- // Only the active updates have flushed
- expect(container.textContent).toEqual('BC');
- expect(ops).toEqual(['BC']);
+ const root = ReactDOM.createRoot(container);
+ root.render();
+ Scheduler.unstable_flushAll();
- instance.push('D');
- expect(container.textContent).toEqual('BC');
- expect(ops).toEqual(['BC']);
+ // Updates are async by default
+ instance.push('A');
+ expect(ops).toEqual([]);
+ expect(container.textContent).toEqual('');
- // Flush the async updates
- Scheduler.unstable_flushAll();
- expect(container.textContent).toEqual('ABCD');
- expect(ops).toEqual(['BC', 'ABCD']);
+ ReactDOM.flushSync(() => {
+ instance.push('B');
+ instance.push('C');
+ // Not flushed yet
+ expect(container.textContent).toEqual('');
+ expect(ops).toEqual([]);
});
+ // Only the active updates have flushed
+ expect(container.textContent).toEqual('BC');
+ expect(ops).toEqual(['BC']);
+
+ instance.push('D');
+ expect(container.textContent).toEqual('BC');
+ expect(ops).toEqual(['BC']);
+
+ // Flush the async updates
+ Scheduler.unstable_flushAll();
+ expect(container.textContent).toEqual('ABCD');
+ expect(ops).toEqual(['BC', 'ABCD']);
+ });
- it('flushControlled flushes updates before yielding to browser', () => {
+ it.experimental(
+ 'flushControlled flushes updates before yielding to browser',
+ () => {
let inst;
class Counter extends React.Component {
state = {counter: 0};
@@ -332,9 +331,12 @@ describe('ReactDOMFiberAsync', () => {
'end of outer flush: 1',
'after outer flush: 3',
]);
- });
+ },
+ );
- it('flushControlled does not flush until end of outermost batchedUpdates', () => {
+ it.experimental(
+ 'flushControlled does not flush until end of outermost batchedUpdates',
+ () => {
let inst;
class Counter extends React.Component {
state = {counter: 0};
@@ -362,32 +364,35 @@ describe('ReactDOMFiberAsync', () => {
'end of batchedUpdates fn: 0',
'after batchedUpdates: 2',
]);
- });
-
- it('flushControlled returns nothing', () => {
- // In the future, we may want to return a thenable "work" object.
- let inst;
- class Counter extends React.Component {
- state = {counter: 0};
- increment = () =>
- this.setState(state => ({counter: state.counter + 1}));
- render() {
- inst = this;
- return this.state.counter;
- }
+ },
+ );
+
+ it.experimental('flushControlled returns nothing', () => {
+ // In the future, we may want to return a thenable "work" object.
+ let inst;
+ class Counter extends React.Component {
+ state = {counter: 0};
+ increment = () =>
+ this.setState(state => ({counter: state.counter + 1}));
+ render() {
+ inst = this;
+ return this.state.counter;
}
- ReactDOM.render(, container);
- expect(container.textContent).toEqual('0');
+ }
+ ReactDOM.render(, container);
+ expect(container.textContent).toEqual('0');
- const returnValue = ReactDOM.unstable_flushControlled(() => {
- inst.increment();
- return 'something';
- });
- expect(container.textContent).toEqual('1');
- expect(returnValue).toBe(undefined);
+ const returnValue = ReactDOM.unstable_flushControlled(() => {
+ inst.increment();
+ return 'something';
});
+ expect(container.textContent).toEqual('1');
+ expect(returnValue).toBe(undefined);
+ });
- it('ignores discrete events on a pending removed element', () => {
+ it.experimental(
+ 'ignores discrete events on a pending removed element',
+ () => {
const disableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
@@ -446,9 +451,12 @@ describe('ReactDOMFiberAsync', () => {
expect(formSubmitted).toBe(false);
expect(submitButtonRef.current).toBe(null);
- });
+ },
+ );
- it('ignores discrete events on a pending removed event listener', () => {
+ it.experimental(
+ 'ignores discrete events on a pending removed event listener',
+ () => {
const disableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
@@ -512,9 +520,12 @@ describe('ReactDOMFiberAsync', () => {
// Therefore the form should never have been submitted.
expect(formSubmitted).toBe(false);
- });
+ },
+ );
- it('uses the newest discrete events on a pending changed event listener', () => {
+ it.experimental(
+ 'uses the newest discrete events on a pending changed event listener',
+ () => {
const enableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
@@ -572,33 +583,33 @@ describe('ReactDOMFiberAsync', () => {
// Therefore the form should have been submitted.
expect(formSubmitted).toBe(true);
- });
- });
-
- describe('createBlockingRoot', () => {
- it('updates flush without yielding in the next event', () => {
- const root = ReactDOM.createBlockingRoot(container);
-
- function Text(props) {
- Scheduler.unstable_yieldValue(props.text);
- return props.text;
- }
-
- root.render(
- <>
-
-
-
- >,
- );
-
- // Nothing should have rendered yet
- expect(container.textContent).toEqual('');
+ },
+ );
+ });
- // Everything should render immediately in the next event
- expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
- expect(container.textContent).toEqual('ABC');
- });
+ describe('createBlockingRoot', () => {
+ it.experimental('updates flush without yielding in the next event', () => {
+ const root = ReactDOM.createBlockingRoot(container);
+
+ function Text(props) {
+ Scheduler.unstable_yieldValue(props.text);
+ return props.text;
+ }
+
+ root.render(
+ <>
+
+
+
+ >,
+ );
+
+ // Nothing should have rendered yet
+ expect(container.textContent).toEqual('');
+
+ // Everything should render immediately in the next event
+ expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
+ expect(container.textContent).toEqual('ABC');
});
- }
+ });
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
index 3276496202105..5e7e003ebe927 100644
--- a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
@@ -105,8 +105,9 @@ describe('ReactDOMHooks', () => {
expect(labelRef.current.innerHTML).toBe('abc');
});
- if (__EXPERIMENTAL__) {
- it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', () => {
+ it.experimental(
+ 'should not bail out when an update is scheduled from within an event handler in Concurrent Mode',
+ () => {
const {createRef, useCallback, useState} = React;
const Example = ({inputRef, labelRef}) => {
@@ -139,6 +140,6 @@ describe('ReactDOMHooks', () => {
Scheduler.unstable_flushAll();
expect(labelRef.current.innerHTML).toBe('abc');
- });
- }
+ },
+ );
});
diff --git a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
index 834f9af4df437..64ed76d90b5b1 100644
--- a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+++ b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
@@ -500,8 +500,9 @@ describe('ReactDOMServerHydration', () => {
expect(element.textContent).toBe('Hello world');
});
- if (__EXPERIMENTAL__) {
- it('does not re-enter hydration after committing the first one', () => {
+ it.experimental(
+ 'does not re-enter hydration after committing the first one',
+ () => {
let finalHTML = ReactDOMServer.renderToString();
let container = document.createElement('div');
container.innerHTML = finalHTML;
@@ -514,8 +515,8 @@ describe('ReactDOMServerHydration', () => {
// warnings.
root.render();
Scheduler.unstable_flushAll();
- });
- }
+ },
+ );
it('Suspense + hydration in legacy mode', () => {
const element = document.createElement('div');
diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
index e141b4a4dff4c..b840567ca1c2b 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
@@ -125,31 +125,27 @@ describe('ReactTestUtils.act()', () => {
]);
});
- if (__EXPERIMENTAL__) {
- it('warns in blocking mode', () => {
- expect(() => {
- const root = ReactDOM.createBlockingRoot(
- document.createElement('div'),
- );
- root.render();
- Scheduler.unstable_flushAll();
- }).toWarnDev([
- 'An update to App ran an effect, but was not wrapped in act(...)',
- 'An update to App ran an effect, but was not wrapped in act(...)',
- ]);
- });
+ it.experimental('warns in blocking mode', () => {
+ expect(() => {
+ const root = ReactDOM.createBlockingRoot(document.createElement('div'));
+ root.render();
+ Scheduler.unstable_flushAll();
+ }).toWarnDev([
+ 'An update to App ran an effect, but was not wrapped in act(...)',
+ 'An update to App ran an effect, but was not wrapped in act(...)',
+ ]);
+ });
- it('warns in concurrent mode', () => {
- expect(() => {
- const root = ReactDOM.createRoot(document.createElement('div'));
- root.render();
- Scheduler.unstable_flushAll();
- }).toWarnDev([
- 'An update to App ran an effect, but was not wrapped in act(...)',
- 'An update to App ran an effect, but was not wrapped in act(...)',
- ]);
- });
- }
+ it.experimental('warns in concurrent mode', () => {
+ expect(() => {
+ const root = ReactDOM.createRoot(document.createElement('div'));
+ root.render();
+ Scheduler.unstable_flushAll();
+ }).toWarnDev([
+ 'An update to App ran an effect, but was not wrapped in act(...)',
+ 'An update to App ran an effect, but was not wrapped in act(...)',
+ ]);
+ });
});
});
diff --git a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
index fed79d8dc24fb..bf5d4489efc25 100644
--- a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
+++ b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
@@ -27,36 +27,30 @@ it('does not warn when rendering in legacy mode', () => {
}).toWarnDev([]);
});
-if (__EXPERIMENTAL__) {
- it('should warn when rendering in concurrent mode', () => {
- expect(() => {
- ReactDOM.createRoot(document.createElement('div')).render();
- }).toWarnDev(
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers.',
- {withoutStack: true},
- );
- // does not warn twice
- expect(() => {
- ReactDOM.createRoot(document.createElement('div')).render();
- }).toWarnDev([]);
- });
+it.experimental('should warn when rendering in concurrent mode', () => {
+ expect(() => {
+ ReactDOM.createRoot(document.createElement('div')).render();
+ }).toWarnDev(
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
+ 'to guarantee consistent behaviour across tests and browsers.',
+ {withoutStack: true},
+ );
+ // does not warn twice
+ expect(() => {
+ ReactDOM.createRoot(document.createElement('div')).render();
+ }).toWarnDev([]);
+});
- it('should warn when rendering in blocking mode', () => {
- expect(() => {
- ReactDOM.createBlockingRoot(document.createElement('div')).render(
- ,
- );
- }).toWarnDev(
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers.',
- {withoutStack: true},
- );
- // does not warn twice
- expect(() => {
- ReactDOM.createBlockingRoot(document.createElement('div')).render(
- ,
- );
- }).toWarnDev([]);
- });
-}
+it.experimental('should warn when rendering in blocking mode', () => {
+ expect(() => {
+ ReactDOM.createBlockingRoot(document.createElement('div')).render();
+ }).toWarnDev(
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
+ 'to guarantee consistent behaviour across tests and browsers.',
+ {withoutStack: true},
+ );
+ // does not warn twice
+ expect(() => {
+ ReactDOM.createBlockingRoot(document.createElement('div')).render();
+ }).toWarnDev([]);
+});
diff --git a/packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js b/packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js
index 532f2216a2c73..3dc0f33b67bf9 100644
--- a/packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js
+++ b/packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js
@@ -474,281 +474,282 @@ describe('ChangeEventPlugin', () => {
}
});
- if (__EXPERIMENTAL__) {
- describe('concurrent mode', () => {
- beforeEach(() => {
- jest.resetModules();
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
- React = require('react');
- ReactDOM = require('react-dom');
- TestUtils = require('react-dom/test-utils');
- Scheduler = require('scheduler');
- });
+ describe('concurrent mode', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
+ React = require('react');
+ ReactDOM = require('react-dom');
+ TestUtils = require('react-dom/test-utils');
+ Scheduler = require('scheduler');
+ });
- it('text input', () => {
- const root = ReactDOM.createRoot(container);
- let input;
-
- let ops = [];
-
- class ControlledInput extends React.Component {
- state = {value: 'initial'};
- onChange = event => this.setState({value: event.target.value});
- render() {
- ops.push(`render: ${this.state.value}`);
- const controlledValue =
- this.state.value === 'changed' ? 'changed [!]' : this.state.value;
- return (
- (input = el)}
- type="text"
- value={controlledValue}
- onChange={this.onChange}
- />
- );
- }
- }
+ it.experimental('text input', () => {
+ const root = ReactDOM.createRoot(container);
+ let input;
- // Initial mount. Test that this is async.
- root.render();
- // Should not have flushed yet.
- expect(ops).toEqual([]);
- expect(input).toBe(undefined);
- // Flush callbacks.
- Scheduler.unstable_flushAll();
- expect(ops).toEqual(['render: initial']);
- expect(input.value).toBe('initial');
-
- ops = [];
-
- // Trigger a change event.
- setUntrackedValue.call(input, 'changed');
- input.dispatchEvent(
- new Event('input', {bubbles: true, cancelable: true}),
- );
- // Change should synchronously flush
- expect(ops).toEqual(['render: changed']);
- // Value should be the controlled value, not the original one
- expect(input.value).toBe('changed [!]');
- });
-
- it('checkbox input', () => {
- const root = ReactDOM.createRoot(container);
- let input;
-
- let ops = [];
-
- class ControlledInput extends React.Component {
- state = {checked: false};
- onChange = event => {
- this.setState({checked: event.target.checked});
- };
- render() {
- ops.push(`render: ${this.state.checked}`);
- const controlledValue = this.props.reverse
- ? !this.state.checked
- : this.state.checked;
- return (
- (input = el)}
- type="checkbox"
- checked={controlledValue}
- onChange={this.onChange}
- />
- );
- }
+ let ops = [];
+
+ class ControlledInput extends React.Component {
+ state = {value: 'initial'};
+ onChange = event => this.setState({value: event.target.value});
+ render() {
+ ops.push(`render: ${this.state.value}`);
+ const controlledValue =
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
+ return (
+ (input = el)}
+ type="text"
+ value={controlledValue}
+ onChange={this.onChange}
+ />
+ );
}
+ }
- // Initial mount. Test that this is async.
- root.render();
- // Should not have flushed yet.
- expect(ops).toEqual([]);
- expect(input).toBe(undefined);
- // Flush callbacks.
- Scheduler.unstable_flushAll();
- expect(ops).toEqual(['render: false']);
- expect(input.checked).toBe(false);
-
- ops = [];
-
- // Trigger a change event.
- input.dispatchEvent(
- new MouseEvent('click', {bubbles: true, cancelable: true}),
- );
- // Change should synchronously flush
- expect(ops).toEqual(['render: true']);
- expect(input.checked).toBe(true);
-
- // Now let's make sure we're using the controlled value.
- root.render();
- Scheduler.unstable_flushAll();
-
- ops = [];
-
- // Trigger another change event.
- input.dispatchEvent(
- new MouseEvent('click', {bubbles: true, cancelable: true}),
- );
- // Change should synchronously flush
- expect(ops).toEqual(['render: true']);
- expect(input.checked).toBe(false);
- });
-
- it('textarea', () => {
- const root = ReactDOM.createRoot(container);
- let textarea;
-
- let ops = [];
-
- class ControlledTextarea extends React.Component {
- state = {value: 'initial'};
- onChange = event => this.setState({value: event.target.value});
- render() {
- ops.push(`render: ${this.state.value}`);
- const controlledValue =
- this.state.value === 'changed' ? 'changed [!]' : this.state.value;
- return (
-