Codemod it.experimental to gate pragma (#18582)

* Codemod it.experimental to gate pragma

Find-and-replace followed by Prettier

* Delete it.experimental

Removes the API from our test setup script
This commit is contained in:
Andrew Clark
2020-04-13 10:28:59 -07:00
committed by GitHub
parent 42994038ce
commit 8635170ab7
20 changed files with 1194 additions and 1246 deletions
+235 -241
View File
@@ -153,7 +153,8 @@ describe('ReactDOMFiberAsync', () => {
Scheduler = require('scheduler');
});
it.experimental('does not perform deferred updates synchronously', () => {
// @gate experimental
it('does not perform deferred updates synchronously', () => {
const inputRef = React.createRef();
const asyncValueRef = React.createRef();
const syncValueRef = React.createRef();
@@ -208,7 +209,8 @@ describe('ReactDOMFiberAsync', () => {
expect(syncValueRef.current.textContent).toBe('hello');
});
it.experimental('top-level updates are concurrent', () => {
// @gate experimental
it('top-level updates are concurrent', () => {
const root = ReactDOM.createRoot(container);
root.render(<div>Hi</div>);
expect(container.textContent).toEqual('');
@@ -221,7 +223,8 @@ describe('ReactDOMFiberAsync', () => {
expect(container.textContent).toEqual('Bye');
});
it.experimental('deep updates (setState) are concurrent', () => {
// @gate experimental
it('deep updates (setState) are concurrent', () => {
let instance;
class Component extends React.Component {
state = {step: 0};
@@ -243,7 +246,8 @@ describe('ReactDOMFiberAsync', () => {
expect(container.textContent).toEqual('1');
});
it.experimental('flushSync flushes updates before end of the tick', () => {
// @gate experimental
it('flushSync flushes updates before end of the tick', () => {
const ops = [];
let instance;
@@ -291,82 +295,79 @@ describe('ReactDOMFiberAsync', () => {
expect(ops).toEqual(['BC', 'ABCD']);
});
it.experimental(
'flushControlled flushes updates before yielding to browser',
() => {
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;
}
// @gate experimental
it('flushControlled flushes updates before yielding to browser', () => {
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;
}
const root = ReactDOM.createRoot(container);
root.render(<Counter />);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('0');
}
const root = ReactDOM.createRoot(container);
root.render(<Counter />);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('0');
// Test that a normal update is async
// Test that a normal update is async
inst.increment();
expect(container.textContent).toEqual('0');
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('1');
const ops = [];
ReactDOM.unstable_flushControlled(() => {
inst.increment();
expect(container.textContent).toEqual('0');
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('1');
const ops = [];
ReactDOM.unstable_flushControlled(() => {
inst.increment();
ReactDOM.unstable_flushControlled(() => {
inst.increment();
ops.push('end of inner flush: ' + container.textContent);
});
ops.push('end of outer flush: ' + container.textContent);
ops.push('end of inner flush: ' + container.textContent);
});
ops.push('after outer flush: ' + container.textContent);
expect(ops).toEqual([
'end of inner flush: 1',
'end of outer flush: 1',
'after outer flush: 3',
]);
},
);
ops.push('end of outer flush: ' + container.textContent);
});
ops.push('after outer flush: ' + container.textContent);
expect(ops).toEqual([
'end of inner flush: 1',
'end of outer flush: 1',
'after outer flush: 3',
]);
});
it.experimental(
'flushControlled does not flush until end of outermost batchedUpdates',
() => {
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;
}
// @gate experimental
it('flushControlled does not flush until end of outermost batchedUpdates', () => {
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(<Counter />, container);
}
ReactDOM.render(<Counter />, container);
const ops = [];
ReactDOM.unstable_batchedUpdates(() => {
const ops = [];
ReactDOM.unstable_batchedUpdates(() => {
inst.increment();
ReactDOM.unstable_flushControlled(() => {
inst.increment();
ReactDOM.unstable_flushControlled(() => {
inst.increment();
ops.push('end of flushControlled fn: ' + container.textContent);
});
ops.push('end of batchedUpdates fn: ' + container.textContent);
ops.push('end of flushControlled fn: ' + container.textContent);
});
ops.push('after batchedUpdates: ' + container.textContent);
expect(ops).toEqual([
'end of flushControlled fn: 0',
'end of batchedUpdates fn: 0',
'after batchedUpdates: 2',
]);
},
);
ops.push('end of batchedUpdates fn: ' + container.textContent);
});
ops.push('after batchedUpdates: ' + container.textContent);
expect(ops).toEqual([
'end of flushControlled fn: 0',
'end of batchedUpdates fn: 0',
'after batchedUpdates: 2',
]);
});
it.experimental('flushControlled returns nothing', () => {
// @gate experimental
it('flushControlled returns nothing', () => {
// In the future, we may want to return a thenable "work" object.
let inst;
class Counter extends React.Component {
@@ -389,201 +390,193 @@ describe('ReactDOMFiberAsync', () => {
expect(returnValue).toBe(undefined);
});
it.experimental(
'ignores discrete events on a pending removed element',
() => {
const disableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
// @gate experimental
it('ignores discrete events on a pending removed element', () => {
const disableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
let formSubmitted = false;
let formSubmitted = false;
class Form extends React.Component {
state = {active: true};
disableForm = () => {
this.setState({active: false});
};
submitForm = () => {
formSubmitted = true; // This should not get invoked
};
render() {
return (
<div>
<button onClick={this.disableForm} ref={disableButtonRef}>
Disable
</button>
{this.state.active ? (
<button onClick={this.submitForm} ref={submitButtonRef}>
Submit
</button>
) : null}
</div>
);
}
}
const root = ReactDOM.createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
const disableButton = disableButtonRef.current;
expect(disableButton.tagName).toBe('BUTTON');
// Dispatch a click event on the Disable-button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
disableButton.dispatchEvent(firstEvent);
// There should now be a pending update to disable the form.
// This should not have flushed yet since it's in concurrent mode.
const submitButton = submitButtonRef.current;
expect(submitButton.tagName).toBe('BUTTON');
// In the meantime, we can dispatch a new client event on the submit button.
const secondEvent = document.createEvent('Event');
secondEvent.initEvent('click', true, true);
// This should force the pending update to flush which disables the submit button before the event is invoked.
submitButton.dispatchEvent(secondEvent);
// Therefore the form should never have been submitted.
expect(formSubmitted).toBe(false);
expect(submitButtonRef.current).toBe(null);
},
);
it.experimental(
'ignores discrete events on a pending removed event listener',
() => {
const disableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
let formSubmitted = false;
class Form extends React.Component {
state = {active: true};
disableForm = () => {
this.setState({active: false});
};
submitForm = () => {
formSubmitted = true; // This should not get invoked
};
disabledSubmitForm = () => {
// The form is disabled.
};
render() {
return (
<div>
<button onClick={this.disableForm} ref={disableButtonRef}>
Disable
</button>
<button
onClick={
this.state.active
? this.submitForm
: this.disabledSubmitForm
}
ref={submitButtonRef}>
class Form extends React.Component {
state = {active: true};
disableForm = () => {
this.setState({active: false});
};
submitForm = () => {
formSubmitted = true; // This should not get invoked
};
render() {
return (
<div>
<button onClick={this.disableForm} ref={disableButtonRef}>
Disable
</button>
{this.state.active ? (
<button onClick={this.submitForm} ref={submitButtonRef}>
Submit
</button>
</div>
);
}
) : null}
</div>
);
}
}
const root = ReactDOM.createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
const root = ReactDOM.createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
const disableButton = disableButtonRef.current;
expect(disableButton.tagName).toBe('BUTTON');
const disableButton = disableButtonRef.current;
expect(disableButton.tagName).toBe('BUTTON');
// Dispatch a click event on the Disable-button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
disableButton.dispatchEvent(firstEvent);
// Dispatch a click event on the Disable-button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
disableButton.dispatchEvent(firstEvent);
// There should now be a pending update to disable the form.
// There should now be a pending update to disable the form.
// This should not have flushed yet since it's in concurrent mode.
const submitButton = submitButtonRef.current;
expect(submitButton.tagName).toBe('BUTTON');
// This should not have flushed yet since it's in concurrent mode.
const submitButton = submitButtonRef.current;
expect(submitButton.tagName).toBe('BUTTON');
// In the meantime, we can dispatch a new client event on the submit button.
const secondEvent = document.createEvent('Event');
secondEvent.initEvent('click', true, true);
// This should force the pending update to flush which disables the submit button before the event is invoked.
submitButton.dispatchEvent(secondEvent);
// In the meantime, we can dispatch a new client event on the submit button.
const secondEvent = document.createEvent('Event');
secondEvent.initEvent('click', true, true);
// This should force the pending update to flush which disables the submit button before the event is invoked.
submitButton.dispatchEvent(secondEvent);
// Therefore the form should never have been submitted.
expect(formSubmitted).toBe(false);
},
);
// Therefore the form should never have been submitted.
expect(formSubmitted).toBe(false);
it.experimental(
'uses the newest discrete events on a pending changed event listener',
() => {
const enableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
expect(submitButtonRef.current).toBe(null);
});
let formSubmitted = false;
// @gate experimental
it('ignores discrete events on a pending removed event listener', () => {
const disableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
class Form extends React.Component {
state = {active: false};
enableForm = () => {
this.setState({active: true});
};
submitForm = () => {
formSubmitted = true; // This should happen
};
render() {
return (
<div>
<button onClick={this.enableForm} ref={enableButtonRef}>
Enable
</button>
<button
onClick={this.state.active ? this.submitForm : null}
ref={submitButtonRef}>
Submit
</button>{' '}
: null}
</div>
);
}
let formSubmitted = false;
class Form extends React.Component {
state = {active: true};
disableForm = () => {
this.setState({active: false});
};
submitForm = () => {
formSubmitted = true; // This should not get invoked
};
disabledSubmitForm = () => {
// The form is disabled.
};
render() {
return (
<div>
<button onClick={this.disableForm} ref={disableButtonRef}>
Disable
</button>
<button
onClick={
this.state.active ? this.submitForm : this.disabledSubmitForm
}
ref={submitButtonRef}>
Submit
</button>
</div>
);
}
}
const root = ReactDOM.createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
const root = ReactDOM.createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
const enableButton = enableButtonRef.current;
expect(enableButton.tagName).toBe('BUTTON');
const disableButton = disableButtonRef.current;
expect(disableButton.tagName).toBe('BUTTON');
// Dispatch a click event on the Enable-button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
enableButton.dispatchEvent(firstEvent);
// Dispatch a click event on the Disable-button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
disableButton.dispatchEvent(firstEvent);
// There should now be a pending update to enable the form.
// There should now be a pending update to disable the form.
// This should not have flushed yet since it's in concurrent mode.
const submitButton = submitButtonRef.current;
expect(submitButton.tagName).toBe('BUTTON');
// This should not have flushed yet since it's in concurrent mode.
const submitButton = submitButtonRef.current;
expect(submitButton.tagName).toBe('BUTTON');
// In the meantime, we can dispatch a new client event on the submit button.
const secondEvent = document.createEvent('Event');
secondEvent.initEvent('click', true, true);
// This should force the pending update to flush which enables the submit button before the event is invoked.
submitButton.dispatchEvent(secondEvent);
// In the meantime, we can dispatch a new client event on the submit button.
const secondEvent = document.createEvent('Event');
secondEvent.initEvent('click', true, true);
// This should force the pending update to flush which disables the submit button before the event is invoked.
submitButton.dispatchEvent(secondEvent);
// Therefore the form should have been submitted.
expect(formSubmitted).toBe(true);
},
);
// Therefore the form should never have been submitted.
expect(formSubmitted).toBe(false);
});
// @gate experimental
it('uses the newest discrete events on a pending changed event listener', () => {
const enableButtonRef = React.createRef();
const submitButtonRef = React.createRef();
let formSubmitted = false;
class Form extends React.Component {
state = {active: false};
enableForm = () => {
this.setState({active: true});
};
submitForm = () => {
formSubmitted = true; // This should happen
};
render() {
return (
<div>
<button onClick={this.enableForm} ref={enableButtonRef}>
Enable
</button>
<button
onClick={this.state.active ? this.submitForm : null}
ref={submitButtonRef}>
Submit
</button>{' '}
: null}
</div>
);
}
}
const root = ReactDOM.createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
const enableButton = enableButtonRef.current;
expect(enableButton.tagName).toBe('BUTTON');
// Dispatch a click event on the Enable-button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
enableButton.dispatchEvent(firstEvent);
// There should now be a pending update to enable the form.
// This should not have flushed yet since it's in concurrent mode.
const submitButton = submitButtonRef.current;
expect(submitButton.tagName).toBe('BUTTON');
// In the meantime, we can dispatch a new client event on the submit button.
const secondEvent = document.createEvent('Event');
secondEvent.initEvent('click', true, true);
// This should force the pending update to flush which enables the submit button before the event is invoked.
submitButton.dispatchEvent(secondEvent);
// Therefore the form should have been submitted.
expect(formSubmitted).toBe(true);
});
});
it('regression test: does not drop passive effects across roots (#17066)', () => {
@@ -617,7 +610,8 @@ describe('ReactDOMFiberAsync', () => {
});
describe('createBlockingRoot', () => {
it.experimental('updates flush without yielding in the next event', () => {
// @gate experimental
it('updates flush without yielding in the next event', () => {
const root = ReactDOM.createBlockingRoot(container);
function Text(props) {
+30 -32
View File
@@ -105,41 +105,39 @@ describe('ReactDOMHooks', () => {
expect(labelRef.current.innerHTML).toBe('abc');
});
it.experimental(
'should not bail out when an update is scheduled from within an event handler in Concurrent Mode',
() => {
const {createRef, useCallback, useState} = React;
// @gate experimental
it('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}) => {
const [text, setText] = useState('');
const handleInput = useCallback(event => {
setText(event.target.value);
});
const Example = ({inputRef, labelRef}) => {
const [text, setText] = useState('');
const handleInput = useCallback(event => {
setText(event.target.value);
});
return (
<>
<input ref={inputRef} onInput={handleInput} />
<label ref={labelRef}>{text}</label>
</>
);
};
const inputRef = createRef();
const labelRef = createRef();
const root = ReactDOM.createRoot(container);
root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
Scheduler.unstable_flushAll();
inputRef.current.value = 'abc';
inputRef.current.dispatchEvent(
new Event('input', {bubbles: true, cancelable: true}),
return (
<>
<input ref={inputRef} onInput={handleInput} />
<label ref={labelRef}>{text}</label>
</>
);
};
Scheduler.unstable_flushAll();
const inputRef = createRef();
const labelRef = createRef();
expect(labelRef.current.innerHTML).toBe('abc');
},
);
const root = ReactDOM.createRoot(container);
root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
Scheduler.unstable_flushAll();
inputRef.current.value = 'abc';
inputRef.current.dispatchEvent(
new Event('input', {bubbles: true, cancelable: true}),
);
Scheduler.unstable_flushAll();
expect(labelRef.current.innerHTML).toBe('abc');
});
});
@@ -491,23 +491,21 @@ describe('ReactDOMServerHydration', () => {
expect(element.textContent).toBe('Hello world');
});
it.experimental(
'does not re-enter hydration after committing the first one',
() => {
const finalHTML = ReactDOMServer.renderToString(<div />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<div />);
Scheduler.unstable_flushAll();
root.render(null);
Scheduler.unstable_flushAll();
// This should not reenter hydration state and therefore not trigger hydration
// warnings.
root.render(<div />);
Scheduler.unstable_flushAll();
},
);
// @gate experimental
it('does not re-enter hydration after committing the first one', () => {
const finalHTML = ReactDOMServer.renderToString(<div />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<div />);
Scheduler.unstable_flushAll();
root.render(null);
Scheduler.unstable_flushAll();
// This should not reenter hydration state and therefore not trigger hydration
// warnings.
root.render(<div />);
Scheduler.unstable_flushAll();
});
it('Suspense + hydration in legacy mode', () => {
const element = document.createElement('div');
+4 -2
View File
@@ -124,7 +124,8 @@ describe('ReactTestUtils.act()', () => {
]);
});
it.experimental('warns in blocking mode', () => {
// @gate experimental
it('warns in blocking mode', () => {
expect(() => {
const root = ReactDOM.createBlockingRoot(document.createElement('div'));
root.render(<App />);
@@ -134,7 +135,8 @@ describe('ReactTestUtils.act()', () => {
]);
});
it.experimental('warns in concurrent mode', () => {
// @gate experimental
it('warns in concurrent mode', () => {
expect(() => {
const root = ReactDOM.createRoot(document.createElement('div'));
root.render(<App />);
@@ -27,7 +27,8 @@ it('does not warn when rendering in legacy mode', () => {
}).toErrorDev([]);
});
it.experimental('should warn when rendering in concurrent mode', () => {
// @gate experimental
it('should warn when rendering in concurrent mode', () => {
expect(() => {
ReactDOM.createRoot(document.createElement('div')).render(<App />);
}).toErrorDev(
@@ -41,7 +42,8 @@ it.experimental('should warn when rendering in concurrent mode', () => {
}).toErrorDev([]);
});
it.experimental('should warn when rendering in blocking mode', () => {
// @gate experimental
it('should warn when rendering in blocking mode', () => {
expect(() => {
ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
}).toErrorDev(
+49 -51
View File
@@ -1287,64 +1287,62 @@ describe('ReactUpdates', () => {
expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
});
it.experimental(
'delays sync updates inside hidden subtrees in Concurrent Mode',
() => {
const container = document.createElement('div');
// @gate experimental
it('delays sync updates inside hidden subtrees in Concurrent Mode', () => {
const container = document.createElement('div');
function Baz() {
Scheduler.unstable_yieldValue('Baz');
return <p>baz</p>;
}
function Baz() {
Scheduler.unstable_yieldValue('Baz');
return <p>baz</p>;
}
let setCounter;
function Bar() {
const [counter, _setCounter] = React.useState(0);
setCounter = _setCounter;
Scheduler.unstable_yieldValue('Bar');
return <p>bar {counter}</p>;
}
let setCounter;
function Bar() {
const [counter, _setCounter] = React.useState(0);
setCounter = _setCounter;
Scheduler.unstable_yieldValue('Bar');
return <p>bar {counter}</p>;
}
function Foo() {
Scheduler.unstable_yieldValue('Foo');
React.useEffect(() => {
Scheduler.unstable_yieldValue('Foo#effect');
});
return (
<div>
<div hidden={true}>
<Bar />
</div>
<Baz />
function Foo() {
Scheduler.unstable_yieldValue('Foo');
React.useEffect(() => {
Scheduler.unstable_yieldValue('Foo#effect');
});
return (
<div>
<div hidden={true}>
<Bar />
</div>
);
}
const root = ReactDOM.createRoot(container);
let hiddenDiv;
act(() => {
root.render(<Foo />);
expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']);
hiddenDiv = container.firstChild.firstChild;
expect(hiddenDiv.hidden).toBe(true);
expect(hiddenDiv.innerHTML).toBe('');
// Run offscreen update
expect(Scheduler).toFlushAndYield(['Bar']);
expect(hiddenDiv.hidden).toBe(true);
expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
});
ReactDOM.flushSync(() => {
setCounter(1);
});
// Should not flush yet
expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
<Baz />
</div>
);
}
const root = ReactDOM.createRoot(container);
let hiddenDiv;
act(() => {
root.render(<Foo />);
expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']);
hiddenDiv = container.firstChild.firstChild;
expect(hiddenDiv.hidden).toBe(true);
expect(hiddenDiv.innerHTML).toBe('');
// Run offscreen update
expect(Scheduler).toFlushAndYield(['Bar']);
expect(hiddenDiv.innerHTML).toBe('<p>bar 1</p>');
},
);
expect(hiddenDiv.hidden).toBe(true);
expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
});
ReactDOM.flushSync(() => {
setCounter(1);
});
// Should not flush yet
expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
// Run offscreen update
expect(Scheduler).toFlushAndYield(['Bar']);
expect(hiddenDiv.innerHTML).toBe('<p>bar 1</p>');
});
it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
class Foo extends React.Component {
@@ -432,87 +432,85 @@ describe('DOMModernPluginEventSystem', () => {
expect(log).toEqual(['second', 'first']);
});
it.experimental(
'does not invoke an event on a parent tree when a subtree is dehydrated',
async () => {
let suspend = false;
let resolve;
const promise = new Promise(
resolvePromise => (resolve = resolvePromise),
// @gate experimental
it('does not invoke an event on a parent tree when a subtree is dehydrated', async () => {
let suspend = false;
let resolve;
const promise = new Promise(
resolvePromise => (resolve = resolvePromise),
);
let clicks = 0;
const childSlotRef = React.createRef();
function Parent() {
return <div onClick={() => clicks++} ref={childSlotRef} />;
}
function Child({text}) {
if (suspend) {
throw promise;
} else {
return <a>Click me</a>;
}
}
function App() {
// The root is a Suspense boundary.
return (
<React.Suspense fallback="Loading...">
<Child />
</React.Suspense>
);
}
let clicks = 0;
const childSlotRef = React.createRef();
suspend = false;
const finalHTML = ReactDOMServer.renderToString(<App />);
function Parent() {
return <div onClick={() => clicks++} ref={childSlotRef} />;
}
const parentContainer = document.createElement('div');
const childContainer = document.createElement('div');
function Child({text}) {
if (suspend) {
throw promise;
} else {
return <a>Click me</a>;
}
}
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(parentContainer);
function App() {
// The root is a Suspense boundary.
return (
<React.Suspense fallback="Loading...">
<Child />
</React.Suspense>
);
}
// We're going to use a different root as a parent.
// This lets us detect whether an event goes through React's event system.
const parentRoot = ReactDOM.createRoot(parentContainer);
parentRoot.render(<Parent />);
Scheduler.unstable_flushAll();
suspend = false;
const finalHTML = ReactDOMServer.renderToString(<App />);
childSlotRef.current.appendChild(childContainer);
const parentContainer = document.createElement('div');
const childContainer = document.createElement('div');
childContainer.innerHTML = finalHTML;
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(parentContainer);
const a = childContainer.getElementsByTagName('a')[0];
// We're going to use a different root as a parent.
// This lets us detect whether an event goes through React's event system.
const parentRoot = ReactDOM.createRoot(parentContainer);
parentRoot.render(<Parent />);
Scheduler.unstable_flushAll();
suspend = true;
childSlotRef.current.appendChild(childContainer);
// Hydrate asynchronously.
const root = ReactDOM.createRoot(childContainer, {hydrate: true});
root.render(<App />);
jest.runAllTimers();
Scheduler.unstable_flushAll();
childContainer.innerHTML = finalHTML;
// The Suspense boundary is not yet hydrated.
a.click();
expect(clicks).toBe(0);
const a = childContainer.getElementsByTagName('a')[0];
// Resolving the promise so that rendering can complete.
suspend = false;
resolve();
await promise;
suspend = true;
Scheduler.unstable_flushAll();
jest.runAllTimers();
// Hydrate asynchronously.
const root = ReactDOM.createRoot(childContainer, {hydrate: true});
root.render(<App />);
jest.runAllTimers();
Scheduler.unstable_flushAll();
// We're now full hydrated.
// The Suspense boundary is not yet hydrated.
a.click();
expect(clicks).toBe(0);
expect(clicks).toBe(1);
// Resolving the promise so that rendering can complete.
suspend = false;
resolve();
await promise;
Scheduler.unstable_flushAll();
jest.runAllTimers();
// We're now full hydrated.
expect(clicks).toBe(1);
document.body.removeChild(parentContainer);
},
);
document.body.removeChild(parentContainer);
});
it('handle click events on dynamic portals', () => {
const log = [];
@@ -1679,55 +1677,53 @@ describe('DOMModernPluginEventSystem', () => {
expect(targetListerner4).toHaveBeenCalledTimes(1);
});
it.experimental(
'should work with concurrent mode updates',
async () => {
const log = [];
const ref = React.createRef();
// @gate experimental
it('should work with concurrent mode updates', async () => {
const log = [];
const ref = React.createRef();
function Test({counter}) {
const click = ReactDOM.unstable_useEvent('click');
function Test({counter}) {
const click = ReactDOM.unstable_useEvent('click');
React.useLayoutEffect(() => {
click.setListener(ref.current, () => {
log.push({counter});
});
React.useLayoutEffect(() => {
click.setListener(ref.current, () => {
log.push({counter});
});
});
Scheduler.unstable_yieldValue('Test');
return <button ref={ref}>Press me</button>;
}
Scheduler.unstable_yieldValue('Test');
return <button ref={ref}>Press me</button>;
}
const root = ReactDOM.createRoot(container);
root.render(<Test counter={0} />);
const root = ReactDOM.createRoot(container);
root.render(<Test counter={0} />);
expect(Scheduler).toFlushAndYield(['Test']);
expect(Scheduler).toFlushAndYield(['Test']);
// Click the button
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 0}]);
// Click the button
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 0}]);
// Clear log
log.length = 0;
// Clear log
log.length = 0;
// Increase counter
root.render(<Test counter={1} />);
// Yield before committing
expect(Scheduler).toFlushAndYieldThrough(['Test']);
// Increase counter
root.render(<Test counter={1} />);
// Yield before committing
expect(Scheduler).toFlushAndYieldThrough(['Test']);
// Click the button again
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 0}]);
// Click the button again
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 0}]);
// Clear log
log.length = 0;
// Clear log
log.length = 0;
// Commit
expect(Scheduler).toFlushAndYield([]);
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 1}]);
},
);
// Commit
expect(Scheduler).toFlushAndYield([]);
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 1}]);
});
it('should correctly work for a basic "click" listener that upgrades', () => {
const clickEvent = jest.fn();
@@ -2259,86 +2255,82 @@ describe('DOMModernPluginEventSystem', () => {
expect(log).toEqual(['beforeblur', 'afterblur']);
});
it.experimental(
'beforeblur and afterblur are called after a focused element is suspended',
() => {
const log = [];
// We have to persist here because we want to read relatedTarget later.
const onAfterBlur = jest.fn(e => {
e.persist();
log.push(e.type);
});
const onBeforeBlur = jest.fn(e => log.push(e.type));
const innerRef = React.createRef();
const Suspense = React.Suspense;
let suspend = false;
let resolve;
const promise = new Promise(
resolvePromise => (resolve = resolvePromise),
);
// @gate experimental
it('beforeblur and afterblur are called after a focused element is suspended', () => {
const log = [];
// We have to persist here because we want to read relatedTarget later.
const onAfterBlur = jest.fn(e => {
e.persist();
log.push(e.type);
});
const onBeforeBlur = jest.fn(e => log.push(e.type));
const innerRef = React.createRef();
const Suspense = React.Suspense;
let suspend = false;
let resolve;
const promise = new Promise(
resolvePromise => (resolve = resolvePromise),
);
function Child() {
if (suspend) {
throw promise;
} else {
return <input ref={innerRef} />;
}
function Child() {
if (suspend) {
throw promise;
} else {
return <input ref={innerRef} />;
}
}
const Component = () => {
const ref = React.useRef(null);
const afterBlurHandle = ReactDOM.unstable_useEvent('afterblur');
const beforeBlurHandle = ReactDOM.unstable_useEvent(
'beforeblur',
);
const Component = () => {
const ref = React.useRef(null);
const afterBlurHandle = ReactDOM.unstable_useEvent('afterblur');
const beforeBlurHandle = ReactDOM.unstable_useEvent('beforeblur');
React.useEffect(() => {
afterBlurHandle.setListener(document, onAfterBlur);
beforeBlurHandle.setListener(ref.current, onBeforeBlur);
});
return (
<div ref={ref}>
<Suspense fallback="Loading...">
<Child />
</Suspense>
</div>
);
};
const container2 = document.createElement('div');
document.body.appendChild(container2);
const root = ReactDOM.createRoot(container2);
ReactTestUtils.act(() => {
root.render(<Component />);
React.useEffect(() => {
afterBlurHandle.setListener(document, onAfterBlur);
beforeBlurHandle.setListener(ref.current, onBeforeBlur);
});
jest.runAllTimers();
const inner = innerRef.current;
const target = createEventTarget(inner);
target.focus();
expect(onBeforeBlur).toHaveBeenCalledTimes(0);
expect(onAfterBlur).toHaveBeenCalledTimes(0);
suspend = true;
ReactTestUtils.act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(onBeforeBlur).toHaveBeenCalledTimes(1);
expect(onAfterBlur).toHaveBeenCalledTimes(1);
expect(onAfterBlur).toHaveBeenCalledWith(
expect.objectContaining({relatedTarget: inner}),
return (
<div ref={ref}>
<Suspense fallback="Loading...">
<Child />
</Suspense>
</div>
);
resolve();
expect(log).toEqual(['beforeblur', 'afterblur']);
};
document.body.removeChild(container2);
},
);
const container2 = document.createElement('div');
document.body.appendChild(container2);
const root = ReactDOM.createRoot(container2);
ReactTestUtils.act(() => {
root.render(<Component />);
});
jest.runAllTimers();
const inner = innerRef.current;
const target = createEventTarget(inner);
target.focus();
expect(onBeforeBlur).toHaveBeenCalledTimes(0);
expect(onAfterBlur).toHaveBeenCalledTimes(0);
suspend = true;
ReactTestUtils.act(() => {
root.render(<Component />);
});
jest.runAllTimers();
expect(onBeforeBlur).toHaveBeenCalledTimes(1);
expect(onAfterBlur).toHaveBeenCalledTimes(1);
expect(onAfterBlur).toHaveBeenCalledWith(
expect.objectContaining({relatedTarget: inner}),
);
resolve();
expect(log).toEqual(['beforeblur', 'afterblur']);
document.body.removeChild(container2);
});
describe('Compatibility with Scopes API', () => {
beforeEach(() => {
@@ -789,7 +789,8 @@ describe('DOMEventResponderSystem', () => {
buttonRef.current.dispatchEvent(createEvent('foobar'));
});
it.experimental('should work with concurrent mode updates', async () => {
// @gate experimental
it('should work with concurrent mode updates', async () => {
const log = [];
const TestResponder = createEventResponder({
targetEventTypes: ['click'],
@@ -477,7 +477,8 @@ describe('ChangeEventPlugin', () => {
Scheduler = require('scheduler');
});
it.experimental('text input', () => {
// @gate experimental
it('text input', () => {
const root = ReactDOM.createRoot(container);
let input;
@@ -519,7 +520,8 @@ describe('ChangeEventPlugin', () => {
expect(input.value).toBe('changed [!]');
});
it.experimental('checkbox input', () => {
// @gate experimental
it('checkbox input', () => {
const root = ReactDOM.createRoot(container);
let input;
@@ -574,7 +576,8 @@ describe('ChangeEventPlugin', () => {
expect(input.checked).toBe(false);
});
it.experimental('textarea', () => {
// @gate experimental
it('textarea', () => {
const root = ReactDOM.createRoot(container);
let textarea;
@@ -616,7 +619,8 @@ describe('ChangeEventPlugin', () => {
expect(textarea.value).toBe('changed [!]');
});
it.experimental('parent of input', () => {
// @gate experimental
it('parent of input', () => {
const root = ReactDOM.createRoot(container);
let input;
@@ -662,7 +666,8 @@ describe('ChangeEventPlugin', () => {
expect(input.value).toBe('changed [!]');
});
it.experimental('is async for non-input events', () => {
// @gate experimental
it('is async for non-input events', () => {
const root = ReactDOM.createRoot(container);
let input;
@@ -711,50 +716,48 @@ describe('ChangeEventPlugin', () => {
expect(input.value).toBe('');
});
it.experimental(
'mouse enter/leave should be user-blocking but not discrete',
async () => {
// This is currently behind a feature flag
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
TestUtils = require('react-dom/test-utils');
Scheduler = require('scheduler');
// @gate experimental
it('mouse enter/leave should be user-blocking but not discrete', async () => {
// This is currently behind a feature flag
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
TestUtils = require('react-dom/test-utils');
Scheduler = require('scheduler');
const {act} = TestUtils;
const {useState} = React;
const {act} = TestUtils;
const {useState} = React;
const root = ReactDOM.createRoot(container);
const root = ReactDOM.createRoot(container);
const target = React.createRef(null);
function Foo() {
const [isHover, setHover] = useState(false);
return (
<div
ref={target}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}>
{isHover ? 'hovered' : 'not hovered'}
</div>
);
}
const target = React.createRef(null);
function Foo() {
const [isHover, setHover] = useState(false);
return (
<div
ref={target}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}>
{isHover ? 'hovered' : 'not hovered'}
</div>
);
}
await act(async () => {
root.render(<Foo />);
});
expect(container.textContent).toEqual('not hovered');
await act(async () => {
root.render(<Foo />);
});
expect(container.textContent).toEqual('not hovered');
await act(async () => {
const mouseOverEvent = document.createEvent('MouseEvents');
mouseOverEvent.initEvent('mouseover', true, true);
target.current.dispatchEvent(mouseOverEvent);
await act(async () => {
const mouseOverEvent = document.createEvent('MouseEvents');
mouseOverEvent.initEvent('mouseover', true, true);
target.current.dispatchEvent(mouseOverEvent);
// 3s should be enough to expire the updates
Scheduler.unstable_advanceTime(3000);
expect(Scheduler).toFlushExpired([]);
expect(container.textContent).toEqual('hovered');
});
},
);
// 3s should be enough to expire the updates
Scheduler.unstable_advanceTime(3000);
expect(Scheduler).toFlushExpired([]);
expect(container.textContent).toEqual('hovered');
});
});
});
});
@@ -236,138 +236,135 @@ describe('SimpleEventPlugin', function() {
Scheduler = require('scheduler');
});
it.experimental(
'flushes pending interactive work before extracting event handler',
() => {
container = document.createElement('div');
const root = ReactDOM.createRoot(container);
document.body.appendChild(container);
// @gate experimental
it('flushes pending interactive work before extracting event handler', () => {
container = document.createElement('div');
const root = ReactDOM.createRoot(container);
document.body.appendChild(container);
let button;
class Button extends React.Component {
state = {disabled: false};
onClick = () => {
// Perform some side-effect
Scheduler.unstable_yieldValue('Side-effect');
// Disable the button
this.setState({disabled: true});
};
render() {
Scheduler.unstable_yieldValue(
`render button: ${this.state.disabled ? 'disabled' : 'enabled'}`,
);
return (
<button
ref={el => (button = el)}
// Handler is removed after the first click
onClick={this.state.disabled ? null : this.onClick}
/>
);
}
}
// Initial mount
root.render(<Button />);
// Should not have flushed yet because it's async
expect(Scheduler).toHaveYielded([]);
expect(button).toBe(undefined);
// Flush async work
expect(Scheduler).toFlushAndYield(['render button: enabled']);
function click() {
button.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
let button;
class Button extends React.Component {
state = {disabled: false};
onClick = () => {
// Perform some side-effect
Scheduler.unstable_yieldValue('Side-effect');
// Disable the button
this.setState({disabled: true});
};
render() {
Scheduler.unstable_yieldValue(
`render button: ${this.state.disabled ? 'disabled' : 'enabled'}`,
);
return (
<button
ref={el => (button = el)}
// Handler is removed after the first click
onClick={this.state.disabled ? null : this.onClick}
/>
);
}
}
// Click the button to trigger the side-effect
click();
expect(Scheduler).toHaveYielded([
// The handler fired
'Side-effect',
// but the component did not re-render yet, because it's async
]);
// Initial mount
root.render(<Button />);
// Should not have flushed yet because it's async
expect(Scheduler).toHaveYielded([]);
expect(button).toBe(undefined);
// Flush async work
expect(Scheduler).toFlushAndYield(['render button: enabled']);
// Click the button again
click();
expect(Scheduler).toHaveYielded([
// Before handling this second click event, the previous interactive
// update is flushed
'render button: disabled',
// The event handler was removed from the button, so there's no second
// side-effect
]);
function click() {
button.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
);
}
// The handler should not fire again no matter how many times we
// click the handler.
click();
click();
click();
click();
click();
expect(Scheduler).toFlushAndYield([]);
},
);
// Click the button to trigger the side-effect
click();
expect(Scheduler).toHaveYielded([
// The handler fired
'Side-effect',
// but the component did not re-render yet, because it's async
]);
it.experimental(
'end result of many interactive updates is deterministic',
() => {
container = document.createElement('div');
const root = ReactDOM.createRoot(container);
document.body.appendChild(container);
// Click the button again
click();
expect(Scheduler).toHaveYielded([
// Before handling this second click event, the previous interactive
// update is flushed
'render button: disabled',
// The event handler was removed from the button, so there's no second
// side-effect
]);
let button;
class Button extends React.Component {
state = {count: 0};
render() {
return (
<button
ref={el => (button = el)}
onClick={() =>
// Intentionally not using the updater form here
this.setState({count: this.state.count + 1})
}>
Count: {this.state.count}
</button>
);
}
}
// The handler should not fire again no matter how many times we
// click the handler.
click();
click();
click();
click();
click();
expect(Scheduler).toFlushAndYield([]);
});
// Initial mount
root.render(<Button />);
// Should not have flushed yet because it's async
expect(button).toBe(undefined);
// Flush async work
Scheduler.unstable_flushAll();
expect(button.textContent).toEqual('Count: 0');
// @gate experimental
it('end result of many interactive updates is deterministic', () => {
container = document.createElement('div');
const root = ReactDOM.createRoot(container);
document.body.appendChild(container);
function click() {
button.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
let button;
class Button extends React.Component {
state = {count: 0};
render() {
return (
<button
ref={el => (button = el)}
onClick={() =>
// Intentionally not using the updater form here
this.setState({count: this.state.count + 1})
}>
Count: {this.state.count}
</button>
);
}
}
// Click the button a single time
click();
// The counter should not have updated yet because it's async
expect(button.textContent).toEqual('Count: 0');
// Initial mount
root.render(<Button />);
// Should not have flushed yet because it's async
expect(button).toBe(undefined);
// Flush async work
Scheduler.unstable_flushAll();
expect(button.textContent).toEqual('Count: 0');
// Click the button many more times
click();
click();
click();
click();
click();
click();
function click() {
button.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
);
}
// Flush the remaining work
Scheduler.unstable_flushAll();
// The counter should equal the total number of clicks
expect(button.textContent).toEqual('Count: 7');
},
);
// Click the button a single time
click();
// The counter should not have updated yet because it's async
expect(button.textContent).toEqual('Count: 0');
it.experimental('flushes discrete updates in order', () => {
// Click the button many more times
click();
click();
click();
click();
click();
click();
// Flush the remaining work
Scheduler.unstable_flushAll();
// The counter should equal the total number of clicks
expect(button.textContent).toEqual('Count: 7');
});
// @gate experimental
it('flushes discrete updates in order', () => {
container = document.createElement('div');
document.body.appendChild(container);
@@ -93,7 +93,8 @@ describe('ReactFlightDOMRelay', () => {
});
});
it.experimental('can transfer a Block to the client and render there', () => {
// @gate experimental
it('can transfer a Block to the client and render there', () => {
function load(firstName, lastName) {
return {name: firstName + ' ' + lastName};
}
@@ -140,7 +140,8 @@ describe('ReactFlightDOM', () => {
});
});
it.experimental('should resolve the root', async () => {
// @gate experimental
it('should resolve the root', async () => {
const {Suspense} = React;
// Model
@@ -191,7 +192,8 @@ describe('ReactFlightDOM', () => {
);
});
it.experimental('should not get confused by $', async () => {
// @gate experimental
it('should not get confused by $', async () => {
const {Suspense} = React;
// Model
@@ -227,7 +229,8 @@ describe('ReactFlightDOM', () => {
expect(container.innerHTML).toBe('<p>$1</p>');
});
it.experimental('should not get confused by @', async () => {
// @gate experimental
it('should not get confused by @', async () => {
const {Suspense} = React;
// Model
@@ -263,7 +266,8 @@ describe('ReactFlightDOM', () => {
expect(container.innerHTML).toBe('<p>@div</p>');
});
it.experimental('should progressively reveal Blocks', async () => {
// @gate experimental
it('should progressively reveal Blocks', async () => {
const {Suspense} = React;
class ErrorBoundary extends React.Component {
@@ -344,69 +344,65 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
);
});
it.experimental(
'is called after a focused suspended element is hidden',
() => {
const Suspense = React.Suspense;
let suspend = false;
let resolve;
const promise = new Promise(
resolvePromise => (resolve = resolvePromise),
);
// @gate experimental
it('is called after a focused suspended element is hidden', () => {
const Suspense = React.Suspense;
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
function Child() {
if (suspend) {
throw promise;
} else {
return <input ref={innerRef} />;
}
function Child() {
if (suspend) {
throw promise;
} else {
return <input ref={innerRef} />;
}
}
const Component = ({show}) => {
const listener = useFocusWithin({
onBeforeBlurWithin,
onBlurWithin,
});
const Component = ({show}) => {
const listener = useFocusWithin({
onBeforeBlurWithin,
onBlurWithin,
});
return (
<div DEPRECATED_flareListeners={listener}>
<Suspense fallback="Loading...">
<Child />
</Suspense>
</div>
);
};
const container2 = document.createElement('div');
document.body.appendChild(container2);
const root = ReactDOM.createRoot(container2);
root.render(<Component />);
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(container2.innerHTML).toBe('<div><input></div>');
const inner = innerRef.current;
const target = createEventTarget(inner);
target.keydown({key: 'Tab'});
target.focus();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onBlurWithin).toHaveBeenCalledTimes(0);
suspend = true;
root.render(<Component />);
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(container2.innerHTML).toBe(
'<div><input style="display: none;">Loading...</div>',
return (
<div DEPRECATED_flareListeners={listener}>
<Suspense fallback="Loading...">
<Child />
</Suspense>
</div>
);
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
expect(onBlurWithin).toHaveBeenCalledTimes(1);
resolve();
};
document.body.removeChild(container2);
},
);
const container2 = document.createElement('div');
document.body.appendChild(container2);
const root = ReactDOM.createRoot(container2);
root.render(<Component />);
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(container2.innerHTML).toBe('<div><input></div>');
const inner = innerRef.current;
const target = createEventTarget(inner);
target.keydown({key: 'Tab'});
target.focus();
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
expect(onBlurWithin).toHaveBeenCalledTimes(0);
suspend = true;
root.render(<Component />);
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(container2.innerHTML).toBe(
'<div><input style="display: none;">Loading...</div>',
);
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
expect(onBlurWithin).toHaveBeenCalledTimes(1);
resolve();
document.body.removeChild(container2);
});
});
it('expect displayName to show up for event component', () => {
@@ -793,7 +793,8 @@ describe('Input event responder', () => {
});
describe('concurrent mode', () => {
it.experimental('text input', () => {
// @gate experimental
it('text input', () => {
const root = ReactDOM.createRoot(container);
let input;
@@ -848,7 +849,8 @@ describe('Input event responder', () => {
expect(input.value).toBe('changed [!]');
});
it.experimental('checkbox input', () => {
// @gate experimental
it('checkbox input', () => {
const root = ReactDOM.createRoot(container);
let input;
@@ -916,7 +918,8 @@ describe('Input event responder', () => {
expect(input.checked).toBe(false);
});
it.experimental('textarea', () => {
// @gate experimental
it('textarea', () => {
const root = ReactDOM.createRoot(container);
let textarea;
+97 -98
View File
@@ -49,7 +49,8 @@ describe('ReactBlocks', () => {
};
});
it.experimental('renders a simple component', () => {
// @gate experimental
it('renders a simple component', () => {
function User(props, data) {
return <div>{typeof data}</div>;
}
@@ -71,7 +72,8 @@ describe('ReactBlocks', () => {
expect(ReactNoop).toMatchRenderedOutput(<div>undefined</div>);
});
it.experimental('prints the name of the render function in warnings', () => {
// @gate experimental
it('prints the name of the render function in warnings', () => {
function load(firstName) {
return {
name: firstName,
@@ -108,7 +110,8 @@ describe('ReactBlocks', () => {
);
});
it.experimental('renders a component with a suspending load', async () => {
// @gate experimental
it('renders a component with a suspending load', async () => {
function load(id) {
return {
id: id,
@@ -147,115 +150,111 @@ describe('ReactBlocks', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
});
it.experimental(
'does not support a lazy wrapper around a chunk',
async () => {
function load(id) {
return {
id: id,
name: readString('Sebastian'),
};
}
// @gate experimental
it('does not support a lazy wrapper around a chunk', async () => {
function load(id) {
return {
id: id,
name: readString('Sebastian'),
};
}
function Render(props, data) {
return (
<span>
{props.title}: {data.name}
</span>
);
}
const loadUser = block(Render, load);
function App({User}) {
return (
<Suspense fallback={'Loading...'}>
<User title="Name" />
</Suspense>
);
}
let resolveLazy;
const LazyUser = React.lazy(
() =>
new Promise(resolve => {
resolveLazy = function() {
resolve({
default: loadUser(123),
});
};
}),
function Render(props, data) {
return (
<span>
{props.title}: {data.name}
</span>
);
}
await ReactNoop.act(async () => {
ReactNoop.render(<App User={LazyUser} />);
});
const loadUser = block(Render, load);
expect(ReactNoop).toMatchRenderedOutput('Loading...');
// Resolve the component.
await resolveLazy();
expect(Scheduler).toFlushAndThrow(
'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
'Lazy element type must resolve to a class or function.' +
(__DEV__
? ' Did you wrap a component in React.lazy() more than once?'
: ''),
function App({User}) {
return (
<Suspense fallback={'Loading...'}>
<User title="Name" />
</Suspense>
);
},
);
}
it.experimental(
'can receive updated data for the same component',
async () => {
function load(firstName) {
return {
name: firstName,
};
}
let resolveLazy;
const LazyUser = React.lazy(
() =>
new Promise(resolve => {
resolveLazy = function() {
resolve({
default: loadUser(123),
});
};
}),
);
function Render(props, data) {
const [initialName] = useState(data.name);
return (
<>
<span>Initial name: {initialName}</span>
<span>Latest name: {data.name}</span>
</>
);
}
await ReactNoop.act(async () => {
ReactNoop.render(<App User={LazyUser} />);
});
const loadUser = block(Render, load);
expect(ReactNoop).toMatchRenderedOutput('Loading...');
function App({User}) {
return (
<Suspense fallback={'Loading...'}>
<User title="Name" />
</Suspense>
);
}
// Resolve the component.
await resolveLazy();
await ReactNoop.act(async () => {
ReactNoop.render(<App User={loadUser('Sebastian')} />);
});
expect(Scheduler).toFlushAndThrow(
'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
'Lazy element type must resolve to a class or function.' +
(__DEV__
? ' Did you wrap a component in React.lazy() more than once?'
: ''),
);
});
expect(ReactNoop).toMatchRenderedOutput(
// @gate experimental
it('can receive updated data for the same component', async () => {
function load(firstName) {
return {
name: firstName,
};
}
function Render(props, data) {
const [initialName] = useState(data.name);
return (
<>
<span>Initial name: Sebastian</span>
<span>Latest name: Sebastian</span>
</>,
<span>Initial name: {initialName}</span>
<span>Latest name: {data.name}</span>
</>
);
}
await ReactNoop.act(async () => {
ReactNoop.render(<App User={loadUser('Dan')} />);
});
const loadUser = block(Render, load);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>Initial name: Sebastian</span>
<span>Latest name: Dan</span>
</>,
function App({User}) {
return (
<Suspense fallback={'Loading...'}>
<User title="Name" />
</Suspense>
);
},
);
}
await ReactNoop.act(async () => {
ReactNoop.render(<App User={loadUser('Sebastian')} />);
});
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>Initial name: Sebastian</span>
<span>Latest name: Sebastian</span>
</>,
);
await ReactNoop.act(async () => {
ReactNoop.render(<App User={loadUser('Dan')} />);
});
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>Initial name: Sebastian</span>
<span>Latest name: Dan</span>
</>,
);
});
});
@@ -798,7 +798,8 @@ describe('ReactHooksWithNoopRenderer', () => {
});
// TODO: This should probably warn
it.experimental('calling startTransition inside render phase', async () => {
// @gate experimental
it('calling startTransition inside render phase', async () => {
let startTransition;
function App() {
const [counter, setCounter] = useState(0);
@@ -2728,152 +2729,149 @@ describe('ReactHooksWithNoopRenderer', () => {
});
});
describe('useTransition', () => {
it.experimental(
'delays showing loading state until after timeout',
async () => {
let transition;
function App() {
const [show, setShow] = useState(false);
const [startTransition, isPending] = useTransition({
timeoutMs: 1000,
});
transition = () => {
startTransition(() => {
setShow(true);
});
};
return (
<Suspense
fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
{show ? (
<AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
) : (
<Text text={`Before... Pending: ${isPending}`} />
)}
</Suspense>
);
}
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: false'),
]);
act(() => {
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
transition,
);
// @gate experimental
it('delays showing loading state until after timeout', async () => {
let transition;
function App() {
const [show, setShow] = useState(false);
const [startTransition, isPending] = useTransition({
timeoutMs: 1000,
});
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
expect(Scheduler).toHaveYielded([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(ReactNoop.getChildren()).toEqual([
hiddenSpan('Before... Pending: true'),
span('Loading... Pending: false'),
]);
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
},
);
it.experimental(
'delays showing loading state until after busyDelayMs + busyMinDurationMs',
async () => {
let transition;
function App() {
const [show, setShow] = useState(false);
const [startTransition, isPending] = useTransition({
busyDelayMs: 1000,
busyMinDurationMs: 2000,
transition = () => {
startTransition(() => {
setShow(true);
});
transition = () => {
startTransition(() => {
setShow(true);
});
};
return (
<Suspense
fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
{show ? (
<AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
) : (
<Text text={`Before... Pending: ${isPending}`} />
)}
</Suspense>
);
}
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: false'),
]);
};
return (
<Suspense
fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
{show ? (
<AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
) : (
<Text text={`Before... Pending: ${isPending}`} />
)}
</Suspense>
);
}
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: false'),
]);
act(() => {
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
transition,
);
act(() => {
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
transition,
);
});
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
expect(Scheduler).toHaveYielded([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(ReactNoop.getChildren()).toEqual([
hiddenSpan('Before... Pending: true'),
span('Loading... Pending: false'),
]);
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
});
// @gate experimental
it('delays showing loading state until after busyDelayMs + busyMinDurationMs', async () => {
let transition;
function App() {
const [show, setShow] = useState(false);
const [startTransition, isPending] = useTransition({
busyDelayMs: 1000,
busyMinDurationMs: 2000,
});
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toHaveYielded([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
transition = () => {
startTransition(() => {
setShow(true);
});
};
return (
<Suspense
fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
{show ? (
<AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
) : (
<Text text={`Before... Pending: ${isPending}`} />
)}
</Suspense>
);
}
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: false'),
]);
// Resolve the promise. The whole tree has now completed. However,
// because we exceeded the busy threshold, we won't commit the
// result yet.
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
act(() => {
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
transition,
);
});
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toHaveYielded([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time until just before the `busyMinDuration` threshold.
Scheduler.unstable_advanceTime(999);
await advanceTimers(999);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Resolve the promise. The whole tree has now completed. However,
// because we exceeded the busy threshold, we won't commit the
// result yet.
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time just a bit more. Now we complete the transition.
Scheduler.unstable_advanceTime(300);
await advanceTimers(300);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
},
);
// Advance time until just before the `busyMinDuration` threshold.
Scheduler.unstable_advanceTime(999);
await advanceTimers(999);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time just a bit more. Now we complete the transition.
Scheduler.unstable_advanceTime(300);
await advanceTimers(300);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
});
});
describe('useDeferredValue', () => {
it.experimental('defers text value until specified timeout', async () => {
// @gate experimental
it('defers text value until specified timeout', async () => {
function TextBox({text}) {
return <AsyncText ms={1000} text={text} />;
}
@@ -3738,127 +3738,125 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
// Regression: https://github.com/facebook/react/issues/18486
it.experimental(
'does not get stuck in pending state with render phase updates',
async () => {
let setTextWithShortTransition;
let setTextWithLongTransition;
// @gate experimental
it('does not get stuck in pending state with render phase updates', async () => {
let setTextWithShortTransition;
let setTextWithLongTransition;
function App() {
const [startShortTransition, isPending1] = React.useTransition({
timeoutMs: 5000,
});
const [startLongTransition, isPending2] = React.useTransition({
timeoutMs: 30000,
});
const isPending = isPending1 || isPending2;
const [text, setText] = React.useState('');
const [mirror, setMirror] = React.useState('');
function App() {
const [startShortTransition, isPending1] = React.useTransition({
timeoutMs: 5000,
});
const [startLongTransition, isPending2] = React.useTransition({
timeoutMs: 30000,
});
const isPending = isPending1 || isPending2;
const [text, setText] = React.useState('');
const [mirror, setMirror] = React.useState('');
if (text !== mirror) {
// Render phase update was needed to repro the bug.
setMirror(text);
}
setTextWithShortTransition = value => {
startShortTransition(() => {
setText(value);
});
};
setTextWithLongTransition = value => {
startLongTransition(() => {
setText(value);
});
};
return (
<>
{isPending ? <Text text="Pending..." /> : null}
{text !== '' ? <AsyncText text={text} /> : <Text text={text} />}
</>
);
if (text !== mirror) {
// Render phase update was needed to repro the bug.
setMirror(text);
}
function Root() {
return (
<Suspense fallback={<Text text="Loading..." />}>
<App />
</Suspense>
);
}
setTextWithShortTransition = value => {
startShortTransition(() => {
setText(value);
});
};
setTextWithLongTransition = value => {
startLongTransition(() => {
setText(value);
});
};
const root = ReactNoop.createRoot();
await ReactNoop.act(async () => {
root.render(<Root />);
});
expect(Scheduler).toHaveYielded(['']);
expect(root).toMatchRenderedOutput(<span prop="" />);
// Update to "a". That will suspend.
await ReactNoop.act(async () => {
setTextWithShortTransition('a');
expect(Scheduler).toFlushAndYield([
'Pending...',
'',
'Suspend! [a]',
'Loading...',
]);
});
expect(Scheduler).toHaveYielded([]);
expect(root).toMatchRenderedOutput(
return (
<>
<span prop="Pending..." />
<span prop="" />
</>,
{isPending ? <Text text="Pending..." /> : null}
{text !== '' ? <AsyncText text={text} /> : <Text text={text} />}
</>
);
}
// Update to "b". That will suspend, too.
await ReactNoop.act(async () => {
setTextWithLongTransition('b');
expect(Scheduler).toFlushAndYield([
// Neither is resolved yet.
'Pending...',
'',
'Pending...',
'Suspend! [a]',
'Loading...',
'Suspend! [b]',
'Loading...',
]);
});
expect(Scheduler).toHaveYielded([]);
expect(root).toMatchRenderedOutput(
<>
<span prop="Pending..." />
<span prop="" />
</>,
function Root() {
return (
<Suspense fallback={<Text text="Loading..." />}>
<App />
</Suspense>
);
}
// Resolve "a". But "b" is still pending.
await ReactNoop.act(async () => {
await resolveText('a');
});
expect(Scheduler).toHaveYielded([
'Promise resolved [a]',
const root = ReactNoop.createRoot();
await ReactNoop.act(async () => {
root.render(<Root />);
});
expect(Scheduler).toHaveYielded(['']);
expect(root).toMatchRenderedOutput(<span prop="" />);
// Update to "a". That will suspend.
await ReactNoop.act(async () => {
setTextWithShortTransition('a');
expect(Scheduler).toFlushAndYield([
'Pending...',
'a',
'',
'Suspend! [a]',
'Loading...',
]);
});
expect(Scheduler).toHaveYielded([]);
expect(root).toMatchRenderedOutput(
<>
<span prop="Pending..." />
<span prop="" />
</>,
);
// Update to "b". That will suspend, too.
await ReactNoop.act(async () => {
setTextWithLongTransition('b');
expect(Scheduler).toFlushAndYield([
// Neither is resolved yet.
'Pending...',
'',
'Pending...',
'Suspend! [a]',
'Loading...',
'Suspend! [b]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="Pending..." />
<span prop="a" />
</>,
);
});
expect(Scheduler).toHaveYielded([]);
expect(root).toMatchRenderedOutput(
<>
<span prop="Pending..." />
<span prop="" />
</>,
);
// Resolve "b". This should remove the pending state.
await ReactNoop.act(async () => {
await resolveText('b');
});
expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
// The bug was that the pending state got stuck forever.
expect(root).toMatchRenderedOutput(<span prop="b" />);
},
);
// Resolve "a". But "b" is still pending.
await ReactNoop.act(async () => {
await resolveText('a');
});
expect(Scheduler).toHaveYielded([
'Promise resolved [a]',
'Pending...',
'a',
'Suspend! [b]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="Pending..." />
<span prop="a" />
</>,
);
// Resolve "b". This should remove the pending state.
await ReactNoop.act(async () => {
await resolveText('b');
});
expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
// The bug was that the pending state got stuck forever.
expect(root).toMatchRenderedOutput(<span prop="b" />);
});
});
@@ -59,47 +59,45 @@ describe('ReactTransition', () => {
return Component;
}
it.experimental(
'isPending works even if called from outside an input event',
async () => {
const Async = createAsyncText('Async');
let start;
function App() {
const [show, setShow] = useState(false);
const [startTransition, isPending] = useTransition();
start = () => startTransition(() => setShow(true));
return (
<Suspense fallback={<Text text="Loading..." />}>
{isPending ? <Text text="Pending..." /> : null}
{show ? <Async /> : <Text text="(empty)" />}
</Suspense>
);
}
// @gate experimental
it('isPending works even if called from outside an input event', async () => {
const Async = createAsyncText('Async');
let start;
function App() {
const [show, setShow] = useState(false);
const [startTransition, isPending] = useTransition();
start = () => startTransition(() => setShow(true));
return (
<Suspense fallback={<Text text="Loading..." />}>
{isPending ? <Text text="Pending..." /> : null}
{show ? <Async /> : <Text text="(empty)" />}
</Suspense>
);
}
const root = ReactNoop.createRoot();
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['(empty)']);
expect(root).toMatchRenderedOutput('(empty)');
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['(empty)']);
expect(root).toMatchRenderedOutput('(empty)');
await act(async () => {
start();
await act(async () => {
start();
expect(Scheduler).toFlushAndYield([
'Pending...',
'(empty)',
'Suspend! [Async]',
'Loading...',
]);
expect(Scheduler).toFlushAndYield([
'Pending...',
'(empty)',
'Suspend! [Async]',
'Loading...',
]);
expect(root).toMatchRenderedOutput('Pending...(empty)');
expect(root).toMatchRenderedOutput('Pending...(empty)');
await Async.resolve();
});
expect(Scheduler).toHaveYielded(['Async']);
expect(root).toMatchRenderedOutput('Async');
},
);
await Async.resolve();
});
expect(Scheduler).toHaveYielded(['Async']);
expect(root).toMatchRenderedOutput('Async');
});
});
@@ -362,76 +362,75 @@ describe('Concurrent Mode', () => {
Scheduler = require('scheduler');
});
it.experimental(
'should warn about unsafe legacy lifecycle methods anywhere in the tree',
() => {
class AsyncRoot extends React.Component {
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate() {}
render() {
return (
// @gate experimental
it('should warn about unsafe legacy lifecycle methods anywhere in the tree', () => {
class AsyncRoot extends React.Component {
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate() {}
render() {
return (
<div>
<Wrapper>
<Foo />
</Wrapper>
<div>
<Wrapper>
<Foo />
</Wrapper>
<div>
<Bar />
<Foo />
</div>
<Bar />
<Foo />
</div>
);
}
</div>
);
}
function Wrapper({children}) {
return <div>{children}</div>;
}
function Wrapper({children}) {
return <div>{children}</div>;
}
class Foo extends React.Component {
UNSAFE_componentWillReceiveProps() {}
render() {
return null;
}
class Foo extends React.Component {
UNSAFE_componentWillReceiveProps() {}
render() {
return null;
}
}
class Bar extends React.Component {
UNSAFE_componentWillReceiveProps() {}
render() {
return null;
}
}
class Bar extends React.Component {
UNSAFE_componentWillReceiveProps() {}
render() {
return null;
}
}
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
root.render(<AsyncRoot />);
expect(() => Scheduler.unstable_flushAll()).toErrorDev(
[
/* eslint-disable max-len */
`Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
root.render(<AsyncRoot />);
expect(() => Scheduler.unstable_flushAll()).toErrorDev(
[
/* eslint-disable max-len */
`Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
* Move code with side effects to componentDidMount, and set initial state in the constructor.
Please update the following components: AsyncRoot`,
`Warning: Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
`Warning: Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
* Move data fetching code or side effects to componentDidUpdate.
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state
Please update the following components: Bar, Foo`,
`Warning: Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
`Warning: Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
* Move data fetching code or side effects to componentDidUpdate.
Please update the following components: AsyncRoot`,
/* eslint-enable max-len */
],
{withoutStack: true},
);
/* eslint-enable max-len */
],
{withoutStack: true},
);
// Dedupe
root.render(<AsyncRoot />);
Scheduler.unstable_flushAll();
},
);
// Dedupe
root.render(<AsyncRoot />);
Scheduler.unstable_flushAll();
});
it.experimental('should coalesce warnings by lifecycle name', () => {
// @gate experimental
it('should coalesce warnings by lifecycle name', () => {
class AsyncRoot extends React.Component {
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate() {}
@@ -513,52 +512,50 @@ Please update the following components: Parent`,
Scheduler.unstable_flushAll();
});
it.experimental(
'should warn about components not present during the initial render',
() => {
class AsyncRoot extends React.Component {
render() {
return this.props.foo ? <Foo /> : <Bar />;
}
// @gate experimental
it('should warn about components not present during the initial render', () => {
class AsyncRoot extends React.Component {
render() {
return this.props.foo ? <Foo /> : <Bar />;
}
class Foo extends React.Component {
UNSAFE_componentWillMount() {}
render() {
return null;
}
}
class Foo extends React.Component {
UNSAFE_componentWillMount() {}
render() {
return null;
}
class Bar extends React.Component {
UNSAFE_componentWillMount() {}
render() {
return null;
}
}
class Bar extends React.Component {
UNSAFE_componentWillMount() {}
render() {
return null;
}
}
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
root.render(<AsyncRoot foo={true} />);
expect(() =>
Scheduler.unstable_flushAll(),
).toErrorDev(
'Using UNSAFE_componentWillMount in strict mode is not recommended',
{withoutStack: true},
);
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
root.render(<AsyncRoot foo={true} />);
expect(() =>
Scheduler.unstable_flushAll(),
).toErrorDev(
'Using UNSAFE_componentWillMount in strict mode is not recommended',
{withoutStack: true},
);
root.render(<AsyncRoot foo={false} />);
expect(() =>
Scheduler.unstable_flushAll(),
).toErrorDev(
'Using UNSAFE_componentWillMount in strict mode is not recommended',
{withoutStack: true},
);
root.render(<AsyncRoot foo={false} />);
expect(() =>
Scheduler.unstable_flushAll(),
).toErrorDev(
'Using UNSAFE_componentWillMount in strict mode is not recommended',
{withoutStack: true},
);
// Dedupe
root.render(<AsyncRoot foo={true} />);
Scheduler.unstable_flushAll();
root.render(<AsyncRoot foo={false} />);
Scheduler.unstable_flushAll();
},
);
// Dedupe
root.render(<AsyncRoot foo={true} />);
Scheduler.unstable_flushAll();
root.render(<AsyncRoot foo={false} />);
Scheduler.unstable_flushAll();
});
it('should also warn inside of "strict" mode trees', () => {
const {StrictMode} = React;
-31
View File
@@ -267,37 +267,6 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
throw Error(errorMsg);
};
// TODO: Deprecate these helpers in favor of @gate pragma
const it = global.it;
const fit = global.fit;
const xit = global.xit;
if (__EXPERIMENTAL__) {
it.experimental = it;
fit.experimental = it.only.experimental = it.experimental.only = fit;
xit.experimental = it.skip.experimental = it.experimental.skip = xit;
} else {
const errorMessage =
'Tests marked experimental are expected to fail, but this one passed.';
it.experimental = (message, callback) => {
it(`[EXPERIMENTAL, SHOULD FAIL] ${message}`, () =>
expectTestToFail(callback, errorMessage));
};
fit.experimental = it.only.experimental = it.experimental.only = (
message,
callback
) => {
fit(`[EXPERIMENTAL, SHOULD FAIL] ${message}`, () =>
expectTestToFail(callback, errorMessage));
};
xit.experimental = it.skip.experimental = it.experimental.skip = (
message,
callback
) => {
xit(`[EXPERIMENTAL, SHOULD FAIL] ${message}`, () =>
expectTestToFail(callback, errorMessage));
};
}
const gatedErrorMessage = 'Gated test was expected to fail, but it passed.';
global._test_gate = (gateFn, testName, callback) => {
let shouldPass;