Migrate conditional tests to gate pragma (#18585)

* Migrate conditional tests to gate pragma

I searched through the codebase for this pattern:

```js
describe('test suite', () => {
  if (!__EXPERIMENTAL__) { // or some other condition
    test("empty test so Jest doesn't complain", () => {});
    return;
  }

  // Unless we're in experimental mode, none of the tests in this block
  // will run.
})
```

and converted them to the `@gate` pragma instead.

The reason this pattern isn't preferred is because you end up disabling
more tests than you need to.

* Add flag for www release channels

Using a heuristic where I check a flag that is known to only be enabled
in www. I left a TODO to instead set the release channel explicitly in
each test config.
This commit is contained in:
Andrew Clark 2020-04-13 14:45:52 -07:00 committed by GitHub
parent 6c43a62c0f
commit bec7599067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 3741 additions and 3169 deletions

View File

@ -25,11 +25,7 @@ describe('ReactHooksInspection', () => {
ReactDebugTools = require('react-debug-tools');
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
// @gate experimental
it('should inspect a simple useResponder hook', () => {
const TestResponder = React.DEPRECATED_createResponder('TestResponder', {});
@ -51,6 +47,7 @@ describe('ReactHooksInspection', () => {
]);
});
// @gate experimental
it('should inspect a simple ReactDOM.useEvent hook', () => {
let clickHandle;
let ref;

View File

@ -27,6 +27,7 @@ describe('StoreStressConcurrent', () => {
print = require('./storeSerializer').print;
});
// TODO: Remove this in favor of @gate pragma
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;

View File

@ -87,11 +87,7 @@ describe('ReactDOMServerPartialHydration', () => {
SuspenseList = React.SuspenseList;
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
// @gate experimental
it('hydrates a parent even if a child Suspense boundary is blocked', async () => {
let suspend = false;
let resolve;
@ -150,6 +146,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
});
// @gate experimental
it('calls the hydration callbacks after hydration or deletion', async () => {
let suspend = false;
let resolve;
@ -240,6 +237,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(deleted.length).toBe(1);
});
// @gate experimental
it('calls the onDeleted hydration callback if the parent gets deleted', async () => {
let suspend = false;
const promise = new Promise(() => {});
@ -297,6 +295,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(deleted.length).toBe(1);
});
// @gate experimental || www
it('warns and replaces the boundary content in legacy mode', async () => {
let suspend = false;
let resolve;
@ -368,6 +367,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hello');
});
// @gate experimental
it('can insert siblings before the dehydrated boundary', () => {
let suspend = false;
const promise = new Promise(() => {});
@ -426,6 +426,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.firstChild.firstChild.textContent).toBe('First');
});
// @gate experimental
it('can delete the dehydrated boundary before it is hydrated', () => {
let suspend = false;
const promise = new Promise(() => {});
@ -482,6 +483,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.firstChild.children[1].textContent).toBe('After');
});
// @gate experimental
it('blocks updates to hydrate the content first if props have changed', async () => {
let suspend = false;
let resolve;
@ -553,6 +555,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi');
});
// @gate experimental
it('shows the fallback if props have changed before hydration completes and is still suspended', async () => {
let suspend = false;
let resolve;
@ -622,6 +625,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi');
});
// @gate experimental
it('shows the fallback of the outer if fallback is missing', async () => {
// This is the same exact test as above but with a nested Suspense without a fallback.
// This should be a noop.
@ -695,6 +699,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi');
});
// @gate experimental
it('clears nested suspense boundaries if they did not hydrate yet', async () => {
let suspend = false;
let resolve;
@ -767,6 +772,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi Hi');
});
// @gate experimental
it('hydrates first if props changed but we are able to resolve within a timeout', async () => {
let suspend = false;
let resolve;
@ -847,6 +853,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi');
});
// @gate experimental
it('blocks the update to hydrate first if context has changed', async () => {
let suspend = false;
let resolve;
@ -931,6 +938,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi');
});
// @gate experimental
it('shows the fallback if context has changed before hydration completes and is still suspended', async () => {
let suspend = false;
let resolve;
@ -1014,6 +1022,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi');
});
// @gate experimental
it('replaces the fallback with client content if it is not rendered by the server', async () => {
let suspend = false;
const promise = new Promise(resolvePromise => {});
@ -1062,6 +1071,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
});
// @gate experimental
it('replaces the fallback within the suspended time if there is a nested suspense', async () => {
let suspend = false;
const promise = new Promise(resolvePromise => {});
@ -1121,6 +1131,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
});
// @gate experimental
it('replaces the fallback within the suspended time if there is a nested suspense in a nested suspense', async () => {
let suspend = false;
const promise = new Promise(resolvePromise => {});
@ -1182,6 +1193,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
});
// @gate experimental
it('waits for pending content to come in from the server and then hydrates it', async () => {
let suspend = false;
const promise = new Promise(resolvePromise => {});
@ -1269,6 +1281,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
});
// @gate experimental
it('handles an error on the client if the server ends up erroring', async () => {
let suspend = false;
const promise = new Promise(resolvePromise => {});
@ -1363,6 +1376,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(div);
});
// @gate experimental
it('shows inserted items in a SuspenseList before content is hydrated', async () => {
let suspend = false;
let resolve;
@ -1448,6 +1462,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(spanB);
});
// @gate experimental
it('shows is able to hydrate boundaries even if others in a list are pending', async () => {
let suspend = false;
let resolve;
@ -1522,6 +1537,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('ALoading B');
});
// @gate experimental
it('shows inserted items before pending in a SuspenseList as fallbacks', async () => {
let suspend = false;
let resolve;
@ -1613,6 +1629,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('ABC');
});
// @gate experimental
it('can client render nested boundaries', async () => {
let suspend = false;
const promise = new Promise(() => {});
@ -1667,6 +1684,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.lastChild.data).toBe('unrelated comment');
});
// @gate experimental
it('can hydrate TWO suspense boundaries', async () => {
const ref1 = React.createRef();
const ref2 = React.createRef();
@ -1706,6 +1724,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref2.current).toBe(span2);
});
// @gate experimental
it('regenerates if it cannot hydrate before changes to props/context expire', async () => {
let suspend = false;
const promise = new Promise(resolvePromise => {});
@ -1787,6 +1806,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(newSpan.className).toBe('hi');
});
// @gate experimental
it('does not invoke an event on a hydrated node until it commits', async () => {
let suspend = false;
let resolve;
@ -1868,6 +1888,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('does not invoke an event on a hydrated EventResponder until it commits', async () => {
let suspend = false;
let resolve;
@ -1949,6 +1970,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('invokes discrete events on nested suspense boundaries in a root (legacy system)', async () => {
let suspend = false;
let resolve;
@ -2028,6 +2050,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('invokes discrete events on nested suspense boundaries in a root (responder system)', async () => {
let suspend = false;
let resolve;
@ -2110,6 +2133,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('does not invoke the parent of dehydrated boundary event', async () => {
let suspend = false;
let resolve;
@ -2184,6 +2208,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('does not invoke an event on a parent tree when a subtree is dehydrated', async () => {
let suspend = false;
let resolve;
@ -2261,6 +2286,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(parentContainer);
});
// @gate experimental
it('blocks only on the last continuous event (legacy system)', async () => {
let suspend1 = false;
let resolve1;
@ -2365,129 +2391,125 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container);
});
if (__EXPERIMENTAL__) {
it('blocks only on the last continuous event (Responder system)', async () => {
useHover = require('react-interactions/events/hover').useHover;
// @gate experimental
it('blocks only on the last continuous event (Responder system)', async () => {
useHover = require('react-interactions/events/hover').useHover;
let suspend1 = false;
let resolve1;
const promise1 = new Promise(
resolvePromise => (resolve1 = resolvePromise),
);
let suspend2 = false;
let resolve2;
const promise2 = new Promise(
resolvePromise => (resolve2 = resolvePromise),
);
let suspend1 = false;
let resolve1;
const promise1 = new Promise(resolvePromise => (resolve1 = resolvePromise));
let suspend2 = false;
let resolve2;
const promise2 = new Promise(resolvePromise => (resolve2 = resolvePromise));
function First({text}) {
if (suspend1) {
throw promise1;
} else {
return 'Hello';
}
function First({text}) {
if (suspend1) {
throw promise1;
} else {
return 'Hello';
}
}
function Second({text}) {
if (suspend2) {
throw promise2;
} else {
return 'World';
}
function Second({text}) {
if (suspend2) {
throw promise2;
} else {
return 'World';
}
}
const ops = [];
const ops = [];
function App() {
const listener1 = useHover({
onHoverStart() {
ops.push('Hover Start First');
},
onHoverEnd() {
ops.push('Hover End First');
},
});
const listener2 = useHover({
onHoverStart() {
ops.push('Hover Start Second');
},
onHoverEnd() {
ops.push('Hover End Second');
},
});
return (
<div>
<Suspense fallback="Loading First...">
<span DEPRECATED_flareListeners={listener1} />
{/* We suspend after to test what happens when we eager
function App() {
const listener1 = useHover({
onHoverStart() {
ops.push('Hover Start First');
},
onHoverEnd() {
ops.push('Hover End First');
},
});
const listener2 = useHover({
onHoverStart() {
ops.push('Hover Start Second');
},
onHoverEnd() {
ops.push('Hover End Second');
},
});
return (
<div>
<Suspense fallback="Loading First...">
<span DEPRECATED_flareListeners={listener1} />
{/* We suspend after to test what happens when we eager
attach the listener. */}
<First />
</Suspense>
<Suspense fallback="Loading Second...">
<span DEPRECATED_flareListeners={listener2}>
<Second />
</span>
</Suspense>
</div>
);
}
<First />
</Suspense>
<Suspense fallback="Loading Second...">
<span DEPRECATED_flareListeners={listener2}>
<Second />
</span>
</Suspense>
</div>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
const appDiv = container.getElementsByTagName('div')[0];
const firstSpan = appDiv.getElementsByTagName('span')[0];
const secondSpan = appDiv.getElementsByTagName('span')[1];
expect(firstSpan.textContent).toBe('');
expect(secondSpan.textContent).toBe('World');
const appDiv = container.getElementsByTagName('div')[0];
const firstSpan = appDiv.getElementsByTagName('span')[0];
const secondSpan = appDiv.getElementsByTagName('span')[1];
expect(firstSpan.textContent).toBe('');
expect(secondSpan.textContent).toBe('World');
// On the client we don't have all data yet but we want to start
// hydrating anyway.
suspend1 = true;
suspend2 = true;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
// On the client we don't have all data yet but we want to start
// hydrating anyway.
suspend1 = true;
suspend2 = true;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
Scheduler.unstable_flushAll();
jest.runAllTimers();
Scheduler.unstable_flushAll();
jest.runAllTimers();
dispatchMouseEvent(appDiv, null);
dispatchMouseEvent(firstSpan, appDiv);
dispatchMouseEvent(secondSpan, firstSpan);
dispatchMouseEvent(appDiv, null);
dispatchMouseEvent(firstSpan, appDiv);
dispatchMouseEvent(secondSpan, firstSpan);
// Neither target is yet hydrated.
expect(ops).toEqual([]);
// Neither target is yet hydrated.
expect(ops).toEqual([]);
// Resolving the second promise so that rendering can complete.
suspend2 = false;
resolve2();
await promise2;
// Resolving the second promise so that rendering can complete.
suspend2 = false;
resolve2();
await promise2;
Scheduler.unstable_flushAll();
jest.runAllTimers();
Scheduler.unstable_flushAll();
jest.runAllTimers();
// We've unblocked the current hover target so we should be
// able to replay it now.
expect(ops).toEqual(['Hover Start Second']);
// We've unblocked the current hover target so we should be
// able to replay it now.
expect(ops).toEqual(['Hover Start Second']);
// Resolving the first promise has no effect now.
suspend1 = false;
resolve1();
await promise1;
// Resolving the first promise has no effect now.
suspend1 = false;
resolve1();
await promise1;
Scheduler.unstable_flushAll();
jest.runAllTimers();
Scheduler.unstable_flushAll();
jest.runAllTimers();
expect(ops).toEqual(['Hover Start Second']);
expect(ops).toEqual(['Hover Start Second']);
document.body.removeChild(container);
});
}
document.body.removeChild(container);
});
// @gate experimental
it('finishes normal pri work before continuing to hydrate a retry', async () => {
let suspend = false;
let resolve;

View File

@ -106,11 +106,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
Suspense = React.Suspense;
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
// @gate experimental
it('hydrates the target boundary synchronously during a click', async () => {
function Child({text}) {
Scheduler.unstable_yieldValue(text);
@ -173,6 +169,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('hydrates at higher pri if sync did not work first time', async () => {
let suspend = false;
let resolve;
@ -257,6 +254,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('hydrates at higher pri for secondary discrete events', async () => {
let suspend = false;
let resolve;
@ -349,247 +347,249 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container);
});
if (__EXPERIMENTAL__) {
it('hydrates the target boundary synchronously during a click (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress;
// @gate experimental
it('hydrates the target boundary synchronously during a click (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress;
function Child({text}) {
Scheduler.unstable_yieldValue(text);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
function Child({text}) {
Scheduler.unstable_yieldValue(text);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
}
function App() {
Scheduler.unstable_yieldValue('App');
return (
<div>
<Suspense fallback="Loading...">
<Child text="A" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="B" />
</Suspense>
</div>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
expect(Scheduler).toHaveYielded(['App', 'A', 'B']);
const container = document.createElement('div');
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
container.innerHTML = finalHTML;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
// Nothing has been hydrated so far.
expect(Scheduler).toHaveYielded([]);
const span = container.getElementsByTagName('span')[1];
const target = createEventTarget(span);
// This should synchronously hydrate the root App and the second suspense
// boundary.
const preventDefault = jest.fn();
target.virtualclick({preventDefault});
// The event should have been canceled because we called preventDefault.
expect(preventDefault).toHaveBeenCalled();
// We rendered App, B and then invoked the event without rendering A.
expect(Scheduler).toHaveYielded(['App', 'B', 'Clicked B']);
// After continuing the scheduler, we finally hydrate A.
expect(Scheduler).toFlushAndYield(['A']);
document.body.removeChild(container);
});
// @gate experimental
it('hydrates at higher pri if sync did not work first time (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress;
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
function Child({text}) {
if ((text === 'A' || text === 'D') && suspend) {
throw promise;
}
Scheduler.unstable_yieldValue(text);
function App() {
Scheduler.unstable_yieldValue('App');
return (
<div>
<Suspense fallback="Loading...">
<Child text="A" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="B" />
</Suspense>
</div>
);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
}
function App() {
Scheduler.unstable_yieldValue('App');
return (
<div>
<Suspense fallback="Loading...">
<Child text="A" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="B" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="C" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="D" />
</Suspense>
</div>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']);
const container = document.createElement('div');
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
container.innerHTML = finalHTML;
const spanD = container.getElementsByTagName('span')[3];
suspend = true;
// A and D will be suspended. We'll click on D which should take
// priority, after we unsuspend.
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
// Nothing has been hydrated so far.
expect(Scheduler).toHaveYielded([]);
// This click target cannot be hydrated yet because it's suspended.
const result = dispatchClickEvent(spanD);
expect(Scheduler).toHaveYielded(['App']);
expect(result).toBe(true);
// Continuing rendering will render B next.
expect(Scheduler).toFlushAndYield(['B', 'C']);
suspend = false;
resolve();
await promise;
// After the click, we should prioritize D and the Click first,
// and only after that render A and C.
expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']);
document.body.removeChild(container);
});
// @gate experimental
it('hydrates at higher pri for secondary discrete events (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress;
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
function Child({text}) {
if ((text === 'A' || text === 'D') && suspend) {
throw promise;
}
Scheduler.unstable_yieldValue(text);
const finalHTML = ReactDOMServer.renderToString(<App />);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
}
expect(Scheduler).toHaveYielded(['App', 'A', 'B']);
function App() {
Scheduler.unstable_yieldValue('App');
return (
<div>
<Suspense fallback="Loading...">
<Child text="A" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="B" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="C" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="D" />
</Suspense>
</div>
);
}
const container = document.createElement('div');
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
const finalHTML = ReactDOMServer.renderToString(<App />);
container.innerHTML = finalHTML;
expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']);
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
const container = document.createElement('div');
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
// Nothing has been hydrated so far.
expect(Scheduler).toHaveYielded([]);
container.innerHTML = finalHTML;
const span = container.getElementsByTagName('span')[1];
const spanA = container.getElementsByTagName('span')[0];
const spanC = container.getElementsByTagName('span')[2];
const spanD = container.getElementsByTagName('span')[3];
const target = createEventTarget(span);
suspend = true;
// This should synchronously hydrate the root App and the second suspense
// boundary.
const preventDefault = jest.fn();
target.virtualclick({preventDefault});
// A and D will be suspended. We'll click on D which should take
// priority, after we unsuspend.
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
// The event should have been canceled because we called preventDefault.
expect(preventDefault).toHaveBeenCalled();
// Nothing has been hydrated so far.
expect(Scheduler).toHaveYielded([]);
// We rendered App, B and then invoked the event without rendering A.
expect(Scheduler).toHaveYielded(['App', 'B', 'Clicked B']);
// This click target cannot be hydrated yet because the first is Suspended.
dispatchClickEvent(spanA);
dispatchClickEvent(spanC);
dispatchClickEvent(spanD);
// After continuing the scheduler, we finally hydrate A.
expect(Scheduler).toFlushAndYield(['A']);
expect(Scheduler).toHaveYielded(['App']);
document.body.removeChild(container);
});
suspend = false;
resolve();
await promise;
it('hydrates at higher pri if sync did not work first time (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress;
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
// We should prioritize hydrating A, C and D first since we clicked in
// them. Only after they're done will we hydrate B.
expect(Scheduler).toFlushAndYield([
'A',
'Clicked A',
'C',
'Clicked C',
'D',
'Clicked D',
// B should render last since it wasn't clicked.
'B',
]);
function Child({text}) {
if ((text === 'A' || text === 'D') && suspend) {
throw promise;
}
Scheduler.unstable_yieldValue(text);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
}
function App() {
Scheduler.unstable_yieldValue('App');
return (
<div>
<Suspense fallback="Loading...">
<Child text="A" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="B" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="C" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="D" />
</Suspense>
</div>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']);
const container = document.createElement('div');
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
container.innerHTML = finalHTML;
const spanD = container.getElementsByTagName('span')[3];
suspend = true;
// A and D will be suspended. We'll click on D which should take
// priority, after we unsuspend.
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
// Nothing has been hydrated so far.
expect(Scheduler).toHaveYielded([]);
// This click target cannot be hydrated yet because it's suspended.
const result = dispatchClickEvent(spanD);
expect(Scheduler).toHaveYielded(['App']);
expect(result).toBe(true);
// Continuing rendering will render B next.
expect(Scheduler).toFlushAndYield(['B', 'C']);
suspend = false;
resolve();
await promise;
// After the click, we should prioritize D and the Click first,
// and only after that render A and C.
expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']);
document.body.removeChild(container);
});
it('hydrates at higher pri for secondary discrete events (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress;
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
function Child({text}) {
if ((text === 'A' || text === 'D') && suspend) {
throw promise;
}
Scheduler.unstable_yieldValue(text);
const listener = usePress({
onPress() {
Scheduler.unstable_yieldValue('Clicked ' + text);
},
});
return <span DEPRECATED_flareListeners={listener}>{text}</span>;
}
function App() {
Scheduler.unstable_yieldValue('App');
return (
<div>
<Suspense fallback="Loading...">
<Child text="A" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="B" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="C" />
</Suspense>
<Suspense fallback="Loading...">
<Child text="D" />
</Suspense>
</div>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']);
const container = document.createElement('div');
// We need this to be in the document since we'll dispatch events on it.
document.body.appendChild(container);
container.innerHTML = finalHTML;
const spanA = container.getElementsByTagName('span')[0];
const spanC = container.getElementsByTagName('span')[2];
const spanD = container.getElementsByTagName('span')[3];
suspend = true;
// A and D will be suspended. We'll click on D which should take
// priority, after we unsuspend.
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
// Nothing has been hydrated so far.
expect(Scheduler).toHaveYielded([]);
// This click target cannot be hydrated yet because the first is Suspended.
dispatchClickEvent(spanA);
dispatchClickEvent(spanC);
dispatchClickEvent(spanD);
expect(Scheduler).toHaveYielded(['App']);
suspend = false;
resolve();
await promise;
// We should prioritize hydrating A, C and D first since we clicked in
// them. Only after they're done will we hydrate B.
expect(Scheduler).toFlushAndYield([
'A',
'Clicked A',
'C',
'Clicked C',
'D',
'Clicked D',
// B should render last since it wasn't clicked.
'B',
]);
document.body.removeChild(container);
});
}
document.body.removeChild(container);
});
// @gate experimental
it('hydrates the hovered targets as higher priority for continuous events', async () => {
let suspend = false;
let resolve;
@ -690,6 +690,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('hydrates the last target path first for continuous events', async () => {
let suspend = false;
let resolve;
@ -775,6 +776,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container);
});
// @gate experimental
it('hydrates the last explicitly hydrated target at higher priority', async () => {
function Child({text}) {
Scheduler.unstable_yieldValue(text);
@ -823,6 +825,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
expect(Scheduler).toFlushAndYield(['App', 'C', 'B', 'A']);
});
// @gate experimental
it('hydrates before an update even if hydration moves away from it', async () => {
function Child({text}) {
Scheduler.unstable_yieldValue(text);

View File

@ -44,11 +44,6 @@ describe('ReactDOMServerSuspense', () => {
resetModules();
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
function Text(props) {
return <div>{props.text}</div>;
}
@ -57,6 +52,7 @@ describe('ReactDOMServerSuspense', () => {
throw new Promise(() => {});
}
// @gate experimental || www
it('should render the children when no promise is thrown', async () => {
const c = await serverRender(
<div>
@ -71,6 +67,7 @@ describe('ReactDOMServerSuspense', () => {
expect(e.textContent).toBe('Children');
});
// @gate experimental || www
it('should render the fallback when a promise thrown', async () => {
const c = await serverRender(
<div>
@ -85,6 +82,7 @@ describe('ReactDOMServerSuspense', () => {
expect(e.textContent).toBe('Fallback');
});
// @gate experimental || www
it('should work with nested suspense components', async () => {
const c = await serverRender(
<div>
@ -105,6 +103,7 @@ describe('ReactDOMServerSuspense', () => {
);
});
// @gate experimental
it('server renders a SuspenseList component and its children', async () => {
const example = (
<React.SuspenseList>
@ -137,33 +136,36 @@ describe('ReactDOMServerSuspense', () => {
expect(divB).toBe(divB2);
});
itThrowsWhenRendering(
'a suspending component outside a Suspense node',
async render => {
await render(
<div>
<React.Suspense />
<AsyncText text="Children" />
<React.Suspense />
</div>,
1,
);
},
'Add a <Suspense fallback=...> component higher in the tree',
);
// TODO: Remove this in favor of @gate pragma
if (__EXPERIMENTAL__) {
itThrowsWhenRendering(
'a suspending component outside a Suspense node',
async render => {
await render(
<div>
<React.Suspense />
<AsyncText text="Children" />
<React.Suspense />
</div>,
1,
);
},
'Add a <Suspense fallback=...> component higher in the tree',
);
itThrowsWhenRendering(
'a suspending component without a Suspense above',
async render => {
await render(
<div>
<AsyncText text="Children" />
</div>,
1,
);
},
'Add a <Suspense fallback=...> component higher in the tree',
);
itThrowsWhenRendering(
'a suspending component without a Suspense above',
async render => {
await render(
<div>
<AsyncText text="Children" />
</div>,
1,
);
},
'Add a <Suspense fallback=...> component higher in the tree',
);
}
it('does not get confused by throwing null', () => {
function Bad() {

View File

@ -18,324 +18,315 @@ const renderSubtreeIntoContainer = require('react-dom')
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
// Once this flag is always true, we should delete this test file
if (__EXPERIMENTAL__) {
describe('renderSubtreeIntoContainer', () => {
it('empty test', () => {
// Empty test to prevent "Your test suite must contain at least one test." error.
});
describe('renderSubtreeIntoContainer', () => {
it('should pass context when rendering subtree elsewhere', () => {
const portal = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.foo}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
getChildContext() {
return {
foo: 'bar',
};
}
render() {
return null;
}
componentDidMount() {
if (ReactFeatureFlags.warnUnstableRenderSubtreeIntoContainer) {
expect(
function() {
renderSubtreeIntoContainer(this, <Component />, portal);
}.bind(this),
).toWarnDev(
'ReactDOM.unstable_renderSubtreeIntoContainer() is deprecated and ' +
'will be removed in a future major release. Consider using React Portals instead.',
);
} else {
renderSubtreeIntoContainer(this, <Component />, portal);
}
}
}
ReactTestUtils.renderIntoDocument(<Parent />);
expect(portal.firstChild.innerHTML).toBe('bar');
});
} else {
describe('renderSubtreeIntoContainer', () => {
it('should pass context when rendering subtree elsewhere', () => {
const portal = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};
it('should throw if parentComponent is invalid', () => {
const portal = document.createElement('div');
render() {
return <div>{this.context.foo}</div>;
}
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.foo}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
// ESLint is confused here and thinks Parent is unused, presumably because
// it is only used inside of the class body?
// eslint-disable-next-line no-unused-vars
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
getChildContext() {
return {
foo: 'bar',
};
}
render() {
return null;
}
componentDidMount() {
if (ReactFeatureFlags.warnUnstableRenderSubtreeIntoContainer) {
expect(
function() {
renderSubtreeIntoContainer(this, <Component />, portal);
}.bind(this),
).toWarnDev(
'ReactDOM.unstable_renderSubtreeIntoContainer() is deprecated and ' +
'will be removed in a future major release. Consider using React Portals instead.',
);
} else {
renderSubtreeIntoContainer(this, <Component />, portal);
}
}
}
ReactTestUtils.renderIntoDocument(<Parent />);
expect(portal.firstChild.innerHTML).toBe('bar');
});
it('should throw if parentComponent is invalid', () => {
const portal = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.foo}</div>;
}
}
// ESLint is confused here and thinks Parent is unused, presumably because
// it is only used inside of the class body?
// eslint-disable-next-line no-unused-vars
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
getChildContext() {
return {
foo: 'bar',
};
}
render() {
return null;
}
componentDidMount() {
expect(function() {
renderSubtreeIntoContainer(<Parent />, <Component />, portal);
}).toThrowError('parentComponentmust be a valid React Component');
}
}
});
it('should update context if it changes due to setState', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
render() {
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
state = {
bar: 'initial',
};
getChildContext() {
return {
foo: this.state.bar,
getFoo: () => this.state.bar,
};
}
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
componentDidUpdate() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
}
const instance = ReactDOM.render(<Parent />, container);
expect(portal.firstChild.innerHTML).toBe('initial-initial');
instance.setState({bar: 'changed'});
expect(portal.firstChild.innerHTML).toBe('changed-changed');
});
it('should update context if it changes due to re-render', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
render() {
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
getChildContext() {
return {
foo: this.props.bar,
getFoo: () => this.props.bar,
};
}
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
componentDidUpdate() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
}
ReactDOM.render(<Parent bar="initial" />, container);
expect(portal.firstChild.innerHTML).toBe('initial-initial');
ReactDOM.render(<Parent bar="changed" />, container);
expect(portal.firstChild.innerHTML).toBe('changed-changed');
});
it('should render portal with non-context-provider parent', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Parent extends React.Component {
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <div>hello</div>, portal);
}
}
ReactDOM.render(<Parent bar="initial" />, container);
expect(portal.firstChild.innerHTML).toBe('hello');
});
it('should get context through non-context-provider parent', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Parent extends React.Component {
render() {
return <Middle />;
}
getChildContext() {
return {value: this.props.value};
}
static childContextTypes = {
value: PropTypes.string.isRequired,
getChildContext() {
return {
foo: 'bar',
};
}
class Middle extends React.Component {
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Child />, portal);
}
render() {
return null;
}
class Child extends React.Component {
static contextTypes = {
value: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.value}</div>;
}
componentDidMount() {
expect(function() {
renderSubtreeIntoContainer(<Parent />, <Component />, portal);
}).toThrowError('parentComponentmust be a valid React Component');
}
ReactDOM.render(<Parent value="foo" />, container);
expect(portal.textContent).toBe('foo');
});
it('should get context through middle non-context-provider layer', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal1 = document.createElement('div');
const portal2 = document.createElement('div');
class Parent extends React.Component {
render() {
return null;
}
getChildContext() {
return {value: this.props.value};
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Middle />, portal1);
}
static childContextTypes = {
value: PropTypes.string.isRequired,
};
}
class Middle extends React.Component {
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Child />, portal2);
}
}
class Child extends React.Component {
static contextTypes = {
value: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.value}</div>;
}
}
ReactDOM.render(<Parent value="foo" />, container);
expect(portal2.textContent).toBe('foo');
});
it('fails gracefully when mixing React 15 and 16', () => {
class C extends React.Component {
render() {
return <div />;
}
}
const c = ReactDOM.render(<C />, document.createElement('div'));
// React 15 calls this:
// https://github.com/facebook/react/blob/77b71fc3c4/src/renderers/dom/client/ReactMount.js#L478-L479
expect(() => {
c._reactInternalInstance._processChildContext({});
}).toThrow(
__DEV__
? '_processChildContext is not available in React 16+. This likely ' +
'means you have multiple copies of React and are attempting to nest ' +
'a React 15 tree inside a React 16 tree using ' +
"unstable_renderSubtreeIntoContainer, which isn't supported. Try to " +
'make sure you have only one copy of React (and ideally, switch to ' +
'ReactDOM.createPortal).'
: "Cannot read property '_processChildContext' of undefined",
);
});
}
});
}
it('should update context if it changes due to setState', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
render() {
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
state = {
bar: 'initial',
};
getChildContext() {
return {
foo: this.state.bar,
getFoo: () => this.state.bar,
};
}
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
componentDidUpdate() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
}
const instance = ReactDOM.render(<Parent />, container);
expect(portal.firstChild.innerHTML).toBe('initial-initial');
instance.setState({bar: 'changed'});
expect(portal.firstChild.innerHTML).toBe('changed-changed');
});
it('should update context if it changes due to re-render', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
render() {
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
getChildContext() {
return {
foo: this.props.bar,
getFoo: () => this.props.bar,
};
}
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
componentDidUpdate() {
renderSubtreeIntoContainer(this, <Component />, portal);
}
}
ReactDOM.render(<Parent bar="initial" />, container);
expect(portal.firstChild.innerHTML).toBe('initial-initial');
ReactDOM.render(<Parent bar="changed" />, container);
expect(portal.firstChild.innerHTML).toBe('changed-changed');
});
it('should render portal with non-context-provider parent', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Parent extends React.Component {
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <div>hello</div>, portal);
}
}
ReactDOM.render(<Parent bar="initial" />, container);
expect(portal.firstChild.innerHTML).toBe('hello');
});
it('should get context through non-context-provider parent', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal = document.createElement('div');
class Parent extends React.Component {
render() {
return <Middle />;
}
getChildContext() {
return {value: this.props.value};
}
static childContextTypes = {
value: PropTypes.string.isRequired,
};
}
class Middle extends React.Component {
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Child />, portal);
}
}
class Child extends React.Component {
static contextTypes = {
value: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.value}</div>;
}
}
ReactDOM.render(<Parent value="foo" />, container);
expect(portal.textContent).toBe('foo');
});
it('should get context through middle non-context-provider layer', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const portal1 = document.createElement('div');
const portal2 = document.createElement('div');
class Parent extends React.Component {
render() {
return null;
}
getChildContext() {
return {value: this.props.value};
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Middle />, portal1);
}
static childContextTypes = {
value: PropTypes.string.isRequired,
};
}
class Middle extends React.Component {
render() {
return null;
}
componentDidMount() {
renderSubtreeIntoContainer(this, <Child />, portal2);
}
}
class Child extends React.Component {
static contextTypes = {
value: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.value}</div>;
}
}
ReactDOM.render(<Parent value="foo" />, container);
expect(portal2.textContent).toBe('foo');
});
it('fails gracefully when mixing React 15 and 16', () => {
class C extends React.Component {
render() {
return <div />;
}
}
const c = ReactDOM.render(<C />, document.createElement('div'));
// React 15 calls this:
// https://github.com/facebook/react/blob/77b71fc3c4/src/renderers/dom/client/ReactMount.js#L478-L479
expect(() => {
c._reactInternalInstance._processChildContext({});
}).toThrow(
__DEV__
? '_processChildContext is not available in React 16+. This likely ' +
'means you have multiple copies of React and are attempting to nest ' +
'a React 15 tree inside a React 16 tree using ' +
"unstable_renderSubtreeIntoContainer, which isn't supported. Try to " +
'make sure you have only one copy of React (and ideally, switch to ' +
'ReactDOM.createPortal).'
: "Cannot read property '_processChildContext' of undefined",
);
});
});

View File

@ -1097,11 +1097,7 @@ describe('DOMModernPluginEventSystem', () => {
ReactTestUtils = require('react-dom/test-utils');
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
// @gate experimental
it('should create the same event listener map', () => {
const listenerMaps = [];
@ -1119,6 +1115,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(listenerMaps[0]).toEqual(listenerMaps[1]);
});
// @gate experimental
it('can render correctly with the ReactDOMServer', () => {
const clickEvent = jest.fn();
@ -1136,6 +1133,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(output).toBe(`<div data-reactroot="">Hello world</div>`);
});
// @gate experimental
it('can render correctly with the ReactDOMServer hydration', () => {
const clickEvent = jest.fn();
const spanRef = React.createRef();
@ -1164,6 +1162,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('should correctly work for a basic "click" listener', () => {
let log = [];
const clickEvent = jest.fn(event => {
@ -1272,6 +1271,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent2).toBeCalledTimes(1);
});
// @gate experimental
it('should correctly work for setting and clearing a basic "click" listener', () => {
const clickEvent = jest.fn();
const divRef = React.createRef();
@ -1315,6 +1315,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toBeCalledTimes(0);
});
// @gate experimental
it('should handle the target being a text node', () => {
const clickEvent = jest.fn();
const buttonRef = React.createRef();
@ -1337,6 +1338,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toBeCalledTimes(1);
});
// @gate experimental
it('handle propagation of click events', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -1390,6 +1392,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(log[3]).toEqual(['bubble', buttonElement]);
});
// @gate experimental
it('handle propagation of click events mixed with onClick events', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -1442,6 +1445,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(log[5]).toEqual(['bubble', buttonElement]);
});
// @gate experimental
it('should correctly work for a basic "click" listener on the outer target', () => {
const log = [];
const clickEvent = jest.fn(event => {
@ -1507,6 +1511,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toBeCalledTimes(2);
});
// @gate experimental
it('should correctly handle many nested target listeners', () => {
const buttonRef = React.createRef();
const targetListener1 = jest.fn();
@ -1572,6 +1577,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(targetListener4).toHaveBeenCalledTimes(2);
});
// @gate experimental
it('should correctly handle stopPropagation corrrectly for target events', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -1605,6 +1611,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('should correctly handle stopPropagation corrrectly for many target events', () => {
const buttonRef = React.createRef();
const targetListerner1 = jest.fn(e => e.stopPropagation());
@ -1639,6 +1646,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(targetListerner4).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('should correctly handle stopPropagation for mixed capture/bubbling target listeners', () => {
const buttonRef = React.createRef();
const targetListerner1 = jest.fn(e => e.stopPropagation());
@ -1725,6 +1733,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(log).toEqual([{counter: 1}]);
});
// @gate experimental
it('should correctly work for a basic "click" listener that upgrades', () => {
const clickEvent = jest.fn();
const buttonRef = React.createRef();
@ -1776,6 +1785,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('should correctly work for a basic "click" listener that upgrades #2', () => {
const clickEvent = jest.fn();
const buttonRef = React.createRef();
@ -1827,6 +1837,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('should correctly work for a basic "click" window listener', () => {
const log = [];
const clickEvent = jest.fn(event => {
@ -1878,6 +1889,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toBeCalledTimes(2);
});
// @gate experimental
it('handle propagation of click events on the window', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -1937,6 +1949,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(log[5]).toEqual(['bubble', window]);
});
// @gate experimental
it('should correctly handle stopPropagation for mixed listeners', () => {
const buttonRef = React.createRef();
const rootListerner1 = jest.fn(e => e.stopPropagation());
@ -1975,6 +1988,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(rootListerner2).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('should correctly handle stopPropagation for delegated listeners', () => {
const buttonRef = React.createRef();
const rootListerner1 = jest.fn(e => e.stopPropagation());
@ -2014,6 +2028,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(rootListerner4).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('handle propagation of click events on the window and document', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -2079,6 +2094,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(log[7]).toEqual(['bubble', window]);
});
// @gate experimental
it('handles propagation of custom user events', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -2153,6 +2169,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(log[5]).toEqual(['bubble', buttonElement]);
});
// @gate experimental
it('beforeblur and afterblur are called after a focused element is unmounted', () => {
const log = [];
// We have to persist here because we want to read relatedTarget later.
@ -2202,6 +2219,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(log).toEqual(['beforeblur', 'afterblur']);
});
// @gate experimental
it('beforeblur and afterblur are called after a nested focused element is unmounted', () => {
const log = [];
// We have to persist here because we want to read relatedTarget later.
@ -2346,6 +2364,7 @@ describe('DOMModernPluginEventSystem', () => {
ReactDOMServer = require('react-dom/server');
});
// @gate experimental
it('handle propagation of click events on a scope', () => {
const buttonRef = React.createRef();
const log = [];
@ -2390,6 +2409,7 @@ describe('DOMModernPluginEventSystem', () => {
]);
});
// @gate experimental
it('handle mixed propagation of click events on a scope', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -2464,6 +2484,7 @@ describe('DOMModernPluginEventSystem', () => {
]);
});
// @gate experimental
it('should not handle the target being a dangling text node within a scope', () => {
const clickEvent = jest.fn();
const buttonRef = React.createRef();
@ -2495,6 +2516,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(clickEvent).toBeCalledTimes(0);
});
// @gate experimental
it('handle stopPropagation (inner) correctly between scopes', () => {
const buttonRef = React.createRef();
const outerOnClick = jest.fn();
@ -2531,6 +2553,7 @@ describe('DOMModernPluginEventSystem', () => {
expect(outerOnClick).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('handle stopPropagation (outer) correctly between scopes', () => {
const buttonRef = React.createRef();
const outerOnClick = jest.fn(e => e.stopPropagation());
@ -2567,6 +2590,8 @@ describe('DOMModernPluginEventSystem', () => {
expect(outerOnClick).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('handle stopPropagation (inner and outer) correctly between scopes', () => {
const buttonRef = React.createRef();
const onClick = jest.fn(e => e.stopPropagation());

View File

@ -66,11 +66,6 @@ function dispatchClickEvent(element) {
describe('DOMEventResponderSystem', () => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
@ -89,6 +84,7 @@ describe('DOMEventResponderSystem', () => {
container = null;
});
// @gate experimental
it('can mount and render correctly with the ReactTestRenderer', () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
@ -106,6 +102,7 @@ describe('DOMEventResponderSystem', () => {
expect(renderer).toMatchRenderedOutput(<div>Hello world</div>);
});
// @gate experimental
it('can render correctly with the ReactDOMServer', () => {
const TestResponder = createEventResponder({});
@ -118,6 +115,7 @@ describe('DOMEventResponderSystem', () => {
expect(output).toBe(`<div data-reactroot="">Hello world</div>`);
});
// @gate experimental
it('can render correctly with the ReactDOMServer hydration', () => {
const onEvent = jest.fn();
const TestResponder = createEventResponder({
@ -147,6 +145,7 @@ describe('DOMEventResponderSystem', () => {
expect(onEvent).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('the event responders should fire on click event', () => {
let eventResponderFiredCount = 0;
const eventLog = [];
@ -202,6 +201,7 @@ describe('DOMEventResponderSystem', () => {
expect(eventResponderFiredCount).toBe(2);
});
// @gate experimental
it('the event responders should fire on click event (passive events forced)', () => {
// JSDOM does not support passive events, so this manually overrides the value to be true
const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
@ -246,6 +246,7 @@ describe('DOMEventResponderSystem', () => {
]);
});
// @gate experimental
it('nested event responders should not fire multiple times', () => {
let eventResponderFiredCount = 0;
let eventLog = [];
@ -327,6 +328,7 @@ describe('DOMEventResponderSystem', () => {
]);
});
// @gate experimental
it('nested event responders should fire in the correct order', () => {
let eventLog = [];
const buttonRef = React.createRef();
@ -390,6 +392,7 @@ describe('DOMEventResponderSystem', () => {
expect(eventLog).toEqual(['B [bubble]', 'A [bubble]']);
});
// @gate experimental
it('nested event responders should fire in the correct order #2', () => {
const eventLog = [];
const buttonRef = React.createRef();
@ -426,6 +429,7 @@ describe('DOMEventResponderSystem', () => {
expect(eventLog).toEqual(['B [bubble]']);
});
// @gate experimental
it('custom event dispatching for click -> magicClick works', () => {
const eventLog = [];
const buttonRef = React.createRef();
@ -472,6 +476,7 @@ describe('DOMEventResponderSystem', () => {
expect(eventLog).toEqual(['magic event fired', 'magicclick', 'bubble']);
});
// @gate experimental
it('the event responder onMount() function should fire', () => {
let onMountFired = 0;
@ -505,6 +510,7 @@ describe('DOMEventResponderSystem', () => {
expect(onMountFired).toEqual(2);
});
// @gate experimental
it('the event responder onUnmount() function should fire', () => {
let onUnmountFired = 0;
@ -551,6 +557,7 @@ describe('DOMEventResponderSystem', () => {
expect(onUnmountFired).toEqual(4);
});
// @gate experimental
it('the event responder onUnmount() function should fire using scopes', () => {
let onUnmountFired = 0;
@ -598,6 +605,7 @@ describe('DOMEventResponderSystem', () => {
expect(onUnmountFired).toEqual(4);
});
// @gate experimental
it('the event responder onUnmount() function should fire with state', () => {
let counter = 0;
@ -621,6 +629,7 @@ describe('DOMEventResponderSystem', () => {
expect(counter).toEqual(5);
});
// @gate experimental
it('the event responder target listeners should correctly fire for only their events', () => {
let clickEventComponent1Fired = 0;
let clickEventComponent2Fired = 0;
@ -682,6 +691,7 @@ describe('DOMEventResponderSystem', () => {
]);
});
// @gate experimental
it('the event responder system should warn on accessing invalid properties', () => {
const TestResponder = createEventResponder({
targetEventTypes: ['click'],
@ -747,6 +757,7 @@ describe('DOMEventResponderSystem', () => {
expect(container.innerHTML).toBe('<button>Click me!</button>');
});
// @gate experimental
it('should work with event responder hooks', () => {
const buttonRef = React.createRef();
const eventLogs = [];
@ -839,6 +850,7 @@ describe('DOMEventResponderSystem', () => {
expect(log).toEqual([{counter: 1}]);
});
// @gate experimental
it('should correctly pass through event properties', () => {
const timeStamps = [];
const ref = React.createRef();
@ -903,6 +915,7 @@ describe('DOMEventResponderSystem', () => {
]);
});
// @gate experimental
it('should not propagate target events through portals by default', () => {
const buttonRef = React.createRef();
const onEvent = jest.fn();
@ -926,6 +939,7 @@ describe('DOMEventResponderSystem', () => {
expect(onEvent).not.toBeCalled();
});
// @gate experimental
it('should propagate target events through portals when enabled', () => {
const buttonRef = React.createRef();
const onEvent = jest.fn();
@ -950,6 +964,7 @@ describe('DOMEventResponderSystem', () => {
expect(onEvent).toBeCalled();
});
// @gate experimental
it('event upgrading should work correctly', () => {
let eventResponderFiredCount = 0;
const buttonRef = React.createRef();

View File

@ -28,8 +28,13 @@ function initializeModules(hasPointerEvents) {
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
useContextMenu = require('react-interactions/events/context-menu')
.useContextMenu;
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
useContextMenu = require('react-interactions/events/context-menu')
.useContextMenu;
}
}
const forcePointerEvents = true;
@ -38,11 +43,6 @@ const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('ContextMenu responder', hasPointerEvents => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
@ -56,6 +56,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
});
describe('all platforms', () => {
// @gate experimental
it('mouse right-click', () => {
const onContextMenu = jest.fn();
const preventDefault = jest.fn();
@ -79,6 +80,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
);
});
// @gate experimental
it('touch long-press', () => {
const onContextMenu = jest.fn();
const preventDefault = jest.fn();
@ -102,6 +104,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
);
});
// @gate experimental
it('"disabled" is true', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
@ -119,6 +122,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
expect(onContextMenu).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('"preventDefault" is false', () => {
const preventDefault = jest.fn();
const onContextMenu = jest.fn();
@ -149,6 +153,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
platform.clear();
});
// @gate experimental
it('mouse modified left-click', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();
@ -181,6 +186,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
platform.clear();
});
// @gate experimental
it('mouse modified left-click', () => {
const onContextMenu = jest.fn();
const ref = React.createRef();

View File

@ -28,8 +28,13 @@ function initializeModules(hasPointerEvents) {
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
FocusResponder = require('react-interactions/events/focus').FocusResponder;
useFocus = require('react-interactions/events/focus').useFocus;
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
FocusResponder = require('react-interactions/events/focus').FocusResponder;
useFocus = require('react-interactions/events/focus').useFocus;
}
}
const forcePointerEvents = true;
@ -38,11 +43,6 @@ const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('Focus responder', hasPointerEvents => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
@ -58,7 +58,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
describe('disabled', () => {
let onBlur, onFocus, ref;
beforeEach(() => {
const componentInit = () => {
onBlur = jest.fn();
onFocus = jest.fn();
ref = React.createRef();
@ -71,9 +71,11 @@ describe.each(table)('Focus responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('does not call callbacks', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
target.blur();
@ -85,7 +87,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
describe('onBlur', () => {
let onBlur, ref;
beforeEach(() => {
const componentInit = () => {
onBlur = jest.fn();
ref = React.createRef();
const Component = () => {
@ -95,9 +97,11 @@ describe.each(table)('Focus responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('is called after "blur" event', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
target.blur();
@ -125,21 +129,25 @@ describe.each(table)('Focus responder', hasPointerEvents => {
ReactDOM.render(<Component />, container);
};
beforeEach(componentInit);
// @gate experimental
it('is called after "focus" event', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
expect(onFocus).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('is not called if descendants of target receive focus', () => {
componentInit();
const target = createEventTarget(innerRef.current);
target.focus();
expect(onFocus).not.toBeCalled();
});
// @gate experimental
it('is called with the correct pointerType: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown();
target.pointerup();
@ -149,7 +157,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called with the correct pointerType: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
const pointerType = 'touch';
target.pointerdown({pointerType});
@ -161,7 +171,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
});
if (hasPointerEvents) {
// @gate experimental
it('is called with the correct pointerType: pen', () => {
componentInit();
const target = createEventTarget(ref.current);
const pointerType = 'pen';
target.pointerdown({pointerType});
@ -173,7 +185,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
});
}
// @gate experimental
it('is called with the correct pointerType using a keyboard', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'LeftArrow'});
target.focus();
@ -183,6 +197,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called with the correct pointerType using Tab+altKey on Mac', () => {
platform.set('mac');
jest.resetModules();
@ -207,7 +222,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
describe('onFocusChange', () => {
let onFocusChange, ref, innerRef;
beforeEach(() => {
const componentInit = () => {
onFocusChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
@ -222,9 +237,11 @@ describe.each(table)('Focus responder', hasPointerEvents => {
);
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('is called after "blur" and "focus" events', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
expect(onFocusChange).toHaveBeenCalledTimes(1);
@ -234,7 +251,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
expect(onFocusChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is not called after "blur" and "focus" events on descendants', () => {
componentInit();
const target = createEventTarget(innerRef.current);
target.focus();
expect(onFocusChange).toHaveBeenCalledTimes(0);
@ -246,7 +265,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
describe('onFocusVisibleChange', () => {
let onFocusVisibleChange, ref, innerRef;
beforeEach(() => {
const componentInit = () => {
onFocusVisibleChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
@ -261,9 +280,11 @@ describe.each(table)('Focus responder', hasPointerEvents => {
);
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('is called after "focus" and "blur" if keyboard navigation is active', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
// use keyboard first
@ -276,7 +297,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
// use keyboard first
@ -293,7 +316,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
});
// @gate experimental
it('is not called after "focus" and "blur" events without keyboard', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
target.pointerdown();
@ -303,7 +328,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('is not called after "blur" and "focus" events on descendants', () => {
componentInit();
const innerTarget = createEventTarget(innerRef.current);
const containerTarget = createEventTarget(container);
containerTarget.keydown({key: 'Tab'});
@ -315,6 +342,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
});
describe('nested Focus components', () => {
// @gate experimental
it('propagates events in the correct order', () => {
const events = [];
const innerRef = React.createRef();
@ -367,6 +395,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
});
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(FocusResponder.displayName).toBe('Focus');
});

View File

@ -25,10 +25,15 @@ const initializeModules = hasPointerEvents => {
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
FocusWithinResponder = require('react-interactions/events/focus')
.FocusWithinResponder;
useFocusWithin = require('react-interactions/events/focus').useFocusWithin;
Scheduler = require('scheduler');
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
FocusWithinResponder = require('react-interactions/events/focus')
.FocusWithinResponder;
useFocusWithin = require('react-interactions/events/focus').useFocusWithin;
}
};
const forcePointerEvents = true;
@ -37,11 +42,6 @@ const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('FocusWithin responder', hasPointerEvents => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
initializeModules();
container = document.createElement('div');
@ -57,7 +57,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
describe('disabled', () => {
let onFocusWithinChange, onFocusWithinVisibleChange, ref;
beforeEach(() => {
const componentInit = () => {
onFocusWithinChange = jest.fn();
onFocusWithinVisibleChange = jest.fn();
ref = React.createRef();
@ -70,9 +70,11 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('prevents custom events being dispatched', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
target.blur();
@ -96,15 +98,17 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
);
};
beforeEach(() => {
const componentInit = () => {
onFocusWithinChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
ReactDOM.render(<Component show={true} />, container);
});
};
// @gate experimental
it('is called after "blur" and "focus" events on focus target', () => {
componentInit();
const target = createEventTarget(ref.current);
target.focus();
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
@ -114,7 +118,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called after "blur" and "focus" events on descendants', () => {
componentInit();
const target = createEventTarget(innerRef.current);
target.focus();
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
@ -124,7 +130,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is only called once when focus moves within and outside the subtree', () => {
componentInit();
const node = ref.current;
const innerNode1 = innerRef.current;
const innerNode2 = innerRef.current;
@ -165,15 +173,17 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
);
};
beforeEach(() => {
const componentInit = () => {
onFocusWithinVisibleChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
ReactDOM.render(<Component show={true} />, container);
});
};
// @gate experimental
it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
componentInit();
const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container);
// use keyboard first
@ -186,7 +196,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called after "focus" and "blur" on descendants if keyboard was used', () => {
componentInit();
const innerTarget = createEventTarget(innerRef.current);
const containerTarget = createEventTarget(container);
// use keyboard first
@ -199,7 +211,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
componentInit();
const node = ref.current;
const innerNode1 = innerRef.current;
const innerNode2 = innerRef2.current;
@ -235,7 +249,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
});
// @gate experimental
it('is not called after "focus" and "blur" events without keyboard', () => {
componentInit();
const innerTarget = createEventTarget(innerRef.current);
innerTarget.pointerdown();
innerTarget.pointerup();
@ -243,7 +259,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('is only called once when focus moves within and outside the subtree', () => {
componentInit();
const node = ref.current;
const innerNode1 = innerRef.current;
const innerNode2 = innerRef2.current;
@ -280,6 +298,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
innerRef2 = React.createRef();
});
// @gate experimental
it('is called after a focused element is unmounted', () => {
const Component = ({show}) => {
const listener = useFocusWithin({
@ -310,6 +329,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called after a nested focused element is unmounted', () => {
const Component = ({show}) => {
const listener = useFocusWithin({
@ -405,6 +425,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
});
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(FocusWithinResponder.displayName).toBe('FocusWithin');
});

View File

@ -24,8 +24,13 @@ function initializeModules(hasPointerEvents) {
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
HoverResponder = require('react-interactions/events/hover').HoverResponder;
useHover = require('react-interactions/events/hover').useHover;
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
HoverResponder = require('react-interactions/events/hover').HoverResponder;
useHover = require('react-interactions/events/hover').useHover;
}
}
const forcePointerEvents = true;
@ -34,11 +39,6 @@ const table = [[forcePointerEvents], [!forcePointerEvents]];
describe.each(table)('Hover responder', hasPointerEvents => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
@ -54,7 +54,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
describe('disabled', () => {
let onHoverChange, onHoverStart, onHoverMove, onHoverEnd, ref;
beforeEach(() => {
const componentInit = () => {
onHoverChange = jest.fn();
onHoverStart = jest.fn();
onHoverMove = jest.fn();
@ -71,9 +71,11 @@ describe.each(table)('Hover responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('does not call callbacks', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
target.pointerexit();
@ -87,7 +89,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
describe('onHoverStart', () => {
let onHoverStart, ref;
beforeEach(() => {
const componentInit = () => {
onHoverStart = jest.fn();
ref = React.createRef();
const Component = () => {
@ -97,22 +99,28 @@ describe.each(table)('Hover responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('is called for mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
expect(onHoverStart).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('is not called for touch pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
expect(onHoverStart).not.toBeCalled();
});
// @gate experimental
it('is called if a mouse pointer is used after a touch pointer', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
@ -124,7 +132,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
describe('onHoverChange', () => {
let onHoverChange, ref;
beforeEach(() => {
const componentInit = () => {
onHoverChange = jest.fn();
ref = React.createRef();
const Component = () => {
@ -134,9 +142,11 @@ describe.each(table)('Hover responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('is called for mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
expect(onHoverChange).toHaveBeenCalledTimes(1);
@ -146,7 +156,9 @@ describe.each(table)('Hover responder', hasPointerEvents => {
expect(onHoverChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is not called for touch pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
@ -157,7 +169,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
describe('onHoverEnd', () => {
let onHoverEnd, ref;
beforeEach(() => {
const componentInit = () => {
onHoverEnd = jest.fn();
ref = React.createRef();
const Component = () => {
@ -167,9 +179,11 @@ describe.each(table)('Hover responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('is called for mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
target.pointerexit();
@ -177,7 +191,9 @@ describe.each(table)('Hover responder', hasPointerEvents => {
});
if (hasPointerEvents) {
// @gate experimental
it('is called once for cancelled mouse pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerenter();
target.pointercancel();
@ -192,14 +208,18 @@ describe.each(table)('Hover responder', hasPointerEvents => {
});
}
// @gate experimental
it('is not called for touch pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
expect(onHoverEnd).not.toBeCalled();
});
// @gate experimental
it('should correctly work with React Portals', () => {
componentInit();
const portalNode = document.createElement('div');
const divRef = React.createRef();
const spanRef = React.createRef();
@ -227,6 +247,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
});
describe('onHoverMove', () => {
// @gate experimental
it('is called after the active pointer moves"', () => {
const onHoverMove = jest.fn();
const ref = React.createRef();
@ -250,6 +271,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
});
describe('nested Hover components', () => {
// @gate experimental
it('not propagate by default', () => {
const events = [];
const innerRef = React.createRef();
@ -310,10 +332,12 @@ describe.each(table)('Hover responder', hasPointerEvents => {
});
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(HoverResponder.displayName).toBe('Hover');
});
// @gate experimental
it('should correctly pass through event properties', () => {
const timeStamps = [];
const ref = React.createRef();

View File

@ -38,18 +38,18 @@ const modulesInit = () => {
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
InputResponder = require('react-interactions/events/input').InputResponder;
useInput = require('react-interactions/events/input').useInput;
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
InputResponder = require('react-interactions/events/input').InputResponder;
useInput = require('react-interactions/events/input').useInput;
}
};
describe('Input event responder', () => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
jest.resetModules();
modulesInit();
@ -66,7 +66,7 @@ describe('Input event responder', () => {
describe('disabled', () => {
let onChange, onValueChange, ref;
beforeEach(() => {
const componentInit = () => {
onChange = jest.fn();
onValueChange = jest.fn();
ref = React.createRef();
@ -80,9 +80,11 @@ describe('Input event responder', () => {
return <input ref={ref} DEPRECATED_flareListeners={listener} />;
}
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
it('prevents custom events being dispatched', () => {
componentInit();
ref.current.dispatchEvent(
new Event('change', {bubbles: true, cancelable: true}),
);
@ -104,6 +106,7 @@ describe('Input event responder', () => {
// keep track of the "current" value and only fire events when it changes.
// See https://github.com/facebook/react/pull/5746.
// @gate experimental
it('should consider initial text value to be current', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -151,6 +154,7 @@ describe('Input event responder', () => {
}
});
// @gate experimental
it('should consider initial checkbox checked=true to be current', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -194,6 +198,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(0);
});
// @gate experimental
it('should consider initial checkbox checked=false to be current', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -236,6 +241,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(0);
});
// @gate experimental
it('should fire change for checkbox input', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -283,6 +289,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(2);
});
// @gate experimental
it('should not fire change setting the value programmatically', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -345,6 +352,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(1);
});
// @gate experimental
it('should not distinguish equal string and number values', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -390,6 +398,7 @@ describe('Input event responder', () => {
});
// See a similar input test above for a detailed description of why.
// @gate experimental
it('should not fire change when setting checked programmatically', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -441,6 +450,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(1);
});
// @gate experimental
it('should only fire change for checked radio button once', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -477,6 +487,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(1);
});
// @gate experimental
it('should track radio button cousins in a group', () => {
let onChangeCalled1 = 0;
let onValueChangeCalled1 = 0;
@ -566,6 +577,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled2).toBe(1);
});
// @gate experimental
it('should deduplicate input value change events', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -672,6 +684,7 @@ describe('Input event responder', () => {
});
});
// @gate experimental
it('should listen for both change and input events when supported', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -711,6 +724,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(2);
});
// @gate experimental
it('should only fire events when the value changes for range inputs', () => {
let onChangeCalled = 0;
let onValueChangeCalled = 0;
@ -756,6 +770,7 @@ describe('Input event responder', () => {
expect(onValueChangeCalled).toBe(2);
});
// @gate experimental || build === "production"
it('does not crash for nodes with custom value property', () => {
let originalCreateElement;
// https://github.com/facebook/react/issues/10196
@ -793,6 +808,7 @@ describe('Input event responder', () => {
});
describe('concurrent mode', () => {
// @gate experimental
// @gate experimental
it('text input', () => {
const root = ReactDOM.createRoot(container);
@ -849,6 +865,7 @@ describe('Input event responder', () => {
expect(input.value).toBe('changed [!]');
});
// @gate experimental
// @gate experimental
it('checkbox input', () => {
const root = ReactDOM.createRoot(container);
@ -918,6 +935,7 @@ describe('Input event responder', () => {
expect(input.checked).toBe(false);
});
// @gate experimental
// @gate experimental
it('textarea', () => {
const root = ReactDOM.createRoot(container);
@ -976,6 +994,7 @@ describe('Input event responder', () => {
});
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(InputResponder.displayName).toBe('Input');
});

View File

@ -22,17 +22,17 @@ function initializeModules(hasPointerEvents) {
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
useKeyboard = require('react-interactions/events/keyboard').useKeyboard;
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
useKeyboard = require('react-interactions/events/keyboard').useKeyboard;
}
}
describe('Keyboard responder', () => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
initializeModules();
container = document.createElement('div');
@ -82,6 +82,7 @@ describe('Keyboard responder', () => {
};
}
// @gate experimental
test('propagates key event when a continuePropagation() is used', () => {
const {
onClickInner,
@ -104,6 +105,7 @@ describe('Keyboard responder', () => {
expect(onClickOuter).toBeCalled();
});
// @gate experimental
test('does not propagate key event by default', () => {
const {
onClickInner,
@ -129,7 +131,7 @@ describe('Keyboard responder', () => {
describe('disabled', () => {
let onKeyDown, onKeyUp, ref;
beforeEach(() => {
const componentInit = () => {
onKeyDown = jest.fn();
onKeyUp = jest.fn();
ref = React.createRef();
@ -138,9 +140,11 @@ describe('Keyboard responder', () => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
test('does not call callbacks', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown();
target.keyup();
@ -152,7 +156,7 @@ describe('Keyboard responder', () => {
describe('onClick', () => {
let onClick, ref;
beforeEach(() => {
const componentInit = () => {
onClick = jest.fn(e => {
e.preventDefault();
});
@ -162,10 +166,12 @@ describe('Keyboard responder', () => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// e.g, "Enter" on link
// @gate experimental
test('click is between key events', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({key: 'Enter'});
@ -187,7 +193,9 @@ describe('Keyboard responder', () => {
});
// e.g., "Spacebar" on button
// @gate experimental
test('click is after key events', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({key: 'Enter'});
@ -209,7 +217,9 @@ describe('Keyboard responder', () => {
});
// e.g, generated by a screen-reader
// @gate experimental
test('click is orphan', () => {
componentInit();
const target = createEventTarget(ref.current);
target.virtualclick();
expect(onClick).toHaveBeenCalledTimes(1);
@ -232,7 +242,7 @@ describe('Keyboard responder', () => {
describe('onKeyDown', () => {
let onKeyDown, ref;
beforeEach(() => {
const componentInit = () => {
onKeyDown = jest.fn();
ref = React.createRef();
const Component = () => {
@ -240,9 +250,11 @@ describe('Keyboard responder', () => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
test('key down', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Q'});
expect(onKeyDown).toHaveBeenCalledTimes(1);
@ -263,7 +275,9 @@ describe('Keyboard responder', () => {
);
});
// @gate experimental
test('modified key down', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({
key: 'Q',
@ -293,7 +307,7 @@ describe('Keyboard responder', () => {
describe('onKeyUp', () => {
let onKeyUp, ref;
beforeEach(() => {
const componentInit = () => {
onKeyUp = jest.fn();
ref = React.createRef();
const Component = () => {
@ -301,9 +315,11 @@ describe('Keyboard responder', () => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
test('key up', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Q'});
target.keyup({key: 'Q'});
@ -325,7 +341,9 @@ describe('Keyboard responder', () => {
);
});
// @gate experimental
test('modified key up', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Q'});
target.keyup({
@ -364,6 +382,7 @@ describe('Keyboard responder', () => {
return ref;
}
// @gate experimental
test('does not prevent native click by default', () => {
const onClick = jest.fn();
const preventDefault = jest.fn();
@ -381,6 +400,7 @@ describe('Keyboard responder', () => {
);
});
// @gate experimental
test('prevents native behaviour with preventDefault', () => {
const onClick = jest.fn(e => e.preventDefault());
const preventDefault = jest.fn();
@ -409,6 +429,7 @@ describe('Keyboard responder', () => {
return ref;
}
// @gate experimental
test('key config matches', () => {
const onKeyDown = jest.fn(e => {
if (e.key === 'Tab') {
@ -434,6 +455,7 @@ describe('Keyboard responder', () => {
);
});
// @gate experimental
test('key config matches (modifier keys)', () => {
const onKeyDown = jest.fn(e => {
if (e.key === 'Tab' && e.shiftKey) {
@ -457,6 +479,7 @@ describe('Keyboard responder', () => {
);
});
// @gate experimental
test('key config does not match (modifier keys)', () => {
const onKeyDown = jest.fn(e => {
if (e.key === 'Tab' && e.shiftKey) {

View File

@ -19,11 +19,6 @@ let Scheduler;
describe('mixing responders with the heritage event system', () => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
@ -39,11 +34,7 @@ describe('mixing responders with the heritage event system', () => {
container = null;
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
// @gate experimental
it('should properly only flush sync once when the event systems are mixed', () => {
const useTap = require('react-interactions/events/tap').useTap;
const ref = React.createRef();
@ -113,6 +104,7 @@ describe('mixing responders with the heritage event system', () => {
document.body.removeChild(newContainer);
});
// @gate experimental
it('should properly flush sync when the event systems are mixed with unstable_flushDiscreteUpdates', () => {
const useTap = require('react-interactions/events/tap').useTap;
const ref = React.createRef();
@ -182,6 +174,7 @@ describe('mixing responders with the heritage event system', () => {
document.body.removeChild(newContainer);
});
// @gate experimental
it(
'should only flush before outermost discrete event handler when mixing ' +
'event systems',
@ -241,6 +234,7 @@ describe('mixing responders with the heritage event system', () => {
);
describe('mixing the Input and Press repsonders', () => {
// @gate experimental
it('is async for non-input events', () => {
const useTap = require('react-interactions/events/tap').useTap;
const useInput = require('react-interactions/events/input').useInput;

View File

@ -30,19 +30,17 @@ function initializeModules(hasPointerEvents) {
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
usePress = require('react-interactions/events/press').usePress;
}
const pointerTypesTable = [['mouse'], ['touch']];
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
usePress = require('react-interactions/events/press').usePress;
}
}
describeWithPointerEvent('Press responder', hasPointerEvents => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
@ -59,7 +57,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
describe('disabled', () => {
let onPressStart, onPressChange, onPressMove, onPressEnd, onPress, ref;
beforeEach(() => {
const componentInit = () => {
onPressStart = jest.fn();
onPressChange = jest.fn();
onPressMove = jest.fn();
@ -78,9 +76,11 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
});
};
// @gate experimental
test('does not call callbacks for pointers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown();
target.pointerup();
@ -91,7 +91,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
expect(onPress).not.toBeCalled();
});
// @gate experimental
test('does not call callbacks for keyboard', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({key: 'Enter'});
@ -106,7 +108,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
describe('onPressStart', () => {
let onPressStart, ref;
beforeEach(() => {
const componentInit = () => {
onPressStart = jest.fn();
ref = React.createRef();
const Component = () => {
@ -117,21 +119,33 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer down: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
expect(onPressStart).toHaveBeenCalledTimes(1);
expect(onPressStart).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'pressstart'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer down: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
expect(onPressStart).toHaveBeenCalledTimes(1);
expect(onPressStart).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'pressstart'}),
);
},
);
// @gate experimental
it('is called after pointer down: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
expect(onPressStart).toHaveBeenCalledTimes(1);
expect(onPressStart).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'pressstart'}),
);
});
// @gate experimental
it('is called after middle-button pointer down', () => {
componentInit();
const target = createEventTarget(ref.current);
const pointerType = 'mouse';
target.pointerdown({
@ -150,7 +164,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called after virtual middle-button pointer down', () => {
componentInit();
const target = createEventTarget(ref.current);
const pointerType = 'mouse';
target.pointerdown({
@ -169,7 +185,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('ignores any events not caused by primary/middle-click or touch/pen contact', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({buttons: buttonsType.secondary});
target.pointerup({buttons: buttonsType.secondary});
@ -178,7 +196,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
expect(onPressStart).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('is called once after "keydown" events for Enter', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keydown({key: 'Enter'});
@ -189,7 +209,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called once after "keydown" events for Spacebar', () => {
componentInit();
const target = createEventTarget(ref.current);
const preventDefault = jest.fn();
target.keydown({key: ' ', preventDefault});
@ -204,7 +226,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is not called after "keydown" for other keys', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'a'});
expect(onPressStart).not.toBeCalled();
@ -214,7 +238,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
describe('onPressEnd', () => {
let onPressEnd, ref;
beforeEach(() => {
const componentInit = () => {
onPressEnd = jest.fn();
ref = React.createRef();
const Component = () => {
@ -225,22 +249,35 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer up: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
target.pointerup({pointerType: 'mouse'});
expect(onPressEnd).toHaveBeenCalledTimes(1);
expect(onPressEnd).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'pressend'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer up: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
target.pointerup({pointerType});
expect(onPressEnd).toHaveBeenCalledTimes(1);
expect(onPressEnd).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'pressend'}),
);
},
);
// @gate experimental
it('is called after pointer up: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
expect(onPressEnd).toHaveBeenCalledTimes(1);
expect(onPressEnd).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'pressend'}),
);
});
// @gate experimental
it('is called after middle-button pointer up', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
button: buttonType.auxiliary,
@ -258,7 +295,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called after virtual middle-button pointer up', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
button: buttonType.auxiliary,
@ -276,7 +315,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called after "keyup" event for Enter', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
// click occurs before keyup
@ -288,7 +329,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called after "keyup" event for Spacebar', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: ' '});
target.keyup({key: ' '});
@ -298,14 +341,18 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is not called after "keyup" event for other keys', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({key: 'a'});
expect(onPressEnd).not.toBeCalled();
});
// @gate experimental
it('is called with keyboard modifiers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({
@ -331,7 +378,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
describe('onPressChange', () => {
let onPressChange, ref;
beforeEach(() => {
const componentInit = () => {
onPressChange = jest.fn();
ref = React.createRef();
const Component = () => {
@ -342,22 +389,35 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer down and up: %s', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
expect(onPressChange).toHaveBeenCalledTimes(1);
expect(onPressChange).toHaveBeenCalledWith(true);
target.pointerup({pointerType: 'mouse'});
expect(onPressChange).toHaveBeenCalledTimes(2);
expect(onPressChange).toHaveBeenCalledWith(false);
});
it.each(pointerTypesTable)(
'is called after pointer down and up: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
expect(onPressChange).toHaveBeenCalledTimes(1);
expect(onPressChange).toHaveBeenCalledWith(true);
target.pointerup({pointerType});
expect(onPressChange).toHaveBeenCalledTimes(2);
expect(onPressChange).toHaveBeenCalledWith(false);
},
);
// @gate experimental
it('is called after pointer down and up: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
expect(onPressChange).toHaveBeenCalledTimes(1);
expect(onPressChange).toHaveBeenCalledWith(true);
target.pointerup({pointerType: 'touch'});
expect(onPressChange).toHaveBeenCalledTimes(2);
expect(onPressChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called after valid "keydown" and "keyup" events', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
expect(onPressChange).toHaveBeenCalledTimes(1);
@ -371,7 +431,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
describe('onPress', () => {
let onPress, ref;
beforeEach(() => {
const componentInit = () => {
onPress = jest.fn();
ref = React.createRef();
const Component = () => {
@ -388,22 +448,35 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
right: 100,
});
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer up: %s', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
target.pointerup({pointerType: 'mouse', x: 10, y: 10});
expect(onPress).toHaveBeenCalledTimes(1);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'press'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer up: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
target.pointerup({pointerType, x: 10, y: 10});
expect(onPress).toHaveBeenCalledTimes(1);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'press'}),
);
},
);
// @gate experimental
it('is called after pointer up: %s', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch', x: 10, y: 10});
expect(onPress).toHaveBeenCalledTimes(1);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'press'}),
);
});
// @gate experimental
it('is not called after middle-button press', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
button: buttonType.auxiliary,
@ -414,7 +487,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
expect(onPress).not.toHaveBeenCalled();
});
// @gate experimental
it('is not called after virtual middle-button press', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
button: buttonType.auxiliary,
@ -425,7 +500,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
expect(onPress).not.toHaveBeenCalled();
});
// @gate experimental
it('is called after valid "keyup" event', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({key: 'Enter'});
@ -435,7 +512,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is not called after invalid "keyup" event', () => {
componentInit();
const inputRef = React.createRef();
const Component = () => {
const listener = usePress({onPress});
@ -450,7 +529,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
expect(onPress).not.toBeCalled();
});
// @gate experimental
it('is called with modifier keys', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({metaKey: true, pointerType: 'mouse'});
target.pointerup({metaKey: true, pointerType: 'mouse'});
@ -463,7 +544,9 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called once after virtual screen reader "click" event', () => {
componentInit();
const target = createEventTarget(ref.current);
const preventDefault = jest.fn();
target.virtualclick({preventDefault});
@ -481,7 +564,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
describe('onPressMove', () => {
let onPressMove, ref;
beforeEach(() => {
const componentInit = () => {
onPressMove = jest.fn();
ref = React.createRef();
const Component = () => {
@ -498,25 +581,41 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
right: 100,
});
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer move: mouse', () => {
componentInit();
const node = ref.current;
const target = createEventTarget(node);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.pointerdown({pointerType: 'mouse'});
target.pointermove({pointerType: 'mouse', x: 10, y: 10});
target.pointermove({pointerType: 'mouse', x: 20, y: 20});
expect(onPressMove).toHaveBeenCalledTimes(2);
expect(onPressMove).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'pressmove'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer move: %s',
pointerType => {
const node = ref.current;
const target = createEventTarget(node);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.pointerdown({pointerType});
target.pointermove({pointerType, x: 10, y: 10});
target.pointermove({pointerType, x: 20, y: 20});
expect(onPressMove).toHaveBeenCalledTimes(2);
expect(onPressMove).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'pressmove'}),
);
},
);
// @gate experimental
it('is called after pointer move: touch', () => {
componentInit();
const node = ref.current;
const target = createEventTarget(node);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.pointerdown({pointerType: 'touch'});
target.pointermove({pointerType: 'touch', x: 10, y: 10});
target.pointermove({pointerType: 'touch', x: 20, y: 20});
expect(onPressMove).toHaveBeenCalledTimes(2);
expect(onPressMove).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'pressmove'}),
);
});
// @gate experimental
it('is not called if pointer move occurs during keyboard press', () => {
componentInit();
const target = createEventTarget(ref.current);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.keydown({key: 'Enter'});
@ -531,6 +630,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
});
describe('link components', () => {
// @gate experimental
it('prevents native behavior by default', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -551,6 +651,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('prevents native behaviour for keyboard events by default', () => {
const onPress = jest.fn();
const preventDefaultClick = jest.fn();
@ -575,6 +676,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('deeply prevents native behaviour by default', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -596,6 +698,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
expect(preventDefault).toBeCalled();
});
// @gate experimental
it('prevents native behaviour by default with nested elements', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -620,6 +723,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('uses native behaviour for interactions with modifier keys', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -642,6 +746,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
});
});
// @gate experimental
it('uses native behaviour for pointer events if preventDefault is false', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -662,6 +767,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('uses native behaviour for keyboard events if preventDefault is false', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -685,6 +791,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
});
});
// @gate experimental
it('should not trigger an invariant in addRootEventTypes()', () => {
const ref = React.createRef();
@ -701,6 +808,7 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
target.pointerdown();
});
// @gate experimental
it('when blur occurs on a pressed target, we should disengage press', () => {
const onPress = jest.fn();
const onPressStart = jest.fn();

View File

@ -30,9 +30,14 @@ function initializeModules(hasPointerEvents) {
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react');
ReactDOM = require('react-dom');
PressResponder = require('react-interactions/events/press-legacy')
.PressResponder;
usePress = require('react-interactions/events/press-legacy').usePress;
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
PressResponder = require('react-interactions/events/press-legacy')
.PressResponder;
usePress = require('react-interactions/events/press-legacy').usePress;
}
}
function removePressMoveStrings(eventString) {
@ -50,11 +55,6 @@ const pointerTypesTable = [['mouse'], ['touch']];
describe.each(environmentTable)('Press responder', hasPointerEvents => {
let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
initializeModules(hasPointerEvents);
container = document.createElement('div');
@ -71,7 +71,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe('disabled', () => {
let onPressStart, onPress, onPressEnd, ref;
beforeEach(() => {
const componentInit = () => {
onPressStart = jest.fn();
onPress = jest.fn();
onPressEnd = jest.fn();
@ -87,9 +87,11 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
});
};
// @gate experimental
it('does not call callbacks', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown();
target.pointerup();
@ -102,7 +104,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe('onPressStart', () => {
let onPressStart, ref;
beforeEach(() => {
const componentInit = () => {
onPressStart = jest.fn();
ref = React.createRef();
const Component = () => {
@ -113,21 +115,33 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer down: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
expect(onPressStart).toHaveBeenCalledTimes(1);
expect(onPressStart).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'pressstart'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer down: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
expect(onPressStart).toHaveBeenCalledTimes(1);
expect(onPressStart).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'pressstart'}),
);
},
);
// @gate experimental
it('is called after pointer down: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
expect(onPressStart).toHaveBeenCalledTimes(1);
expect(onPressStart).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'pressstart'}),
);
});
// @gate experimental
it('is called after middle-button pointer down', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
button: buttonType.auxiliary,
@ -144,7 +158,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is not called after pointer move following middle-button press', () => {
componentInit();
const node = ref.current;
const target = createEventTarget(node);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
@ -159,7 +175,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPressStart).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('ignores any events not caused by primary/middle-click or touch/pen contact', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({buttons: buttonsType.secondary});
target.pointerup({buttons: buttonsType.secondary});
@ -168,7 +186,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPressStart).toHaveBeenCalledTimes(0);
});
// @gate experimental
it('is called once after "keydown" events for Enter', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keydown({key: 'Enter'});
@ -179,7 +199,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called once after "keydown" events for Spacebar', () => {
componentInit();
const target = createEventTarget(ref.current);
const preventDefault = jest.fn();
target.keydown({key: ' ', preventDefault});
@ -194,7 +216,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is not called after "keydown" for other keys', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'a'});
expect(onPressStart).not.toBeCalled();
@ -204,7 +228,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe('onPressEnd', () => {
let onPressEnd, ref;
beforeEach(() => {
const componentInit = () => {
onPressEnd = jest.fn();
ref = React.createRef();
const Component = () => {
@ -215,22 +239,35 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer up: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
target.pointerup({pointerType: 'mouse'});
expect(onPressEnd).toHaveBeenCalledTimes(1);
expect(onPressEnd).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'pressend'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer up: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
target.pointerup({pointerType});
expect(onPressEnd).toHaveBeenCalledTimes(1);
expect(onPressEnd).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'pressend'}),
);
},
);
// @gate experimental
it('is called after pointer up: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch'});
expect(onPressEnd).toHaveBeenCalledTimes(1);
expect(onPressEnd).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'pressend'}),
);
});
// @gate experimental
it('is called after middle-button pointer up', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
buttons: buttonsType.auxiliary,
@ -247,7 +284,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called after "keyup" event for Enter', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
// click occurs before keyup
@ -259,7 +298,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called after "keyup" event for Spacebar', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: ' '});
target.keyup({key: ' '});
@ -269,14 +310,18 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is not called after "keyup" event for other keys', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({key: 'a'});
expect(onPressEnd).not.toBeCalled();
});
// @gate experimental
it('is called with keyboard modifiers', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({
@ -302,7 +347,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe('onPressChange', () => {
let onPressChange, ref;
beforeEach(() => {
const componentInit = () => {
onPressChange = jest.fn();
ref = React.createRef();
const Component = () => {
@ -313,22 +358,35 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer down and up: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
expect(onPressChange).toHaveBeenCalledTimes(1);
expect(onPressChange).toHaveBeenCalledWith(true);
target.pointerup({pointerType: 'mouse'});
expect(onPressChange).toHaveBeenCalledTimes(2);
expect(onPressChange).toHaveBeenCalledWith(false);
});
it.each(pointerTypesTable)(
'is called after pointer down and up: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
expect(onPressChange).toHaveBeenCalledTimes(1);
expect(onPressChange).toHaveBeenCalledWith(true);
target.pointerup({pointerType});
expect(onPressChange).toHaveBeenCalledTimes(2);
expect(onPressChange).toHaveBeenCalledWith(false);
},
);
// @gate experimental
it('is called after pointer down and up: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
expect(onPressChange).toHaveBeenCalledTimes(1);
expect(onPressChange).toHaveBeenCalledWith(true);
target.pointerup({pointerType: 'touch'});
expect(onPressChange).toHaveBeenCalledTimes(2);
expect(onPressChange).toHaveBeenCalledWith(false);
});
// @gate experimental
it('is called after valid "keydown" and "keyup" events', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
expect(onPressChange).toHaveBeenCalledTimes(1);
@ -342,7 +400,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe('onPress', () => {
let onPress, ref;
beforeEach(() => {
const componentInit = () => {
onPress = jest.fn();
ref = React.createRef();
const Component = () => {
@ -359,22 +417,35 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
right: 100,
});
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer up: mouse', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'mouse'});
target.pointerup({pointerType: 'mouse', x: 10, y: 10});
expect(onPress).toHaveBeenCalledTimes(1);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'press'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer up: %s',
pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
target.pointerup({pointerType, x: 10, y: 10});
expect(onPress).toHaveBeenCalledTimes(1);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'press'}),
);
},
);
// @gate experimental
it('is called after pointer up: touch', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointerup({pointerType: 'touch', x: 10, y: 10});
expect(onPress).toHaveBeenCalledTimes(1);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'press'}),
);
});
// @gate experimental
it('is not called after middle-button press', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
buttons: buttonsType.auxiliary,
@ -384,7 +455,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPress).not.toHaveBeenCalled();
});
// @gate experimental
it('is not called after virtual middle-button press', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({
button: buttonType.auxiliary,
@ -395,7 +468,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPress).not.toHaveBeenCalled();
});
// @gate experimental
it('is called after valid "keyup" event', () => {
componentInit();
const target = createEventTarget(ref.current);
target.keydown({key: 'Enter'});
target.keyup({key: 'Enter'});
@ -405,7 +480,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is not called after invalid "keyup" event', () => {
componentInit();
const inputRef = React.createRef();
const Component = () => {
const listener = usePress({onPress});
@ -420,7 +497,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPress).not.toBeCalled();
});
// @gate experimental
it('is called with modifier keys', () => {
componentInit();
const target = createEventTarget(ref.current);
target.pointerdown({metaKey: true, pointerType: 'mouse'});
target.pointerup({metaKey: true, pointerType: 'mouse'});
@ -433,7 +512,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('is called if target rect is not right but the target is (for mouse events)', () => {
componentInit();
const buttonRef = React.createRef();
const divRef = React.createRef();
@ -455,7 +536,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPress).toBeCalled();
});
// @gate experimental
it('is called once after virtual screen reader "click" event', () => {
componentInit();
const target = createEventTarget(ref.current);
const preventDefault = jest.fn();
target.virtualclick({preventDefault});
@ -473,7 +556,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe('onPressMove', () => {
let onPressMove, ref;
beforeEach(() => {
const componentInit = () => {
onPressMove = jest.fn();
ref = React.createRef();
const Component = () => {
@ -490,25 +573,41 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
right: 100,
});
document.elementFromPoint = () => ref.current;
};
// @gate experimental
it('is called after pointer move: mouse', () => {
componentInit();
const node = ref.current;
const target = createEventTarget(node);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.pointerdown({pointerType: 'mouse'});
target.pointermove({pointerType: 'mouse', x: 10, y: 10});
target.pointermove({pointerType: 'mouse', x: 20, y: 20});
expect(onPressMove).toHaveBeenCalledTimes(2);
expect(onPressMove).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'mouse', type: 'pressmove'}),
);
});
it.each(pointerTypesTable)(
'is called after pointer move: %s',
pointerType => {
const node = ref.current;
const target = createEventTarget(node);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.pointerdown({pointerType});
target.pointermove({pointerType, x: 10, y: 10});
target.pointermove({pointerType, x: 20, y: 20});
expect(onPressMove).toHaveBeenCalledTimes(2);
expect(onPressMove).toHaveBeenCalledWith(
expect.objectContaining({pointerType, type: 'pressmove'}),
);
},
);
// @gate experimental
it('is called after pointer move: touch', () => {
componentInit();
const node = ref.current;
const target = createEventTarget(node);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.pointerdown({pointerType: 'touch'});
target.pointermove({pointerType: 'touch', x: 10, y: 10});
target.pointermove({pointerType: 'touch', x: 20, y: 20});
expect(onPressMove).toHaveBeenCalledTimes(2);
expect(onPressMove).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'touch', type: 'pressmove'}),
);
});
// @gate experimental
it('is not called if pointer move occurs during keyboard press', () => {
componentInit();
const target = createEventTarget(ref.current);
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
target.keydown({key: 'Enter'});
@ -525,7 +624,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe.each(pointerTypesTable)('press with movement: %s', pointerType => {
let events, ref, outerRef;
beforeEach(() => {
const componentInit = () => {
events = [];
ref = React.createRef();
outerRef = React.createRef();
@ -548,7 +647,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current;
});
};
const rectMock = {width: 100, height: 100, x: 50, y: 50};
const pressRectOffset = 20;
@ -569,7 +668,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
* HitRect X <= Move to X and release
*
*/
// @gate experimental
it('"onPress*" events are called immediately', () => {
componentInit();
const target = createEventTarget(ref.current);
target.setBoundingClientRect(rectMock);
target.pointerdown({pointerType});
@ -585,7 +686,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
]);
});
// @gate experimental
it('"onPress*" events are correctly called with target change', () => {
componentInit();
const target = createEventTarget(ref.current);
const outerTarget = createEventTarget(outerRef.current);
target.setBoundingClientRect(rectMock);
@ -614,7 +717,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
]);
});
// @gate experimental
it('press retention offset can be configured', () => {
componentInit();
const localEvents = [];
const localRef = React.createRef();
const createEventHandler = msg => () => {
@ -654,7 +759,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
]);
});
// @gate experimental
it('responder region accounts for decrease in element dimensions', () => {
componentInit();
const target = createEventTarget(ref.current);
target.setBoundingClientRect(rectMock);
target.pointerdown({pointerType});
@ -674,7 +781,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
]);
});
// @gate experimental
it('responder region accounts for increase in element dimensions', () => {
componentInit();
const target = createEventTarget(ref.current);
target.setBoundingClientRect(rectMock);
target.pointerdown({pointerType});
@ -704,7 +813,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
*
* X <= Move to X and release
*/
// @gate experimental
it('"onPress" is not called on release', () => {
componentInit();
const target = createEventTarget(ref.current);
const targetContainer = createEventTarget(container);
target.setBoundingClientRect(rectMock);
@ -727,7 +838,9 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
});
});
// @gate experimental
it('"onPress" is called on re-entry to hit rect', () => {
componentInit();
const target = createEventTarget(ref.current);
const targetContainer = createEventTarget(container);
target.setBoundingClientRect(rectMock);
@ -759,6 +872,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
describe('nested responders', () => {
if (hasPointerEvents) {
// @gate experimental
it('dispatch events in the correct order', () => {
const events = [];
const ref = React.createRef();
@ -819,6 +933,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
}
describe('correctly not propagate', () => {
// @gate experimental
it('for onPress', () => {
const ref = React.createRef();
const onPress = jest.fn();
@ -845,6 +960,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPress).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('for onPressStart/onPressEnd', () => {
const ref = React.createRef();
const onPressStart = jest.fn();
@ -874,6 +990,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPressEnd).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('for onPressChange', () => {
const ref = React.createRef();
const onPressChange = jest.fn();
@ -903,6 +1020,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
});
describe('link components', () => {
// @gate experimental
it('prevents native behavior by default', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -923,6 +1041,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('prevents native behaviour for keyboard events by default', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -943,6 +1062,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('deeply prevents native behaviour by default', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -964,6 +1084,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(preventDefault).toBeCalled();
});
// @gate experimental
it('prevents native behaviour by default with nested elements', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -988,6 +1109,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('uses native behaviour for interactions with modifier keys', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -1010,6 +1132,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
});
});
// @gate experimental
it('uses native behaviour for pointer events if preventDefault is false', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -1030,6 +1153,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
);
});
// @gate experimental
it('uses native behaviour for keyboard events if preventDefault is false', () => {
const onPress = jest.fn();
const preventDefault = jest.fn();
@ -1053,7 +1177,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
});
describe('responder cancellation', () => {
it.each(pointerTypesTable)('ends on pointer cancel', pointerType => {
// @gate experimental
it('ends on pointer cancel: mouse', () => {
const onPressEnd = jest.fn();
const ref = React.createRef();
@ -1064,12 +1189,30 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.pointerdown({pointerType});
target.pointercancel({pointerType});
target.pointerdown({pointerType: 'mouse'});
target.pointercancel({pointerType: 'mouse'});
expect(onPressEnd).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('ends on pointer cancel: touch', () => {
const onPressEnd = jest.fn();
const ref = React.createRef();
const Component = () => {
const listener = usePress({onPressEnd});
return <a href="#" ref={ref} DEPRECATED_flareListeners={listener} />;
};
ReactDOM.render(<Component />, container);
const target = createEventTarget(ref.current);
target.pointerdown({pointerType: 'touch'});
target.pointercancel({pointerType: 'touch'});
expect(onPressEnd).toHaveBeenCalledTimes(1);
});
});
// @gate experimental
it('does end on "scroll" to document (not mouse)', () => {
const onPressEnd = jest.fn();
const ref = React.createRef();
@ -1087,6 +1230,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPressEnd).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('does end on "scroll" to a parent container (not mouse)', () => {
const onPressEnd = jest.fn();
const ref = React.createRef();
@ -1109,6 +1253,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPressEnd).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('does not end on "scroll" to an element outside', () => {
const onPressEnd = jest.fn();
const ref = React.createRef();
@ -1132,10 +1277,12 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPressEnd).not.toBeCalled();
});
// @gate experimental
it('expect displayName to show up for event component', () => {
expect(PressResponder.displayName).toBe('Press');
});
// @gate experimental
it('should not trigger an invariant in addRootEventTypes()', () => {
const ref = React.createRef();
@ -1152,6 +1299,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
target.pointerdown();
});
// @gate experimental
it('event.preventDefault works as expected', () => {
const onPress = jest.fn(e => e.preventDefault());
const onPressStart = jest.fn(e => e.preventDefault());
@ -1173,6 +1321,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(onPressEnd).toBeCalled();
});
// @gate experimental
it('when blur occurs on a pressed target, we should disengage press', () => {
const onPress = jest.fn();
const onPressStart = jest.fn();

View File

@ -24,6 +24,7 @@ export const {
getOrCreateRootContainer,
createRoot,
createBlockingRoot,
createLegacyRoot,
getChildrenAsJSX,
getPendingChildrenAsJSX,
createPortal,

View File

@ -50,16 +50,12 @@ function initReactDOMServer() {
}
describe('ReactFiberFundamental', () => {
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
describe('NoopRenderer', () => {
beforeEach(() => {
initNoopRenderer();
});
// @gate experimental
it('should render a simple fundamental component with a single child', () => {
const FundamentalComponent = createReactFundamentalComponent({
reconcileChildren: true,
@ -94,6 +90,7 @@ describe('ReactFiberFundamental', () => {
initTestRenderer();
});
// @gate experimental
it('should render a simple fundamental component with a single child', () => {
const FundamentalComponent = createReactFundamentalComponent({
reconcileChildren: true,
@ -130,6 +127,7 @@ describe('ReactFiberFundamental', () => {
initReactDOM();
});
// @gate experimental
it('should render a simple fundamental component with a single child', () => {
const FundamentalComponent = createReactFundamentalComponent({
reconcileChildren: true,
@ -155,6 +153,7 @@ describe('ReactFiberFundamental', () => {
expect(container.innerHTML).toBe('');
});
// @gate experimental
it('should render a simple fundamental component without reconcileChildren', () => {
const FundamentalComponent = createReactFundamentalComponent({
reconcileChildren: false,
@ -186,6 +185,7 @@ describe('ReactFiberFundamental', () => {
initReactDOMServer();
});
// @gate experimental
it('should render a simple fundamental component with a single child', () => {
const getInstance = jest.fn();
const FundamentalComponent = createReactFundamentalComponent({
@ -210,6 +210,7 @@ describe('ReactFiberFundamental', () => {
expect(output).toBe('<div>Hello world again</div>');
});
// @gate experimental
it('should render a simple fundamental component without reconcileChildren', () => {
const FundamentalComponent = createReactFundamentalComponent({
reconcileChildren: false,

View File

@ -1121,384 +1121,380 @@ describe('ReactHooksWithNoopRenderer', () => {
},
);
if (
require('shared/ReactFeatureFlags')
.deferPassiveEffectCleanupDuringUnmount &&
require('shared/ReactFeatureFlags')
.runAllPassiveEffectDestroysBeforeCreates
) {
it('defers passive effect destroy functions during unmount', () => {
function Child({bar, foo}) {
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive bar create');
return () => {
Scheduler.unstable_yieldValue('passive bar destroy');
};
}, [bar]);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout bar create');
return () => {
Scheduler.unstable_yieldValue('layout bar destroy');
};
}, [bar]);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive foo create');
return () => {
Scheduler.unstable_yieldValue('passive foo destroy');
};
}, [foo]);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout foo create');
return () => {
Scheduler.unstable_yieldValue('layout foo destroy');
};
}, [foo]);
Scheduler.unstable_yieldValue('render');
return null;
}
// @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('defers passive effect destroy functions during unmount', () => {
function Child({bar, foo}) {
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive bar create');
return () => {
Scheduler.unstable_yieldValue('passive bar destroy');
};
}, [bar]);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout bar create');
return () => {
Scheduler.unstable_yieldValue('layout bar destroy');
};
}, [bar]);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive foo create');
return () => {
Scheduler.unstable_yieldValue('passive foo destroy');
};
}, [foo]);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout foo create');
return () => {
Scheduler.unstable_yieldValue('layout foo destroy');
};
}, [foo]);
Scheduler.unstable_yieldValue('render');
return null;
}
act(() => {
ReactNoop.render(<Child bar={1} foo={1} />, () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'render',
'layout bar create',
'layout foo create',
'Sync effect',
]);
// Effects are deferred until after the commit
expect(Scheduler).toFlushAndYield([
'passive bar create',
'passive foo create',
]);
});
// This update is exists to test an internal implementation detail:
// Effects without updating dependencies lose their layout/passive tag during an update.
act(() => {
ReactNoop.render(<Child bar={1} foo={2} />, () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'render',
'layout foo destroy',
'layout foo create',
'Sync effect',
]);
// Effects are deferred until after the commit
expect(Scheduler).toFlushAndYield([
'passive foo destroy',
'passive foo create',
]);
});
// Unmount the component and verify that passive destroy functions are deferred until post-commit.
act(() => {
ReactNoop.render(null, () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'layout bar destroy',
'layout foo destroy',
'Sync effect',
]);
// Effects are deferred until after the commit
expect(Scheduler).toFlushAndYield([
'passive bar destroy',
'passive foo destroy',
]);
});
act(() => {
ReactNoop.render(<Child bar={1} foo={1} />, () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'render',
'layout bar create',
'layout foo create',
'Sync effect',
]);
// Effects are deferred until after the commit
expect(Scheduler).toFlushAndYield([
'passive bar create',
'passive foo create',
]);
});
it('does not warn about state updates for unmounted components with pending passive unmounts', () => {
let completePendingRequest = null;
function Component() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout create');
return () => {
Scheduler.unstable_yieldValue('layout destroy');
};
}, []);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive create');
// Mimic an XHR request with a complete handler that updates state.
completePendingRequest = () => setDidLoad(true);
return () => {
Scheduler.unstable_yieldValue('passive destroy');
};
}, []);
return didLoad;
}
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'layout create',
'Sync effect',
]);
ReactNoop.flushPassiveEffects();
expect(Scheduler).toHaveYielded(['passive create']);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYieldThrough(['layout destroy']);
// Simulate an XHR completing, which will cause a state update-
// but should not log a warning.
completePendingRequest();
ReactNoop.flushPassiveEffects();
expect(Scheduler).toHaveYielded(['passive destroy']);
});
// This update is exists to test an internal implementation detail:
// Effects without updating dependencies lose their layout/passive tag during an update.
act(() => {
ReactNoop.render(<Child bar={1} foo={2} />, () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'render',
'layout foo destroy',
'layout foo create',
'Sync effect',
]);
// Effects are deferred until after the commit
expect(Scheduler).toFlushAndYield([
'passive foo destroy',
'passive foo create',
]);
});
it('still warns about state updates for unmounted components with no pending passive unmounts', () => {
let completePendingRequest = null;
function Component() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout create');
// Mimic an XHR request with a complete handler that updates state.
completePendingRequest = () => setDidLoad(true);
return () => {
Scheduler.unstable_yieldValue('layout destroy');
};
}, []);
return didLoad;
}
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'layout create',
'Sync effect',
]);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYieldThrough(['layout destroy']);
// Simulate an XHR completing.
expect(completePendingRequest).toErrorDev(
"Warning: Can't perform a React state update on an unmounted component.",
);
});
// Unmount the component and verify that passive destroy functions are deferred until post-commit.
act(() => {
ReactNoop.render(null, () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'layout bar destroy',
'layout foo destroy',
'Sync effect',
]);
// Effects are deferred until after the commit
expect(Scheduler).toFlushAndYield([
'passive bar destroy',
'passive foo destroy',
]);
});
});
it('still warns if there are pending passive unmount effects but not for the current fiber', () => {
let completePendingRequest = null;
function ComponentWithXHR() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('a:layout create');
return () => {
Scheduler.unstable_yieldValue('a:layout destroy');
};
}, []);
React.useEffect(() => {
Scheduler.unstable_yieldValue('a:passive create');
// Mimic an XHR request with a complete handler that updates state.
completePendingRequest = () => setDidLoad(true);
}, []);
return didLoad;
}
// @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('does not warn about state updates for unmounted components with pending passive unmounts', () => {
let completePendingRequest = null;
function Component() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout create');
return () => {
Scheduler.unstable_yieldValue('layout destroy');
};
}, []);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive create');
// Mimic an XHR request with a complete handler that updates state.
completePendingRequest = () => setDidLoad(true);
return () => {
Scheduler.unstable_yieldValue('passive destroy');
};
}, []);
return didLoad;
}
function ComponentWithPendingPassiveUnmount() {
React.useEffect(() => {
Scheduler.unstable_yieldValue('b:passive create');
return () => {
Scheduler.unstable_yieldValue('b:passive destroy');
};
}, []);
return null;
}
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'layout create',
'Sync effect',
]);
ReactNoop.flushPassiveEffects();
expect(Scheduler).toHaveYielded(['passive create']);
act(() => {
ReactNoop.renderToRootWithID(
<>
<ComponentWithXHR />
<ComponentWithPendingPassiveUnmount />
</>,
'root',
() => Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'a:layout create',
'Sync effect',
]);
ReactNoop.flushPassiveEffects();
expect(Scheduler).toHaveYielded([
'a:passive create',
'b:passive create',
]);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYieldThrough(['layout destroy']);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYieldThrough(['a:layout destroy']);
// Simulate an XHR completing, which will cause a state update-
// but should not log a warning.
completePendingRequest();
// Simulate an XHR completing in the component without a pending passive effect..
expect(completePendingRequest).toErrorDev(
"Warning: Can't perform a React state update on an unmounted component.",
);
});
ReactNoop.flushPassiveEffects();
expect(Scheduler).toHaveYielded(['passive destroy']);
});
});
it('still warns if there are updates after pending passive unmount effects have been flushed', () => {
let updaterFunction;
it('warns about state updates for unmounted components with no pending passive unmounts', () => {
let completePendingRequest = null;
function Component() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout create');
// Mimic an XHR request with a complete handler that updates state.
completePendingRequest = () => setDidLoad(true);
return () => {
Scheduler.unstable_yieldValue('layout destroy');
};
}, []);
return didLoad;
}
function Component() {
Scheduler.unstable_yieldValue('Component');
const [state, setState] = React.useState(false);
updaterFunction = setState;
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive create');
return () => {
Scheduler.unstable_yieldValue('passive destroy');
};
}, []);
return state;
}
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'layout create',
'Sync effect',
]);
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
});
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYieldThrough(['layout destroy']);
// Simulate an XHR completing.
expect(completePendingRequest).toErrorDev(
"Warning: Can't perform a React state update on an unmounted component.",
);
});
});
// @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('still warns if there are pending passive unmount effects but not for the current fiber', () => {
let completePendingRequest = null;
function ComponentWithXHR() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('a:layout create');
return () => {
Scheduler.unstable_yieldValue('a:layout destroy');
};
}, []);
React.useEffect(() => {
Scheduler.unstable_yieldValue('a:passive create');
// Mimic an XHR request with a complete handler that updates state.
completePendingRequest = () => setDidLoad(true);
}, []);
return didLoad;
}
function ComponentWithPendingPassiveUnmount() {
React.useEffect(() => {
Scheduler.unstable_yieldValue('b:passive create');
return () => {
Scheduler.unstable_yieldValue('b:passive destroy');
};
}, []);
return null;
}
act(() => {
ReactNoop.renderToRootWithID(
<>
<ComponentWithXHR />
<ComponentWithPendingPassiveUnmount />
</>,
'root',
() => Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'a:layout create',
'Sync effect',
]);
ReactNoop.flushPassiveEffects();
expect(Scheduler).toHaveYielded([
'a:passive create',
'b:passive create',
]);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYieldThrough(['a:layout destroy']);
// Simulate an XHR completing in the component without a pending passive effect..
expect(completePendingRequest).toErrorDev(
"Warning: Can't perform a React state update on an unmounted component.",
);
});
});
it('warns if there are updates after pending passive unmount effects have been flushed', () => {
let updaterFunction;
function Component() {
Scheduler.unstable_yieldValue('Component');
const [state, setState] = React.useState(false);
updaterFunction = setState;
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive create');
return () => {
Scheduler.unstable_yieldValue('passive destroy');
};
}, []);
return state;
}
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
});
expect(Scheduler).toHaveYielded([
'Component',
'Sync effect',
'passive create',
]);
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYield(['passive destroy']);
act(() => {
expect(() => {
updaterFunction(true);
}).toErrorDev(
"Warning: Can't perform a React state update on an unmounted component. " +
'This is a no-op, but it indicates a memory leak in your application. ' +
'To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.\n' +
' in Component (at **)',
);
});
});
it('does not show a warning when a component updates its own state from within passive unmount function', () => {
function Component() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive create');
return () => {
setDidLoad(true);
Scheduler.unstable_yieldValue('passive destroy');
};
}, []);
return didLoad;
}
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'Sync effect',
'passive create',
]);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYield(['passive destroy']);
act(() => {
expect(() => {
updaterFunction(true);
}).toErrorDev(
"Warning: Can't perform a React state update on an unmounted component. " +
'This is a no-op, but it indicates a memory leak in your application. ' +
'To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.\n' +
' in Component (at **)',
);
});
});
});
it('does not show a warning when a component updates its own state from within passive unmount function', () => {
function Component() {
Scheduler.unstable_yieldValue('Component');
const [didLoad, setDidLoad] = React.useState(false);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive create');
return () => {
setDidLoad(true);
Scheduler.unstable_yieldValue('passive destroy');
};
}, []);
return didLoad;
}
it('does not show a warning when a component updates a childs state from within passive unmount function', () => {
function Parent() {
Scheduler.unstable_yieldValue('Parent');
const updaterRef = React.useRef(null);
React.useEffect(() => {
Scheduler.unstable_yieldValue('Parent passive create');
return () => {
updaterRef.current(true);
Scheduler.unstable_yieldValue('Parent passive destroy');
};
}, []);
return <Child updaterRef={updaterRef} />;
}
act(() => {
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
Scheduler.unstable_yieldValue('Sync effect'),
);
expect(Scheduler).toFlushAndYieldThrough([
'Component',
'Sync effect',
'passive create',
]);
function Child({updaterRef}) {
Scheduler.unstable_yieldValue('Child');
const [state, setState] = React.useState(false);
React.useEffect(() => {
Scheduler.unstable_yieldValue('Child passive create');
updaterRef.current = setState;
}, []);
return state;
}
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYield(['passive destroy']);
});
act(() => {
ReactNoop.renderToRootWithID(<Parent />, 'root');
expect(Scheduler).toFlushAndYieldThrough([
'Parent',
'Child',
'Child passive create',
'Parent passive create',
]);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYield(['Parent passive destroy']);
});
});
it('does not show a warning when a component updates a childs state from within passive unmount function', () => {
function Parent() {
Scheduler.unstable_yieldValue('Parent');
const updaterRef = React.useRef(null);
React.useEffect(() => {
Scheduler.unstable_yieldValue('Parent passive create');
return () => {
updaterRef.current(true);
Scheduler.unstable_yieldValue('Parent passive destroy');
};
}, []);
return <Child updaterRef={updaterRef} />;
}
it('does not show a warning when a component updates a parents state from within passive unmount function', () => {
function Parent() {
const [state, setState] = React.useState(false);
Scheduler.unstable_yieldValue('Parent');
return <Child setState={setState} state={state} />;
}
function Child({updaterRef}) {
Scheduler.unstable_yieldValue('Child');
const [state, setState] = React.useState(false);
React.useEffect(() => {
Scheduler.unstable_yieldValue('Child passive create');
updaterRef.current = setState;
}, []);
return state;
}
function Child({setState, state}) {
Scheduler.unstable_yieldValue('Child');
React.useEffect(() => {
Scheduler.unstable_yieldValue('Child passive create');
return () => {
Scheduler.unstable_yieldValue('Child passive destroy');
setState(true);
};
}, []);
return state;
}
act(() => {
ReactNoop.renderToRootWithID(<Parent />, 'root');
expect(Scheduler).toFlushAndYieldThrough([
'Parent',
'Child',
'Child passive create',
'Parent passive create',
]);
act(() => {
ReactNoop.renderToRootWithID(<Parent />, 'root');
expect(Scheduler).toFlushAndYieldThrough([
'Parent',
'Child',
'Child passive create',
]);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYield(['Parent passive destroy']);
});
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYield(['Child passive destroy']);
});
it('does not show a warning when a component updates a parents state from within passive unmount function', () => {
function Parent() {
const [state, setState] = React.useState(false);
Scheduler.unstable_yieldValue('Parent');
return <Child setState={setState} state={state} />;
}
function Child({setState, state}) {
Scheduler.unstable_yieldValue('Child');
React.useEffect(() => {
Scheduler.unstable_yieldValue('Child passive create');
return () => {
Scheduler.unstable_yieldValue('Child passive destroy');
setState(true);
};
}, []);
return state;
}
act(() => {
ReactNoop.renderToRootWithID(<Parent />, 'root');
expect(Scheduler).toFlushAndYieldThrough([
'Parent',
'Child',
'Child passive create',
]);
// Unmount but don't process pending passive destroy function
ReactNoop.unmountRootWithID('root');
expect(Scheduler).toFlushAndYield(['Child passive destroy']);
});
});
}
});
it('updates have async priority', () => {
function Counter(props) {

View File

@ -26,11 +26,6 @@ describe('ReactScope', () => {
Scheduler = require('scheduler');
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
describe('ReactDOM', () => {
let ReactDOM;
let container;
@ -47,6 +42,7 @@ describe('ReactScope', () => {
container = null;
});
// @gate experimental
it('DO_NOT_USE_queryAllNodes() works as intended', () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_createScope();
@ -81,6 +77,7 @@ describe('ReactScope', () => {
expect(scopeRef.current).toBe(null);
});
// @gate experimental
it('DO_NOT_USE_queryAllNodes() provides the correct host instance', () => {
const testScopeQuery = (type, props) => type === 'div';
const TestScope = React.unstable_createScope();
@ -127,6 +124,7 @@ describe('ReactScope', () => {
expect(scopeRef.current).toBe(null);
});
// @gate experimental
it('DO_NOT_USE_queryFirstNode() works as intended', () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_createScope();
@ -161,6 +159,7 @@ describe('ReactScope', () => {
expect(scopeRef.current).toBe(null);
});
// @gate experimental
it('containsNode() works as intended', () => {
const TestScope = React.unstable_createScope();
const scopeRef = React.createRef();
@ -210,6 +209,7 @@ describe('ReactScope', () => {
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
});
// @gate experimental
it('scopes support server-side rendering and hydration', () => {
const TestScope = React.unstable_createScope();
const scopeRef = React.createRef();
@ -240,6 +240,7 @@ describe('ReactScope', () => {
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
});
// @gate experimental
it('event responders can be attached to scopes', () => {
let onKeyDown = jest.fn();
const TestScope = React.unstable_createScope();
@ -282,6 +283,7 @@ describe('ReactScope', () => {
expect(onKeyDown).toHaveBeenCalledTimes(1);
});
// @gate experimental
it('getChildContextValues() works as intended', () => {
const TestContext = React.createContext();
const TestScope = React.unstable_createScope();
@ -310,6 +312,7 @@ describe('ReactScope', () => {
expect(scopeRef.current).toBe(null);
});
// @gate experimental
it('correctly works with suspended boundaries that are hydrated', async () => {
let suspend = false;
let resolve;
@ -384,6 +387,7 @@ describe('ReactScope', () => {
ReactTestRenderer = require('react-test-renderer');
});
// @gate experimental
it('DO_NOT_USE_queryAllNodes() works as intended', () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_createScope();
@ -420,6 +424,7 @@ describe('ReactScope', () => {
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
});
// @gate experimental
it('DO_NOT_USE_queryFirstNode() works as intended', () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_createScope();
@ -456,6 +461,7 @@ describe('ReactScope', () => {
expect(node).toEqual(aRef.current);
});
// @gate experimental
it('containsNode() works as intended', () => {
const TestScope = React.unstable_createScope();
const scopeRef = React.createRef();

View File

@ -6,11 +6,6 @@ let Suspense;
let SuspenseList;
describe('ReactSuspenseList', () => {
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
@ -47,6 +42,7 @@ describe('ReactSuspenseList', () => {
return Component;
}
// @gate experimental
it('warns if an unsupported revealOrder option is used', () => {
function Foo() {
return (
@ -66,6 +62,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('warns if a upper case revealOrder option is used', () => {
function Foo() {
return (
@ -85,6 +82,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('warns if a misspelled revealOrder option is used', () => {
function Foo() {
return (
@ -105,6 +103,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('warns if a single element is passed to a "forwards" list', () => {
function Foo({children}) {
return <SuspenseList revealOrder="forwards">{children}</SuspenseList>;
@ -137,6 +136,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('warns if a single fragment is passed to a "backwards" list', () => {
function Foo() {
return (
@ -157,6 +157,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('warns if a nested array is passed to a "forwards" list', () => {
function Foo({items}) {
return (
@ -184,6 +185,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('shows content independently by default', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -250,6 +252,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('shows content independently in legacy mode regardless of option', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -322,6 +325,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays all "together"', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -391,6 +395,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays all "together" even when nested as siblings', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -476,6 +481,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays all "together" in nested SuspenseLists', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -537,6 +543,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays all "together" in nested SuspenseLists where the inner is default', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -596,6 +603,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays all "together" during an update', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -680,6 +688,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('avoided boundaries can be coordinate with SuspenseList', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -778,6 +787,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays each items in "forwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -843,6 +853,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays each items in "backwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -908,6 +919,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays added row at the top "together" and the bottom in "forwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1062,6 +1074,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('displays added row at the top "together" and the bottom in "backwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1246,6 +1259,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('switches to rendering fallbacks if the tail takes long CPU time', async () => {
function Foo() {
return (
@ -1308,6 +1322,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('only shows one loading state at a time for "collapsed" tail insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1377,6 +1392,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('warns if an unsupported tail option is used', () => {
function Foo() {
return (
@ -1397,6 +1413,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('warns if a tail option is used with "together"', () => {
function Foo() {
return (
@ -1417,6 +1434,7 @@ describe('ReactSuspenseList', () => {
]);
});
// @gate experimental
it('renders one "collapsed" fallback even if CPU time elapsed', async () => {
function Foo() {
return (
@ -1483,6 +1501,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('adding to the middle does not collapse insertions (forwards)', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1625,6 +1644,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('adding to the middle does not collapse insertions (backwards)', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1772,6 +1792,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('adding to the middle of committed tail does not collapse insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1929,6 +1950,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('only shows no initial loading state "hidden" tail insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@ -1992,6 +2014,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('eventually resolves a nested forwards suspense list', async () => {
const B = createAsyncText('B');
@ -2054,6 +2077,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('eventually resolves a nested forwards suspense list with a hidden tail', async () => {
const B = createAsyncText('B');
@ -2100,6 +2124,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('eventually resolves two nested forwards suspense lists with a hidden tail', async () => {
const B = createAsyncText('B');
@ -2167,6 +2192,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('can do unrelated adjacent updates', async () => {
let updateAdjacent;
function Adjacent() {
@ -2213,6 +2239,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('is able to re-suspend the last rows during an update with hidden', async () => {
const AsyncB = createAsyncText('B');
@ -2301,6 +2328,7 @@ describe('ReactSuspenseList', () => {
expect(previousInst).toBe(setAsyncB);
});
// @gate experimental
it('is able to re-suspend the last rows during an update with hidden', async () => {
const AsyncB = createAsyncText('B');
@ -2389,6 +2417,7 @@ describe('ReactSuspenseList', () => {
expect(previousInst).toBe(setAsyncB);
});
// @gate experimental
it('is able to interrupt a partially rendered tree and continue later', async () => {
const AsyncA = createAsyncText('A');
@ -2486,6 +2515,7 @@ describe('ReactSuspenseList', () => {
);
});
// @gate experimental
it('can resume class components when revealed together', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');

View File

@ -11,11 +11,6 @@ let resolveText;
let rejectText;
describe('ReactSuspenseWithNoopRenderer', () => {
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => {
jest.resetModules();
@ -922,6 +917,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('Async')]);
});
// @gate experimental
it('starts working on an update even if its priority falls between two suspended levels', async () => {
function App(props) {
return (
@ -2331,6 +2327,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
timeoutMs: 2000,
};
// @gate experimental
it('top level render', async () => {
function App({page}) {
return (
@ -2385,6 +2382,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('B')]);
});
// @gate experimental
it('hooks', async () => {
let transitionToPage;
function App() {
@ -2452,6 +2450,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('B')]);
});
// @gate experimental
it('classes', async () => {
let transitionToPage;
class App extends React.Component {
@ -2523,6 +2522,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
});
// @gate experimental
it('disables suspense config when nothing is passed to withSuspenseConfig', async () => {
function App({page}) {
return (
@ -2597,6 +2597,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
]);
});
// @gate experimental
it('withSuspenseConfig timeout applies when we use an updated avoided boundary', async () => {
function App({page}) {
return (
@ -2645,6 +2646,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
]);
});
// @gate experimental
it('withSuspenseConfig timeout applies when we use a newly created avoided boundary', async () => {
function App({page}) {
return (
@ -2692,6 +2694,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
]);
});
// @gate experimental
it('supports delaying a busy spinner from disappearing', async () => {
const SUSPENSE_CONFIG = {
timeoutMs: 10000,
@ -2854,6 +2857,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(root).toMatchRenderedOutput(<span prop="Foo" />);
});
// @gate experimental
it('should not render hidden content while suspended on higher pri', async () => {
function Offscreen() {
Scheduler.unstable_yieldValue('Offscreen');
@ -2908,6 +2912,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
});
// @gate experimental
it('should be able to unblock higher pri content before suspended hidden', async () => {
function Offscreen() {
Scheduler.unstable_yieldValue('Offscreen');
@ -3624,6 +3629,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
});
// @gate experimental
it('regression: ping at high priority causes update to be dropped', async () => {
const {useState, useTransition} = React;

View File

@ -62,13 +62,9 @@ describe('ReactDOMTracing', () => {
loadModules();
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
describe('interaction tracing', () => {
describe('hidden', () => {
// @gate experimental
it('traces interaction through hidden subtree', () => {
const Child = () => {
const [didMount, setDidMount] = React.useState(false);
@ -145,6 +141,7 @@ describe('ReactDOMTracing', () => {
);
});
// @gate experimental
it('traces interaction through hidden subtree when there is other pending traced work', () => {
const Child = () => {
Scheduler.unstable_yieldValue('Child');
@ -212,6 +209,7 @@ describe('ReactDOMTracing', () => {
).toHaveBeenLastNotifiedOfInteraction(interaction);
});
// @gate experimental
it('traces interaction through hidden subtree that schedules more idle/never work', () => {
const Child = () => {
const [didMount, setDidMount] = React.useState(false);
@ -294,6 +292,7 @@ describe('ReactDOMTracing', () => {
);
});
// @gate experimental
it('does not continue interactions across pre-existing idle work', () => {
const Child = () => {
Scheduler.unstable_yieldValue('Child');
@ -394,6 +393,7 @@ describe('ReactDOMTracing', () => {
});
});
// @gate experimental
it('should properly trace interactions when there is work of interleaved priorities', () => {
const Child = () => {
Scheduler.unstable_yieldValue('Child');
@ -515,6 +515,7 @@ describe('ReactDOMTracing', () => {
});
});
// @gate experimental
it('should properly trace interactions through a multi-pass SuspenseList render', () => {
const SuspenseList = React.SuspenseList;
const Suspense = React.Suspense;
@ -599,7 +600,8 @@ describe('ReactDOMTracing', () => {
});
describe('hydration', () => {
it('traces interaction across hydration', async done => {
// @gate experimental
it('traces interaction across hydration', () => {
const ref = React.createRef();
function Child() {
@ -644,11 +646,10 @@ describe('ReactDOMTracing', () => {
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
done();
});
it('traces interaction across suspended hydration', async done => {
// @gate experimental
it('traces interaction across suspended hydration', async () => {
let suspend = false;
let resolve;
const promise = new Promise(
@ -716,11 +717,10 @@ describe('ReactDOMTracing', () => {
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
done();
});
it('traces interaction across client-rendered hydration', async done => {
// @gate experimental
it('traces interaction across client-rendered hydration', () => {
let suspend = false;
const promise = new Promise(() => {});
const ref = React.createRef();
@ -788,8 +788,6 @@ describe('ReactDOMTracing', () => {
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(interaction);
done();
});
});
});

View File

@ -53,11 +53,7 @@ describe('ProfilerDOM', () => {
return props.text;
}
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
// @gate experimental
it('should correctly trace interactions for async roots', async () => {
let resolve;
let thenable = {

View File

@ -36,29 +36,28 @@ describe('ReactError', () => {
}
});
if (__DEV__) {
it("empty test so Jest doesn't complain", () => {});
} else {
it('should error with minified error code', () => {
expect(() => ReactDOM.render('Hi', null)).toThrowError(
'Minified React error #200; visit ' +
'https://reactjs.org/docs/error-decoder.html?invariant=200' +
' for the full message or use the non-minified dev environment' +
' for full errors and additional helpful warnings.',
);
});
it('should serialize arguments', () => {
function Oops() {
return;
}
Oops.displayName = '#wtf';
const container = document.createElement('div');
expect(() => ReactDOM.render(<Oops />, container)).toThrowError(
'Minified React error #152; visit ' +
'https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=%23wtf' +
' for the full message or use the non-minified dev environment' +
' for full errors and additional helpful warnings.',
);
});
}
// @gate build === "production"
it('should error with minified error code', () => {
expect(() => ReactDOM.render('Hi', null)).toThrowError(
'Minified React error #200; visit ' +
'https://reactjs.org/docs/error-decoder.html?invariant=200' +
' for the full message or use the non-minified dev environment' +
' for full errors and additional helpful warnings.',
);
});
// @gate build === "production"
it('should serialize arguments', () => {
function Oops() {
return;
}
Oops.displayName = '#wtf';
const container = document.createElement('div');
expect(() => ReactDOM.render(<Oops />, container)).toThrowError(
'Minified React error #152; visit ' +
'https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=%23wtf' +
' for the full message or use the non-minified dev environment' +
' for full errors and additional helpful warnings.',
);
});
});

View File

@ -11,7 +11,6 @@
let React;
let ReactDOM;
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
describe('Component stack trace displaying', () => {
beforeEach(() => {
@ -19,11 +18,7 @@ describe('Component stack trace displaying', () => {
ReactDOM = require('react-dom');
});
if (ReactFeatureFlags.enableComponentStackLocations) {
it("empty test so Jest doesn't complain", () => {});
return;
}
// @gate !enableComponentStackLocations || !__DEV__
it('should provide filenames in stack traces', () => {
class Component extends React.Component {
render() {

View File

@ -35,7 +35,11 @@
const environmentFlags = {
__DEV__,
build: __DEV__ ? 'development' : 'production',
// TODO: Should "experimental" also imply "modern"? Maybe we should
// always compare to the channel?
experimental: __EXPERIMENTAL__,
// Similarly, should stable imply "classic"?
stable: !__EXPERIMENTAL__,
};
@ -44,6 +48,18 @@ function getTestFlags() {
// not to but there are exceptions.
const featureFlags = require('shared/ReactFeatureFlags');
// TODO: This is a heuristic to detect the release channel by checking a flag
// that is known to only be enabled in www. What we should do instead is set
// the release channel explicitly in the each test config file.
const www = featureFlags.enableSuspenseCallback === true;
const releaseChannel = www
? __EXPERIMENTAL__
? 'modern'
: 'classic'
: __EXPERIMENTAL__
? 'experimental'
: 'stable';
// Return a proxy so we can throw if you attempt to access a flag that
// doesn't exist.
return new Proxy(
@ -52,6 +68,11 @@ function getTestFlags() {
old: featureFlags.enableNewReconciler === true,
new: featureFlags.enableNewReconciler === true,
channel: releaseChannel,
modern: releaseChannel === 'modern',
classic: releaseChannel === 'classic',
www,
...featureFlags,
...environmentFlags,
},