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

View File

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

View File

@ -87,11 +87,7 @@ describe('ReactDOMServerPartialHydration', () => {
SuspenseList = React.SuspenseList; SuspenseList = React.SuspenseList;
}); });
if (!__EXPERIMENTAL__) { // @gate experimental
it("empty test so Jest doesn't complain", () => {});
return;
}
it('hydrates a parent even if a child Suspense boundary is blocked', async () => { it('hydrates a parent even if a child Suspense boundary is blocked', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -150,6 +146,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span); expect(ref.current).toBe(span);
}); });
// @gate experimental
it('calls the hydration callbacks after hydration or deletion', async () => { it('calls the hydration callbacks after hydration or deletion', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -240,6 +237,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(deleted.length).toBe(1); expect(deleted.length).toBe(1);
}); });
// @gate experimental
it('calls the onDeleted hydration callback if the parent gets deleted', async () => { it('calls the onDeleted hydration callback if the parent gets deleted', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(() => {}); const promise = new Promise(() => {});
@ -297,6 +295,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(deleted.length).toBe(1); expect(deleted.length).toBe(1);
}); });
// @gate experimental || www
it('warns and replaces the boundary content in legacy mode', async () => { it('warns and replaces the boundary content in legacy mode', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -368,6 +367,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hello'); expect(container.textContent).toBe('Hello');
}); });
// @gate experimental
it('can insert siblings before the dehydrated boundary', () => { it('can insert siblings before the dehydrated boundary', () => {
let suspend = false; let suspend = false;
const promise = new Promise(() => {}); const promise = new Promise(() => {});
@ -426,6 +426,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.firstChild.firstChild.textContent).toBe('First'); expect(container.firstChild.firstChild.textContent).toBe('First');
}); });
// @gate experimental
it('can delete the dehydrated boundary before it is hydrated', () => { it('can delete the dehydrated boundary before it is hydrated', () => {
let suspend = false; let suspend = false;
const promise = new Promise(() => {}); const promise = new Promise(() => {});
@ -482,6 +483,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.firstChild.children[1].textContent).toBe('After'); expect(container.firstChild.children[1].textContent).toBe('After');
}); });
// @gate experimental
it('blocks updates to hydrate the content first if props have changed', async () => { it('blocks updates to hydrate the content first if props have changed', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -553,6 +555,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi'); expect(span.className).toBe('hi');
}); });
// @gate experimental
it('shows the fallback if props have changed before hydration completes and is still suspended', async () => { it('shows the fallback if props have changed before hydration completes and is still suspended', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -622,6 +625,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi'); expect(container.textContent).toBe('Hi');
}); });
// @gate experimental
it('shows the fallback of the outer if fallback is missing', async () => { 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 is the same exact test as above but with a nested Suspense without a fallback.
// This should be a noop. // This should be a noop.
@ -695,6 +699,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi'); expect(container.textContent).toBe('Hi');
}); });
// @gate experimental
it('clears nested suspense boundaries if they did not hydrate yet', async () => { it('clears nested suspense boundaries if they did not hydrate yet', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -767,6 +772,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi Hi'); expect(container.textContent).toBe('Hi Hi');
}); });
// @gate experimental
it('hydrates first if props changed but we are able to resolve within a timeout', async () => { it('hydrates first if props changed but we are able to resolve within a timeout', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -847,6 +853,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi'); expect(span.className).toBe('hi');
}); });
// @gate experimental
it('blocks the update to hydrate first if context has changed', async () => { it('blocks the update to hydrate first if context has changed', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -931,6 +938,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi'); expect(span.className).toBe('hi');
}); });
// @gate experimental
it('shows the fallback if context has changed before hydration completes and is still suspended', async () => { it('shows the fallback if context has changed before hydration completes and is still suspended', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -1014,6 +1022,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('Hi'); expect(container.textContent).toBe('Hi');
}); });
// @gate experimental
it('replaces the fallback with client content if it is not rendered by the server', async () => { it('replaces the fallback with client content if it is not rendered by the server', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(resolvePromise => {}); const promise = new Promise(resolvePromise => {});
@ -1062,6 +1071,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span); expect(ref.current).toBe(span);
}); });
// @gate experimental
it('replaces the fallback within the suspended time if there is a nested suspense', async () => { it('replaces the fallback within the suspended time if there is a nested suspense', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(resolvePromise => {}); const promise = new Promise(resolvePromise => {});
@ -1121,6 +1131,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span); 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 () => { it('replaces the fallback within the suspended time if there is a nested suspense in a nested suspense', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(resolvePromise => {}); const promise = new Promise(resolvePromise => {});
@ -1182,6 +1193,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span); expect(ref.current).toBe(span);
}); });
// @gate experimental
it('waits for pending content to come in from the server and then hydrates it', async () => { it('waits for pending content to come in from the server and then hydrates it', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(resolvePromise => {}); const promise = new Promise(resolvePromise => {});
@ -1269,6 +1281,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span); expect(ref.current).toBe(span);
}); });
// @gate experimental
it('handles an error on the client if the server ends up erroring', async () => { it('handles an error on the client if the server ends up erroring', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(resolvePromise => {}); const promise = new Promise(resolvePromise => {});
@ -1363,6 +1376,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(div); expect(ref.current).toBe(div);
}); });
// @gate experimental
it('shows inserted items in a SuspenseList before content is hydrated', async () => { it('shows inserted items in a SuspenseList before content is hydrated', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -1448,6 +1462,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(spanB); expect(ref.current).toBe(spanB);
}); });
// @gate experimental
it('shows is able to hydrate boundaries even if others in a list are pending', async () => { it('shows is able to hydrate boundaries even if others in a list are pending', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -1522,6 +1537,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('ALoading B'); expect(container.textContent).toBe('ALoading B');
}); });
// @gate experimental
it('shows inserted items before pending in a SuspenseList as fallbacks', async () => { it('shows inserted items before pending in a SuspenseList as fallbacks', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -1613,6 +1629,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('ABC'); expect(container.textContent).toBe('ABC');
}); });
// @gate experimental
it('can client render nested boundaries', async () => { it('can client render nested boundaries', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(() => {}); const promise = new Promise(() => {});
@ -1667,6 +1684,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.lastChild.data).toBe('unrelated comment'); expect(container.lastChild.data).toBe('unrelated comment');
}); });
// @gate experimental
it('can hydrate TWO suspense boundaries', async () => { it('can hydrate TWO suspense boundaries', async () => {
const ref1 = React.createRef(); const ref1 = React.createRef();
const ref2 = React.createRef(); const ref2 = React.createRef();
@ -1706,6 +1724,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref2.current).toBe(span2); expect(ref2.current).toBe(span2);
}); });
// @gate experimental
it('regenerates if it cannot hydrate before changes to props/context expire', async () => { it('regenerates if it cannot hydrate before changes to props/context expire', async () => {
let suspend = false; let suspend = false;
const promise = new Promise(resolvePromise => {}); const promise = new Promise(resolvePromise => {});
@ -1787,6 +1806,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(newSpan.className).toBe('hi'); expect(newSpan.className).toBe('hi');
}); });
// @gate experimental
it('does not invoke an event on a hydrated node until it commits', async () => { it('does not invoke an event on a hydrated node until it commits', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -1868,6 +1888,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('does not invoke an event on a hydrated EventResponder until it commits', async () => { it('does not invoke an event on a hydrated EventResponder until it commits', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -1949,6 +1970,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('invokes discrete events on nested suspense boundaries in a root (legacy system)', async () => { it('invokes discrete events on nested suspense boundaries in a root (legacy system)', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -2028,6 +2050,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('invokes discrete events on nested suspense boundaries in a root (responder system)', async () => { it('invokes discrete events on nested suspense boundaries in a root (responder system)', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -2110,6 +2133,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('does not invoke the parent of dehydrated boundary event', async () => { it('does not invoke the parent of dehydrated boundary event', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -2184,6 +2208,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('does not invoke an event on a parent tree when a subtree is dehydrated', async () => { it('does not invoke an event on a parent tree when a subtree is dehydrated', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -2261,6 +2286,7 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(parentContainer); document.body.removeChild(parentContainer);
}); });
// @gate experimental
it('blocks only on the last continuous event (legacy system)', async () => { it('blocks only on the last continuous event (legacy system)', async () => {
let suspend1 = false; let suspend1 = false;
let resolve1; let resolve1;
@ -2365,20 +2391,16 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
if (__EXPERIMENTAL__) { // @gate experimental
it('blocks only on the last continuous event (Responder system)', async () => { it('blocks only on the last continuous event (Responder system)', async () => {
useHover = require('react-interactions/events/hover').useHover; useHover = require('react-interactions/events/hover').useHover;
let suspend1 = false; let suspend1 = false;
let resolve1; let resolve1;
const promise1 = new Promise( const promise1 = new Promise(resolvePromise => (resolve1 = resolvePromise));
resolvePromise => (resolve1 = resolvePromise),
);
let suspend2 = false; let suspend2 = false;
let resolve2; let resolve2;
const promise2 = new Promise( const promise2 = new Promise(resolvePromise => (resolve2 = resolvePromise));
resolvePromise => (resolve2 = resolvePromise),
);
function First({text}) { function First({text}) {
if (suspend1) { if (suspend1) {
@ -2486,8 +2508,8 @@ describe('ReactDOMServerPartialHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
}
// @gate experimental
it('finishes normal pri work before continuing to hydrate a retry', async () => { it('finishes normal pri work before continuing to hydrate a retry', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;

View File

@ -106,11 +106,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
Suspense = React.Suspense; Suspense = React.Suspense;
}); });
if (!__EXPERIMENTAL__) { // @gate experimental
it("empty test so Jest doesn't complain", () => {});
return;
}
it('hydrates the target boundary synchronously during a click', async () => { it('hydrates the target boundary synchronously during a click', async () => {
function Child({text}) { function Child({text}) {
Scheduler.unstable_yieldValue(text); Scheduler.unstable_yieldValue(text);
@ -173,6 +169,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('hydrates at higher pri if sync did not work first time', async () => { it('hydrates at higher pri if sync did not work first time', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -257,6 +254,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('hydrates at higher pri for secondary discrete events', async () => { it('hydrates at higher pri for secondary discrete events', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -349,7 +347,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
if (__EXPERIMENTAL__) { // @gate experimental
it('hydrates the target boundary synchronously during a click (flare)', async () => { it('hydrates the target boundary synchronously during a click (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress; const usePress = require('react-interactions/events/press').usePress;
@ -415,6 +413,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('hydrates at higher pri if sync did not work first time (flare)', async () => { it('hydrates at higher pri if sync did not work first time (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress; const usePress = require('react-interactions/events/press').usePress;
let suspend = false; let suspend = false;
@ -498,6 +497,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('hydrates at higher pri for secondary discrete events (flare)', async () => { it('hydrates at higher pri for secondary discrete events (flare)', async () => {
const usePress = require('react-interactions/events/press').usePress; const usePress = require('react-interactions/events/press').usePress;
let suspend = false; let suspend = false;
@ -588,8 +588,8 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
}
// @gate experimental
it('hydrates the hovered targets as higher priority for continuous events', async () => { it('hydrates the hovered targets as higher priority for continuous events', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -690,6 +690,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('hydrates the last target path first for continuous events', async () => { it('hydrates the last target path first for continuous events', async () => {
let suspend = false; let suspend = false;
let resolve; let resolve;
@ -775,6 +776,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container); document.body.removeChild(container);
}); });
// @gate experimental
it('hydrates the last explicitly hydrated target at higher priority', async () => { it('hydrates the last explicitly hydrated target at higher priority', async () => {
function Child({text}) { function Child({text}) {
Scheduler.unstable_yieldValue(text); Scheduler.unstable_yieldValue(text);
@ -823,6 +825,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
expect(Scheduler).toFlushAndYield(['App', 'C', 'B', 'A']); expect(Scheduler).toFlushAndYield(['App', 'C', 'B', 'A']);
}); });
// @gate experimental
it('hydrates before an update even if hydration moves away from it', async () => { it('hydrates before an update even if hydration moves away from it', async () => {
function Child({text}) { function Child({text}) {
Scheduler.unstable_yieldValue(text); Scheduler.unstable_yieldValue(text);

View File

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

View File

@ -18,14 +18,6 @@ const renderSubtreeIntoContainer = require('react-dom')
const ReactFeatureFlags = require('shared/ReactFeatureFlags'); 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.
});
});
} else {
describe('renderSubtreeIntoContainer', () => { describe('renderSubtreeIntoContainer', () => {
it('should pass context when rendering subtree elsewhere', () => { it('should pass context when rendering subtree elsewhere', () => {
const portal = document.createElement('div'); const portal = document.createElement('div');
@ -338,4 +330,3 @@ if (__EXPERIMENTAL__) {
); );
}); });
}); });
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,8 +31,13 @@ function initializeModules(hasPointerEvents) {
ReactFeatureFlags.enableDeprecatedFlareAPI = true; ReactFeatureFlags.enableDeprecatedFlareAPI = true;
React = require('react'); React = require('react');
ReactDOM = require('react-dom'); ReactDOM = require('react-dom');
// TODO: This import throws outside of experimental mode. Figure out better
// strategy for gated imports.
if (__EXPERIMENTAL__) {
useTap = require('react-interactions/events/tap').useTap; useTap = require('react-interactions/events/tap').useTap;
} }
}
const coordinatesInside = {x: 51, y: 51}; const coordinatesInside = {x: 51, y: 51};
const coordinatesOutside = {x: 49, y: 49}; const coordinatesOutside = {x: 49, y: 49};
@ -73,11 +78,6 @@ function tapAndReleaseOutside({
describeWithPointerEvent('Tap responder', hasPointerEvents => { describeWithPointerEvent('Tap responder', hasPointerEvents => {
let container; let container;
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
beforeEach(() => { beforeEach(() => {
initializeModules(hasPointerEvents); initializeModules(hasPointerEvents);
container = document.createElement('div'); container = document.createElement('div');
@ -91,6 +91,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
resetActivePointers(); resetActivePointers();
}); });
// @gate experimental
test('supports repeated use', () => { test('supports repeated use', () => {
const ref = React.createRef(); const ref = React.createRef();
const Component = () => { const Component = () => {
@ -114,7 +115,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
describe('disabled', () => { describe('disabled', () => {
let onTapStart, onTapChange, onTapUpdate, onTapCancel, onTapEnd, ref; let onTapStart, onTapChange, onTapUpdate, onTapCancel, onTapEnd, ref;
beforeEach(() => { const componentInit = () => {
onTapStart = jest.fn(); onTapStart = jest.fn();
onTapChange = jest.fn(); onTapChange = jest.fn();
onTapUpdate = jest.fn(); onTapUpdate = jest.fn();
@ -133,9 +134,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
return <div ref={ref} DEPRECATED_flareListeners={listener} />; return <div ref={ref} DEPRECATED_flareListeners={listener} />;
}; };
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
}); };
// @gate experimental
test('does not call callbacks', () => { test('does not call callbacks', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.pointerdown(); target.pointerdown();
target.pointerup(); target.pointerup();
@ -159,7 +162,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
document.elementFromPoint = () => ref.current; document.elementFromPoint = () => ref.current;
} }
beforeEach(() => { const componentInit = () => {
onTapCancel = jest.fn(); onTapCancel = jest.fn();
onTapUpdate = jest.fn(); onTapUpdate = jest.fn();
ref = React.createRef(); ref = React.createRef();
@ -168,9 +171,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
onTapCancel, onTapCancel,
onTapUpdate, onTapUpdate,
}); });
}); };
// @gate experimental
test('ignores values less than 10', () => { test('ignores values less than 10', () => {
componentInit();
render({ render({
maximumDistance: 5, maximumDistance: 5,
onTapCancel, onTapCancel,
@ -184,7 +189,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapCancel).toHaveBeenCalledTimes(0); expect(onTapCancel).toHaveBeenCalledTimes(0);
}); });
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('below threshold', pointerType => { testWithPointerType('below threshold', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.pointerdown({pointerType, x: 0, y: 0}); target.pointerdown({pointerType, x: 0, y: 0});
target.pointermove({pointerType, x: 10, y: 10}); target.pointermove({pointerType, x: 10, y: 10});
@ -193,18 +202,20 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
testWithPointerType('above threshold', pointerType => { testWithPointerType('above threshold', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.pointerdown({pointerType, x: 0, y: 0}); target.pointerdown({pointerType, x: 0, y: 0});
target.pointermove({pointerType, x: 15, y: 14}); target.pointermove({pointerType, x: 15, y: 14});
expect(onTapUpdate).toHaveBeenCalledTimes(0); expect(onTapUpdate).toHaveBeenCalledTimes(0);
expect(onTapCancel).toHaveBeenCalledTimes(1); expect(onTapCancel).toHaveBeenCalledTimes(1);
}); });
}
}); });
describe('onAuxiliaryTap', () => { describe('onAuxiliaryTap', () => {
let onAuxiliaryTap, ref; let onAuxiliaryTap, ref;
beforeEach(() => { const componentInit = () => {
onAuxiliaryTap = jest.fn(); onAuxiliaryTap = jest.fn();
ref = React.createRef(); ref = React.createRef();
const Component = () => { const Component = () => {
@ -213,9 +224,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}; };
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current; document.elementFromPoint = () => ref.current;
}); };
// @gate experimental
test('auxiliary-button pointer up', () => { test('auxiliary-button pointer up', () => {
componentInit();
const pointerType = 'mouse'; const pointerType = 'mouse';
const button = buttonType.auxiliary; const button = buttonType.auxiliary;
const buttons = buttonsType.auxiliary; const buttons = buttonsType.auxiliary;
@ -225,7 +238,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onAuxiliaryTap).toHaveBeenCalledTimes(1); expect(onAuxiliaryTap).toHaveBeenCalledTimes(1);
}); });
// @gate experimental
test('modifier-button pointer up', () => { test('modifier-button pointer up', () => {
componentInit();
const pointerType = 'mouse'; const pointerType = 'mouse';
const button = buttonType.primary; const button = buttonType.primary;
const buttons = buttonsType.primary; const buttons = buttonsType.primary;
@ -239,7 +254,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
describe('onTapStart', () => { describe('onTapStart', () => {
let onTapStart, ref; let onTapStart, ref;
beforeEach(() => { const componentInit = () => {
onTapStart = jest.fn(); onTapStart = jest.fn();
ref = React.createRef(); ref = React.createRef();
const Component = () => { const Component = () => {
@ -248,9 +263,13 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}; };
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current; document.elementFromPoint = () => ref.current;
}); };
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('pointer down', pointerType => { testWithPointerType('pointer down', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const nativeEvent = { const nativeEvent = {
button: buttonType.primary, button: buttonType.primary,
@ -294,8 +313,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}), }),
); );
}); });
}
// @gate experimental
test('second pointer on target', () => { test('second pointer on target', () => {
componentInit();
const pointerType = 'touch'; const pointerType = 'touch';
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const button = buttonType.primary; const button = buttonType.primary;
@ -306,7 +328,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapStart).toHaveBeenCalledTimes(1); expect(onTapStart).toHaveBeenCalledTimes(1);
}); });
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('ignored buttons and modifiers', pointerType => { testWithPointerType('ignored buttons and modifiers', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
if (pointerType !== 'touch') { if (pointerType !== 'touch') {
// right-click // right-click
@ -373,12 +399,13 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapStart).toHaveBeenCalledTimes(0); expect(onTapStart).toHaveBeenCalledTimes(0);
}); });
}
}); });
describe('onTapEnd', () => { describe('onTapEnd', () => {
let onTapEnd, ref; let onTapEnd, ref;
beforeEach(() => { const componentInit = () => {
onTapEnd = jest.fn(); onTapEnd = jest.fn();
ref = React.createRef(); ref = React.createRef();
const Component = () => { const Component = () => {
@ -387,9 +414,13 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}; };
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current; document.elementFromPoint = () => ref.current;
}); };
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('pointer up', pointerType => { testWithPointerType('pointer up', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const button = buttonType.primary; const button = buttonType.primary;
const buttons = buttonsType.primary; const buttons = buttonsType.primary;
@ -432,6 +463,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
testWithPointerType('zero-dimension hit rect', pointerType => { testWithPointerType('zero-dimension hit rect', pointerType => {
componentInit();
const targetRef = React.createRef(); const targetRef = React.createRef();
const innerRef = React.createRef(); const innerRef = React.createRef();
@ -455,6 +487,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
testWithPointerType('pointer up outside target', pointerType => { testWithPointerType('pointer up outside target', pointerType => {
componentInit();
const downTarget = createEventTarget(ref.current); const downTarget = createEventTarget(ref.current);
const upTarget = createEventTarget(container); const upTarget = createEventTarget(container);
tapAndReleaseOutside({ tapAndReleaseOutside({
@ -465,9 +498,12 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
expect(onTapEnd).not.toBeCalled(); expect(onTapEnd).not.toBeCalled();
}); });
}
if (hasPointerEvents) { if (hasPointerEvents) {
// @gate experimental
test('second pointer up off target', () => { test('second pointer up off target', () => {
componentInit();
const pointerType = 'touch'; const pointerType = 'touch';
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const offTarget = createEventTarget(container); const offTarget = createEventTarget(container);
@ -490,7 +526,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
} }
// @gate experimental
test('ignored buttons and modifiers', () => { test('ignored buttons and modifiers', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
// right-click // right-click
target.pointerdown({ target.pointerdown({
@ -553,7 +591,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
const rect = {x: 0, y: 0, width: 100, height: 100}; const rect = {x: 0, y: 0, width: 100, height: 100};
const coordinates = {x: 10, y: 10}; const coordinates = {x: 10, y: 10};
beforeEach(() => { const componentInit = () => {
onTapUpdate = jest.fn(); onTapUpdate = jest.fn();
ref = React.createRef(); ref = React.createRef();
const Component = () => { const Component = () => {
@ -562,9 +600,13 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}; };
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current; document.elementFromPoint = () => ref.current;
}); };
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('requires activation', pointerType => { testWithPointerType('requires activation', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.setBoundingClientRect(rect); target.setBoundingClientRect(rect);
if (pointerType !== 'touch') { if (pointerType !== 'touch') {
@ -575,6 +617,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
testWithPointerType('pointer move', pointerType => { testWithPointerType('pointer move', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.setBoundingClientRect(rect); target.setBoundingClientRect(rect);
target.pointerdown({pointerType}); target.pointerdown({pointerType});
@ -615,6 +658,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
testWithPointerType('pointer moves outside target', pointerType => { testWithPointerType('pointer moves outside target', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container); const containerTarget = createEventTarget(container);
target.setBoundingClientRect(rect); target.setBoundingClientRect(rect);
@ -634,9 +678,12 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
// No extra 'onTapUpdate' calls when the pointer is outside the target // No extra 'onTapUpdate' calls when the pointer is outside the target
expect(onTapUpdate).toHaveBeenCalledTimes(1); expect(onTapUpdate).toHaveBeenCalledTimes(1);
}); });
}
if (hasPointerEvents) { if (hasPointerEvents) {
// @gate experimental
test('second pointer off target', () => { test('second pointer off target', () => {
componentInit();
const pointerType = 'touch'; const pointerType = 'touch';
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const offTarget = createEventTarget(container); const offTarget = createEventTarget(container);
@ -659,7 +706,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
eventsLog.push(msg); eventsLog.push(msg);
}; };
beforeEach(() => { const componentInit = () => {
eventsLog = []; eventsLog = [];
onTapChange = jest.fn(); onTapChange = jest.fn();
ref = React.createRef(); ref = React.createRef();
@ -677,9 +724,13 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}; };
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
document.elementFromPoint = () => ref.current; document.elementFromPoint = () => ref.current;
}); };
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('pointer down/up', pointerType => { testWithPointerType('pointer down/up', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.pointerdown({pointerType}); target.pointerdown({pointerType});
expect(onTapChange).toHaveBeenCalledTimes(1); expect(onTapChange).toHaveBeenCalledTimes(1);
@ -691,6 +742,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
testWithPointerType('pointer cancel', pointerType => { testWithPointerType('pointer cancel', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.pointerdown({pointerType}); target.pointerdown({pointerType});
expect(onTapChange).toHaveBeenCalledTimes(1); expect(onTapChange).toHaveBeenCalledTimes(1);
@ -702,6 +754,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
testWithPointerType('pointer move outside target', pointerType => { testWithPointerType('pointer move outside target', pointerType => {
componentInit();
const downTarget = createEventTarget(ref.current); const downTarget = createEventTarget(ref.current);
const upTarget = createEventTarget(container); const upTarget = createEventTarget(container);
tapAndMoveOutside({ tapAndMoveOutside({
@ -712,12 +765,13 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
expect(onTapChange).toHaveBeenCalledTimes(2); expect(onTapChange).toHaveBeenCalledTimes(2);
}); });
}
}); });
describe('onTapCancel', () => { describe('onTapCancel', () => {
let onTapCancel, onTapUpdate, parentRef, ref, siblingRef; let onTapCancel, onTapUpdate, parentRef, ref, siblingRef;
beforeEach(() => { const componentInit = () => {
onTapCancel = jest.fn(); onTapCancel = jest.fn();
onTapUpdate = jest.fn(); onTapUpdate = jest.fn();
parentRef = React.createRef(); parentRef = React.createRef();
@ -733,9 +787,13 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
); );
}; };
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
}); };
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('pointer cancel', pointerType => { testWithPointerType('pointer cancel', pointerType => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.pointerdown({pointerType}); target.pointerdown({pointerType});
target.pointercancel({pointerType}); target.pointercancel({pointerType});
@ -768,8 +826,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
target.pointermove({pointerType, x: 5, y: 5}); target.pointermove({pointerType, x: 5, y: 5});
expect(onTapUpdate).not.toBeCalled(); expect(onTapUpdate).not.toBeCalled();
}); });
}
// @gate experimental
test('second pointer on target', () => { test('second pointer on target', () => {
componentInit();
const pointerType = 'touch'; const pointerType = 'touch';
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const button = buttonType.primary; const button = buttonType.primary;
@ -780,7 +841,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
if (hasPointerEvents) { if (hasPointerEvents) {
// @gate experimental
test('second pointer off target', () => { test('second pointer off target', () => {
componentInit();
const pointerType = 'touch'; const pointerType = 'touch';
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const offTarget = createEventTarget(container); const offTarget = createEventTarget(container);
@ -792,7 +855,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
} }
// TODO: Get rid of this condition somehow. Perhaps with a dynamic verion of
// the @gate pragma.
if (__EXPERIMENTAL__) {
testWithPointerType('pointer move outside target', pointerType => { testWithPointerType('pointer move outside target', pointerType => {
componentInit();
const downTarget = createEventTarget(ref.current); const downTarget = createEventTarget(ref.current);
const upTarget = createEventTarget(container); const upTarget = createEventTarget(container);
tapAndMoveOutside({ tapAndMoveOutside({
@ -803,8 +870,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
expect(onTapCancel).toBeCalled(); expect(onTapCancel).toBeCalled();
}); });
}
// @gate experimental
test('ignored modifiers', () => { test('ignored modifiers', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const button = buttonType.primary; const button = buttonType.primary;
const buttons = buttonsType.primary; const buttons = buttonsType.primary;
@ -824,13 +894,17 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapCancel).toHaveBeenCalledTimes(4); expect(onTapCancel).toHaveBeenCalledTimes(4);
}); });
// @gate experimental
test('long press context menu', () => { test('long press context menu', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.contextmenu({}, {pointerType: 'touch'}); target.contextmenu({}, {pointerType: 'touch'});
expect(onTapCancel).toHaveBeenCalledTimes(1); expect(onTapCancel).toHaveBeenCalledTimes(1);
}); });
// @gate experimental
test('parent scroll (non-mouse)', () => { test('parent scroll (non-mouse)', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const parentTarget = createEventTarget(parentRef.current); const parentTarget = createEventTarget(parentRef.current);
target.pointerdown({pointerType: 'touch'}); target.pointerdown({pointerType: 'touch'});
@ -838,7 +912,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapCancel).toHaveBeenCalledTimes(1); expect(onTapCancel).toHaveBeenCalledTimes(1);
}); });
// @gate experimental
test('sibling scroll', () => { test('sibling scroll', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const siblingTarget = createEventTarget(siblingRef.current); const siblingTarget = createEventTarget(siblingRef.current);
target.pointerdown(); target.pointerdown();
@ -846,7 +922,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapCancel).not.toBeCalled(); expect(onTapCancel).not.toBeCalled();
}); });
// @gate experimental
test('document scroll (non-mouse)', () => { test('document scroll (non-mouse)', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const documentTarget = createEventTarget(document); const documentTarget = createEventTarget(document);
target.pointerdown({pointerType: 'touch'}); target.pointerdown({pointerType: 'touch'});
@ -855,7 +933,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
}); });
// Scroll on an element not managed by React // Scroll on an element not managed by React
// @gate experimental
test('root container scroll (non-mouse)', () => { test('root container scroll (non-mouse)', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
const containerTarget = createEventTarget(container); const containerTarget = createEventTarget(container);
target.pointerdown({pointerType: 'touch'}); target.pointerdown({pointerType: 'touch'});
@ -867,7 +947,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
describe('preventDefault', () => { describe('preventDefault', () => {
let onTapEnd, ref, innerRef, preventDefault, remount; let onTapEnd, ref, innerRef, preventDefault, remount;
beforeEach(() => { const componentInit = () => {
remount = function(shouldPreventDefault) { remount = function(shouldPreventDefault) {
onTapEnd = jest.fn(); onTapEnd = jest.fn();
preventDefault = jest.fn(); preventDefault = jest.fn();
@ -887,9 +967,11 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
ReactDOM.render(<Component />, container); ReactDOM.render(<Component />, container);
}; };
remount(); remount();
}); };
// @gate experimental
test('prevents native behavior by default', () => { test('prevents native behavior by default', () => {
componentInit();
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);
target.pointerdown(); target.pointerdown();
target.pointerup({preventDefault}); target.pointerup({preventDefault});
@ -899,7 +981,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
); );
}); });
// @gate experimental
test('prevents native behaviour by default (inner target)', () => { test('prevents native behaviour by default (inner target)', () => {
componentInit();
const innerTarget = createEventTarget(innerRef.current); const innerTarget = createEventTarget(innerRef.current);
innerTarget.pointerdown(); innerTarget.pointerdown();
innerTarget.pointerup({preventDefault}); innerTarget.pointerup({preventDefault});
@ -909,7 +993,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
); );
}); });
// @gate experimental
test('allows native behaviour if false', () => { test('allows native behaviour if false', () => {
componentInit();
remount(false); remount(false);
const target = createEventTarget(ref.current); const target = createEventTarget(ref.current);

View File

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

View File

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

View File

@ -1121,12 +1121,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}, },
); );
if ( // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
require('shared/ReactFeatureFlags')
.deferPassiveEffectCleanupDuringUnmount &&
require('shared/ReactFeatureFlags')
.runAllPassiveEffectDestroysBeforeCreates
) {
it('defers passive effect destroy functions during unmount', () => { it('defers passive effect destroy functions during unmount', () => {
function Child({bar, foo}) { function Child({bar, foo}) {
React.useEffect(() => { React.useEffect(() => {
@ -1211,6 +1206,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}); });
}); });
// @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('does not warn about state updates for unmounted components with pending passive unmounts', () => { it('does not warn about state updates for unmounted components with pending passive unmounts', () => {
let completePendingRequest = null; let completePendingRequest = null;
function Component() { function Component() {
@ -1258,7 +1254,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}); });
}); });
it('still warns about state updates for unmounted components with no pending passive unmounts', () => { it('warns about state updates for unmounted components with no pending passive unmounts', () => {
let completePendingRequest = null; let completePendingRequest = null;
function Component() { function Component() {
Scheduler.unstable_yieldValue('Component'); Scheduler.unstable_yieldValue('Component');
@ -1295,6 +1291,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}); });
}); });
// @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
it('still warns if there are pending passive unmount effects but not for the current fiber', () => { it('still warns if there are pending passive unmount effects but not for the current fiber', () => {
let completePendingRequest = null; let completePendingRequest = null;
function ComponentWithXHR() { function ComponentWithXHR() {
@ -1355,7 +1352,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}); });
}); });
it('still warns if there are updates after pending passive unmount effects have been flushed', () => { it('warns if there are updates after pending passive unmount effects have been flushed', () => {
let updaterFunction; let updaterFunction;
function Component() { function Component() {
@ -1498,7 +1495,6 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(Scheduler).toFlushAndYield(['Child passive destroy']); expect(Scheduler).toFlushAndYield(['Child passive destroy']);
}); });
}); });
}
it('updates have async priority', () => { it('updates have async priority', () => {
function Counter(props) { function Counter(props) {

View File

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

View File

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

View File

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

View File

@ -140,11 +140,9 @@ describe('useMutableSource', () => {
return <div>{`${label}:${snapshot}`}</div>; return <div>{`${label}:${snapshot}`}</div>;
} }
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
} else {
beforeEach(loadModules); beforeEach(loadModules);
// @gate experimental
it('should subscribe to a source and schedule updates when it changes', () => { it('should subscribe to a source and schedule updates when it changes', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -212,6 +210,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should restart work if a new source is mutated during render', () => { it('should restart work if a new source is mutated during render', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -246,6 +245,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should schedule an update if a new source is mutated between render and commit (subscription)', () => { it('should schedule an update if a new source is mutated between render and commit (subscription)', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -285,6 +285,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should unsubscribe and resubscribe if a new source is used', () => { it('should unsubscribe and resubscribe if a new source is used', () => {
const sourceA = createSource('a-one'); const sourceA = createSource('a-one');
const mutableSourceA = createMutableSource(sourceA); const mutableSourceA = createMutableSource(sourceA);
@ -335,6 +336,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should unsubscribe and resubscribe if a new subscribe function is provided', () => { it('should unsubscribe and resubscribe if a new subscribe function is provided', () => {
const source = createSource('a-one'); const source = createSource('a-one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -399,6 +401,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should re-use previously read snapshot value when reading is unsafe', () => { it('should re-use previously read snapshot value when reading is unsafe', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -455,6 +458,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should read from source on newly mounted subtree if no pending updates are scheduled for source', () => { it('should read from source on newly mounted subtree if no pending updates are scheduled for source', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -494,6 +498,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should throw and restart render if source and snapshot are unavailable during an update', () => { it('should throw and restart render if source and snapshot are unavailable during an update', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -521,13 +526,10 @@ describe('useMutableSource', () => {
// Changing values should schedule an update with React. // Changing values should schedule an update with React.
// Start working on this update but don't finish it. // Start working on this update but don't finish it.
Scheduler.unstable_runWithPriority( Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => {
Scheduler.unstable_LowPriority,
() => {
source.value = 'two'; source.value = 'two';
expect(Scheduler).toFlushAndYieldThrough(['a:two']); expect(Scheduler).toFlushAndYieldThrough(['a:two']);
}, });
);
const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s); const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
@ -563,6 +565,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should throw and restart render if source and snapshot are unavailable during a sync update', () => { it('should throw and restart render if source and snapshot are unavailable during a sync update', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -590,13 +593,10 @@ describe('useMutableSource', () => {
// Changing values should schedule an update with React. // Changing values should schedule an update with React.
// Start working on this update but don't finish it. // Start working on this update but don't finish it.
Scheduler.unstable_runWithPriority( Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => {
Scheduler.unstable_LowPriority,
() => {
source.value = 'two'; source.value = 'two';
expect(Scheduler).toFlushAndYieldThrough(['a:two']); expect(Scheduler).toFlushAndYieldThrough(['a:two']);
}, });
);
const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s); const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
@ -629,6 +629,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should only update components whose subscriptions fire', () => { it('should only update components whose subscriptions fire', () => {
const source = createComplexSource('a:one', 'b:one'); const source = createComplexSource('a:one', 'b:one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -657,11 +658,7 @@ describe('useMutableSource', () => {
</>, </>,
() => Scheduler.unstable_yieldValue('Sync effect'), () => Scheduler.unstable_yieldValue('Sync effect'),
); );
expect(Scheduler).toFlushAndYield([ expect(Scheduler).toFlushAndYield(['a:a:one', 'b:b:one', 'Sync effect']);
'a:a:one',
'b:b:one',
'Sync effect',
]);
// Changes to part of the store (e.g. A) should not render other parts. // Changes to part of the store (e.g. A) should not render other parts.
source.valueA = 'a:two'; source.valueA = 'a:two';
@ -671,6 +668,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should detect tearing in part of the store not yet subscribed to', () => { it('should detect tearing in part of the store not yet subscribed to', () => {
const source = createComplexSource('a:one', 'b:one'); const source = createComplexSource('a:one', 'b:one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -735,6 +733,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('does not schedule an update for subscriptions that fire with an unchanged snapshot', () => { it('does not schedule an update for subscriptions that fire with an unchanged snapshot', () => {
const MockComponent = jest.fn(Component); const MockComponent = jest.fn(Component);
@ -761,6 +760,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should throw and restart if getSnapshot changes between scheduled update and re-render', () => { it('should throw and restart if getSnapshot changes between scheduled update and re-render', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -790,12 +790,9 @@ describe('useMutableSource', () => {
ReactNoop.flushPassiveEffects(); ReactNoop.flushPassiveEffects();
// Change the source (and schedule an update). // Change the source (and schedule an update).
Scheduler.unstable_runWithPriority( Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => {
Scheduler.unstable_LowPriority,
() => {
source.value = 'two'; source.value = 'two';
}, });
);
// Schedule a higher priority update that changes getSnapshot. // Schedule a higher priority update that changes getSnapshot.
Scheduler.unstable_runWithPriority( Scheduler.unstable_runWithPriority(
@ -809,6 +806,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should recover from a mutation during yield when other work is scheduled', () => { it('should recover from a mutation during yield when other work is scheduled', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -842,6 +840,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should not throw if the new getSnapshot returns the same snapshot value', () => { it('should not throw if the new getSnapshot returns the same snapshot value', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -896,6 +895,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should not throw if getSnapshot changes but the source can be safely read from anyway', () => { it('should not throw if getSnapshot changes but the source can be safely read from anyway', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -935,6 +935,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should still schedule an update if an eager selector throws after a mutation', () => { it('should still schedule an update if an eager selector throws after a mutation', () => {
const source = createSource({ const source = createSource({
friends: [ friends: [
@ -1001,6 +1002,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should not warn about updates that fire between unmount and passive unsubcribe', () => { it('should not warn about updates that fire between unmount and passive unsubcribe', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -1037,6 +1039,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should support inline selectors and updates that are processed after selector change', async () => { it('should support inline selectors and updates that are processed after selector change', async () => {
const source = createSource({ const source = createSource({
a: 'initial', a: 'initial',
@ -1081,6 +1084,7 @@ describe('useMutableSource', () => {
expect(root).toMatchRenderedOutput('Another update'); expect(root).toMatchRenderedOutput('Another update');
}); });
// @gate experimental
it('should clear the update queue when getSnapshot changes with pending lower priority updates', async () => { it('should clear the update queue when getSnapshot changes with pending lower priority updates', async () => {
const source = createSource({ const source = createSource({
a: 'initial', a: 'initial',
@ -1137,6 +1141,7 @@ describe('useMutableSource', () => {
expect(root).toMatchRenderedOutput('B: Update'); expect(root).toMatchRenderedOutput('B: Update');
}); });
// @gate experimental
it('should clear the update queue when source changes with pending lower priority updates', async () => { it('should clear the update queue when source changes with pending lower priority updates', async () => {
const sourceA = createSource('initial'); const sourceA = createSource('initial');
const sourceB = createSource('initial'); const sourceB = createSource('initial');
@ -1175,6 +1180,7 @@ describe('useMutableSource', () => {
expect(root).toMatchRenderedOutput('B: Update'); expect(root).toMatchRenderedOutput('B: Update');
}); });
// @gate experimental
it('should always treat reading as potentially unsafe when getSnapshot changes between renders', async () => { it('should always treat reading as potentially unsafe when getSnapshot changes between renders', async () => {
const source = createSource({ const source = createSource({
a: 'foo', a: 'foo',
@ -1264,6 +1270,7 @@ describe('useMutableSource', () => {
expect(Scheduler).toHaveYielded(['x: bar, y: bar']); expect(Scheduler).toHaveYielded(['x: bar, y: bar']);
}); });
// @gate experimental
it('getSnapshot changes and then source is mutated in between paint and passive effect phase', async () => { it('getSnapshot changes and then source is mutated in between paint and passive effect phase', async () => {
const source = createSource({ const source = createSource({
a: 'foo', a: 'foo',
@ -1322,6 +1329,7 @@ describe('useMutableSource', () => {
expect(root).toMatchRenderedOutput('baz'); expect(root).toMatchRenderedOutput('baz');
}); });
// @gate experimental
it('getSnapshot changes and then source is mutated in between paint and passive effect phase, case 2', async () => { it('getSnapshot changes and then source is mutated in between paint and passive effect phase, case 2', async () => {
const source = createSource({ const source = createSource({
a: 'a0', a: 'a0',
@ -1391,6 +1399,7 @@ describe('useMutableSource', () => {
expect(root.getChildrenAsJSX()).toEqual('first: a1, second: a1'); expect(root.getChildrenAsJSX()).toEqual('first: a1, second: a1');
}); });
// @gate experimental
it('getSnapshot changes and then source is mutated during interleaved event', async () => { it('getSnapshot changes and then source is mutated during interleaved event', async () => {
const {useEffect} = React; const {useEffect} = React;
@ -1456,11 +1465,7 @@ describe('useMutableSource', () => {
await act(async () => { await act(async () => {
root.render(<App parentConfig={configA} childConfig={configB} />); root.render(<App parentConfig={configA} childConfig={configB} />);
}); });
expect(Scheduler).toHaveYielded([ expect(Scheduler).toHaveYielded(['Parent: 1', 'Child: 2', 'Commit: 1, 2']);
'Parent: 1',
'Child: 2',
'Commit: 1, 2',
]);
await act(async () => { await act(async () => {
// Switch the parent and the child to read using the same config // Switch the parent and the child to read using the same config
@ -1494,6 +1499,7 @@ describe('useMutableSource', () => {
]); ]);
}); });
// @gate experimental
it('should not tear with newly mounted component when updates were scheduled at a lower priority', async () => { it('should not tear with newly mounted component when updates were scheduled at a lower priority', async () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -1576,6 +1582,7 @@ describe('useMutableSource', () => {
if (__DEV__) { if (__DEV__) {
describe('dev warnings', () => { describe('dev warnings', () => {
// @gate experimental
it('should warn if the subscribe function does not return an unsubscribe function', () => { it('should warn if the subscribe function does not return an unsubscribe function', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -1598,6 +1605,7 @@ describe('useMutableSource', () => {
); );
}); });
// @gate experimental
it('should error if multiple renderers of the same type use a mutable source at the same time', () => { it('should error if multiple renderers of the same type use a mutable source at the same time', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -1654,6 +1662,7 @@ describe('useMutableSource', () => {
}); });
}); });
// @gate experimental
it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', () => { it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', () => {
const source = createSource('one'); const source = createSource('one');
const mutableSource = createMutableSource(source); const mutableSource = createMutableSource(source);
@ -1714,5 +1723,4 @@ describe('useMutableSource', () => {
}); });
}); });
} }
}
}); });

View File

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

View File

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

View File

@ -36,9 +36,7 @@ describe('ReactError', () => {
} }
}); });
if (__DEV__) { // @gate build === "production"
it("empty test so Jest doesn't complain", () => {});
} else {
it('should error with minified error code', () => { it('should error with minified error code', () => {
expect(() => ReactDOM.render('Hi', null)).toThrowError( expect(() => ReactDOM.render('Hi', null)).toThrowError(
'Minified React error #200; visit ' + 'Minified React error #200; visit ' +
@ -47,6 +45,8 @@ describe('ReactError', () => {
' for full errors and additional helpful warnings.', ' for full errors and additional helpful warnings.',
); );
}); });
// @gate build === "production"
it('should serialize arguments', () => { it('should serialize arguments', () => {
function Oops() { function Oops() {
return; return;
@ -60,5 +60,4 @@ describe('ReactError', () => {
' for full errors and additional helpful warnings.', ' for full errors and additional helpful warnings.',
); );
}); });
}
}); });

View File

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

View File

@ -35,7 +35,11 @@
const environmentFlags = { const environmentFlags = {
__DEV__, __DEV__,
build: __DEV__ ? 'development' : 'production', build: __DEV__ ? 'development' : 'production',
// TODO: Should "experimental" also imply "modern"? Maybe we should
// always compare to the channel?
experimental: __EXPERIMENTAL__, experimental: __EXPERIMENTAL__,
// Similarly, should stable imply "classic"?
stable: !__EXPERIMENTAL__, stable: !__EXPERIMENTAL__,
}; };
@ -44,6 +48,18 @@ function getTestFlags() {
// not to but there are exceptions. // not to but there are exceptions.
const featureFlags = require('shared/ReactFeatureFlags'); 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 // Return a proxy so we can throw if you attempt to access a flag that
// doesn't exist. // doesn't exist.
return new Proxy( return new Proxy(
@ -52,6 +68,11 @@ function getTestFlags() {
old: featureFlags.enableNewReconciler === true, old: featureFlags.enableNewReconciler === true,
new: featureFlags.enableNewReconciler === true, new: featureFlags.enableNewReconciler === true,
channel: releaseChannel,
modern: releaseChannel === 'modern',
classic: releaseChannel === 'classic',
www,
...featureFlags, ...featureFlags,
...environmentFlags, ...environmentFlags,
}, },