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 ( -