diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 90105289..8ff72bd3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,8 +14,9 @@ jobs:
# Otherwise how would we know if a specific React version caused the failure?
fail-fast: false
matrix:
- REACT_DIST: [16, 17]
-
+ REACT_DIST: [16, 17, next]
+ # Unstable release channel so let's not block a potential release of `react-transition-group`
+ continue-on-error: ${{ matrix.REACT_DIST == 'next' }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14
diff --git a/package.json b/package.json
index b17cae7f..2afe2dd2 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,7 @@
"@semantic-release/npm": "^7.0.5",
"@storybook/addon-actions": "^6.3.4",
"@storybook/react": "^6.3.4",
- "@testing-library/react": "^12.1.2",
+ "@testing-library/react": "alpha",
"@typescript-eslint/eslint-plugin": "^4.26.1",
"astroturf": "^0.10.4",
"babel-eslint": "^10.1.0",
diff --git a/test/CSSTransition-test.js b/test/CSSTransition-test.js
index 0efaaad2..9ec4672a 100644
--- a/test/CSSTransition-test.js
+++ b/test/CSSTransition-test.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { render } from './utils';
+import { render, waitFor } from './utils';
import CSSTransition from '../src/CSSTransition';
import TransitionGroup from '../src/TransitionGroup';
@@ -36,8 +36,9 @@ describe('CSSTransition', () => {
});
describe('entering', () => {
- it('should apply classes at each transition state', (done) => {
+ it('should apply classes at each transition state', async () => {
let count = 0;
+ let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
@@ -63,12 +64,16 @@ describe('CSSTransition', () => {
onEntered() {
expect(nodeRef.current.className).toEqual('test-enter-done');
expect(count).toEqual(2);
- done();
+ done = true;
},
});
+
+ await waitFor(() => {
+ expect(done).toBe(true);
+ });
});
- it('should apply custom classNames names', (done) => {
+ it('should apply custom classNames names', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
@@ -102,15 +107,17 @@ describe('CSSTransition', () => {
onEntered() {
expect(nodeRef.current.className).toEqual('custom-super-done');
- expect(count).toEqual(2);
- done();
},
});
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
});
});
describe('appearing', () => {
- it('should apply appear classes at each transition state', (done) => {
+ it('should apply appear classes at each transition state', async () => {
let count = 0;
const nodeRef = React.createRef();
render(
@@ -137,17 +144,21 @@ describe('CSSTransition', () => {
expect(nodeRef.current.className).toEqual(
'appear-test-appear-done appear-test-enter-done'
);
- expect(count).toEqual(2);
- done();
}}
>
);
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
});
- it('should lose the "*-appear-done" class after leaving and entering again', (done) => {
+ it('should lose the "*-appear-done" class after leaving and entering again', async () => {
const nodeRef = React.createRef();
+ let entered = false;
+ let exited = false;
const { setProps } = render(
{
in={true}
appear={true}
onEntered={() => {
- setProps({
- in: false,
- onEntered: () => {},
- onExited: () => {
- expect(nodeRef.current.className).toBe('appear-test-exit-done');
- setProps({
- in: true,
- onEntered: () => {
- expect(nodeRef.current.className).toBe(
- 'appear-test-enter-done'
- );
- done();
- },
- });
- },
- });
+ entered = true;
}}
>
);
+
+ await waitFor(() => {
+ expect(entered).toEqual(true);
+ });
+ setProps({
+ in: false,
+ onEntered: () => {},
+ onExited: () => {
+ exited = true;
+ },
+ });
+
+ await waitFor(() => {
+ expect(exited).toEqual(true);
+ });
+ expect(nodeRef.current.className).toBe('appear-test-exit-done');
+ entered = false;
+ setProps({
+ in: true,
+ onEntered: () => {
+ entered = true;
+ },
+ });
+
+ await waitFor(() => {
+ expect(entered).toEqual(true);
+ });
+ expect(nodeRef.current.className).toBe('appear-test-enter-done');
});
- it('should not add undefined when appearDone is not defined', (done) => {
+ it('should not add undefined when appearDone is not defined', async () => {
const nodeRef = React.createRef();
+ let done = false;
render(
{
onEntered={(isAppearing) => {
expect(isAppearing).toEqual(true);
expect(nodeRef.current.className).toEqual('');
- done();
+ done = true;
}}
>
);
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
- it('should not be appearing in normal enter mode', (done) => {
+ it('should not be appearing in normal enter mode', async () => {
let count = 0;
const nodeRef = React.createRef();
render(
@@ -237,10 +266,12 @@ describe('CSSTransition', () => {
expect(nodeRef.current.className).toEqual(
'not-appear-test-enter-done'
);
- expect(count).toEqual(2);
- done();
},
});
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
});
it('should not enter the transition states when appear=false', () => {
@@ -269,7 +300,7 @@ describe('CSSTransition', () => {
});
describe('exiting', () => {
- it('should apply classes at each transition state', (done) => {
+ it('should apply classes at each transition state', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
@@ -295,13 +326,15 @@ describe('CSSTransition', () => {
onExited() {
expect(nodeRef.current.className).toEqual('test-exit-done');
- expect(count).toEqual(2);
- done();
},
});
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
});
- it('should apply custom classNames names', (done) => {
+ it('should apply custom classNames names', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
@@ -336,13 +369,15 @@ describe('CSSTransition', () => {
onExited() {
expect(nodeRef.current.className).toEqual('custom-super-done');
- expect(count).toEqual(2);
- done();
},
});
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
});
- it('should support empty prefix', (done) => {
+ it('should support empty prefix', async () => {
let count = 0;
const nodeRef = React.createRef();
@@ -367,10 +402,12 @@ describe('CSSTransition', () => {
onExited() {
expect(nodeRef.current.className).toEqual('exit-done');
- expect(count).toEqual(2);
- done();
},
});
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
});
});
@@ -412,20 +449,7 @@ describe('CSSTransition', () => {
);
- const rerender = (getProps) =>
- new Promise((resolve) =>
- setProps({
- onEnter: undefined,
- onEntering: undefined,
- onEntered: undefined,
- onExit: undefined,
- onExiting: undefined,
- onExited: undefined,
- ...getProps(resolve),
- })
- );
-
- await rerender((resolve) => ({
+ setProps({
direction: 'up',
text: 'bar',
nodeRef: nodeRef.bar,
@@ -439,11 +463,14 @@ describe('CSSTransition', () => {
expect(nodeRef.bar.current.className).toEqual(
'up-enter up-enter-active'
);
- resolve();
},
- }));
+ });
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
- await rerender((resolve) => ({
+ setProps({
direction: 'down',
text: 'foo',
nodeRef: nodeRef.foo,
@@ -457,11 +484,12 @@ describe('CSSTransition', () => {
onEntered() {
count++;
expect(nodeRef.foo.current.className).toEqual('down-enter-done');
- resolve();
},
- }));
+ });
- expect(count).toEqual(4);
+ await waitFor(() => {
+ expect(count).toEqual(4);
+ });
});
});
});
diff --git a/test/CSSTransitionGroup-test.js b/test/CSSTransitionGroup-test.js
index 4ccd48f2..3f37106c 100644
--- a/test/CSSTransitionGroup-test.js
+++ b/test/CSSTransitionGroup-test.js
@@ -218,7 +218,9 @@ describe('CSSTransitionGroup', () => {
ReactDOM.unmountComponentAtNode(container);
// Testing that no exception is thrown here, as the timeout has been cleared.
- jest.runAllTimers();
+ act(() => {
+ jest.runAllTimers();
+ });
});
it('should handle unmounted elements properly', () => {
diff --git a/test/SwitchTransition-test.js b/test/SwitchTransition-test.js
index 6691d9d5..9250a586 100644
--- a/test/SwitchTransition-test.js
+++ b/test/SwitchTransition-test.js
@@ -106,6 +106,9 @@ describe('SwitchTransition', () => {
act(() => {
jest.runAllTimers();
});
+ act(() => {
+ jest.runAllTimers();
+ });
expect(log).toEqual([
'exit',
'exiting',
@@ -137,6 +140,9 @@ describe('SwitchTransition', () => {
act(() => {
jest.runAllTimers();
});
+ act(() => {
+ jest.runAllTimers();
+ });
expect(log).toEqual([
'enter',
'entering',
diff --git a/test/Transition-test.js b/test/Transition-test.js
index 76b9905c..e9124e14 100644
--- a/test/Transition-test.js
+++ b/test/Transition-test.js
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import { render } from './utils';
+import { render, waitFor } from './utils';
import Transition, {
UNMOUNTED,
@@ -27,14 +27,6 @@ expect.extend({
});
describe('Transition', () => {
- // beforeEach(() => {
- // jest.useFakeTimers();
- // });
-
- // afterEach(() => {
- // jest.useRealTimers();
- // });
-
it('should not transition on mount', () => {
const nodeRef = React.createRef();
render(
@@ -119,8 +111,9 @@ describe('Transition', () => {
expect(nodeRef.current.textContent).toBe('foo: foo, bar: bar');
});
- it('should allow addEndListener instead of timeouts', (done) => {
+ it('should allow addEndListener instead of timeouts', async () => {
let listener = jest.fn((end) => setTimeout(end, 0));
+ let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
@@ -129,7 +122,7 @@ describe('Transition', () => {
addEndListener={listener}
onEntered={() => {
expect(listener).toHaveBeenCalledTimes(1);
- done();
+ done = true;
}}
>
@@ -137,10 +130,15 @@ describe('Transition', () => {
);
setProps({ in: true });
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
- it('should fallback to timeouts with addEndListener', (done) => {
+ it('should fallback to timeouts with addEndListener', async () => {
let calledEnd = false;
+ let done = false;
let listener = (end) =>
setTimeout(() => {
calledEnd = true;
@@ -155,7 +153,7 @@ describe('Transition', () => {
addEndListener={listener}
onEntered={() => {
expect(calledEnd).toEqual(false);
- done();
+ done = true;
}}
>
@@ -163,10 +161,15 @@ describe('Transition', () => {
);
setProps({ in: true });
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
- it('should mount/unmount immediately if not have enter/exit timeout', (done) => {
+ it('should mount/unmount immediately if not have enter/exit timeout', async () => {
const nodeRef = React.createRef();
+ let done = false;
const { setProps } = render(
{(status) => status: {status}
}
@@ -182,12 +185,16 @@ describe('Transition', () => {
in: false,
onExited() {
expect(nodeRef.current.textContent).toEqual(`status: ${EXITED}`);
- if (!calledAfterTimeout) {
- return done();
+ if (calledAfterTimeout) {
+ throw new Error('wrong timeout');
}
- throw new Error('wrong timeout');
+ done = true;
},
});
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
it('should use `React.findDOMNode` when `nodeRef` is not provided', () => {
@@ -220,8 +227,9 @@ describe('Transition', () => {
});
describe('appearing timeout', () => {
- it('should use enter timeout if appear not set', (done) => {
+ it('should use enter timeout if appear not set', async () => {
let calledBeforeEntered = false;
+ let done = false;
setTimeout(() => {
calledBeforeEntered = true;
}, 10);
@@ -240,15 +248,20 @@ describe('Transition', () => {
setProps({
onEntered() {
if (calledBeforeEntered) {
- done();
+ done = true;
} else {
throw new Error('wrong timeout');
}
},
});
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
- it('should use appear timeout if appear is set', (done) => {
+ it('should use appear timeout if appear is set', async () => {
+ let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
{
if (isCausedLate) {
throw new Error('wrong timeout');
} else {
- done();
+ done = true;
}
},
});
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
});
describe('entering', () => {
- it('should fire callbacks', (done) => {
+ it('should fire callbacks', async () => {
let callOrder = [];
+ let done = false;
let onEnter = jest.fn(() => callOrder.push('onEnter'));
let onEntering = jest.fn(() => callOrder.push('onEntering'));
const nodeRef = React.createRef();
@@ -303,12 +321,16 @@ describe('Transition', () => {
expect(onEnter).toHaveBeenCalledTimes(1);
expect(onEntering).toHaveBeenCalledTimes(1);
expect(callOrder).toEqual(['onEnter', 'onEntering']);
- done();
+ done = true;
},
});
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
- it('should move to each transition state', (done) => {
+ it('should move to each transition state', async () => {
let count = 0;
const nodeRef = React.createRef();
const { setProps } = render(
@@ -334,16 +356,19 @@ describe('Transition', () => {
onEntered() {
expect(nodeRef.current.textContent).toEqual(`status: ${ENTERED}`);
- expect(count).toEqual(2);
- done();
},
});
+
+ await waitFor(() => {
+ expect(count).toEqual(2);
+ });
});
});
describe('exiting', () => {
- it('should fire callbacks', (done) => {
+ it('should fire callbacks', async () => {
let callOrder = [];
+ let done = false;
let onExit = jest.fn(() => callOrder.push('onExit'));
let onExiting = jest.fn(() => callOrder.push('onExiting'));
const nodeRef = React.createRef();
@@ -366,13 +391,18 @@ describe('Transition', () => {
expect(onExit).toHaveBeenCalledTimes(1);
expect(onExiting).toHaveBeenCalledTimes(1);
expect(callOrder).toEqual(['onExit', 'onExiting']);
- done();
+ done = true;
},
});
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
- it('should move to each transition state', (done) => {
+ it('should move to each transition state', async () => {
let count = 0;
+ let done = false;
const nodeRef = React.createRef();
const { setProps } = render(
@@ -398,9 +428,13 @@ describe('Transition', () => {
onExited() {
expect(nodeRef.current.textContent).toEqual(`status: ${EXITED}`);
expect(count).toEqual(2);
- done();
+ done = true;
},
});
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
});
@@ -449,25 +483,35 @@ describe('Transition', () => {
setProps({ in: true });
});
- it('should stay mounted after exiting', (done) => {
+ it('should stay mounted after exiting', async () => {
+ let entered = false;
+ let exited = false;
const { container, setProps } = render(
{
- expect(container.textContent).toEqual(`status: ${ENTERED}`);
-
- setProps({ in: false });
+ entered = true;
}}
onExited={() => {
- expect(container.textContent).toEqual(`status: ${EXITED}`);
-
- done();
+ exited = true;
}}
/>
);
expect(container.textContent).toEqual('');
setProps({ in: true });
+
+ await waitFor(() => {
+ expect(entered).toEqual(true);
+ });
+ expect(container.textContent).toEqual(`status: ${ENTERED}`);
+
+ setProps({ in: false });
+
+ await waitFor(() => {
+ expect(exited).toEqual(true);
+ });
+ expect(container.textContent).toEqual(`status: ${EXITED}`);
});
});
@@ -500,7 +544,8 @@ describe('Transition', () => {
};
}
- it('should mount when entering', (done) => {
+ it('should mount when entering', async () => {
+ let done = false;
const instanceRef = React.createRef();
const { setProps } = render(
{
expect(instanceRef.current.getStatus()).toEqual(EXITED);
expect(instanceRef.current.nodeRef.current).toExist();
- done();
+ done = true;
}}
/>
);
@@ -519,9 +564,14 @@ describe('Transition', () => {
expect(instanceRef.current.nodeRef.current).toBeNull();
setProps({ in: true });
+
+ await waitFor(() => {
+ expect(done).toEqual(true);
+ });
});
- it('should unmount after exiting', (done) => {
+ it('should unmount after exiting', async () => {
+ let exited = false;
const instanceRef = React.createRef();
const { setProps } = render(
{
in
onExited={() => {
setTimeout(() => {
- expect(instanceRef.current.getStatus()).toEqual(UNMOUNTED);
- expect(instanceRef.current.nodeRef.current).not.toExist();
- done();
+ exited = true;
});
}}
/>
@@ -541,6 +589,13 @@ describe('Transition', () => {
expect(instanceRef.current.nodeRef.current).toExist();
setProps({ in: false });
+
+ await waitFor(() => {
+ expect(exited).toEqual(true);
+ });
+
+ expect(instanceRef.current.getStatus()).toEqual(UNMOUNTED);
+ expect(instanceRef.current.nodeRef.current).not.toExist();
});
});
});
diff --git a/test/TransitionGroup-test.js b/test/TransitionGroup-test.js
index 6379dfd9..da07db44 100644
--- a/test/TransitionGroup-test.js
+++ b/test/TransitionGroup-test.js
@@ -92,14 +92,24 @@ describe('TransitionGroup', () => {
act(() => {
jest.runAllTimers();
});
- expect(log).toEqual(['appear', 'appearing', 'appeared']);
+ expect(log).toEqual(
+ // React 18 StrictEffects will call `componentDidMount` twice causing two `onEnter` calls.
+ React.useTransition !== undefined
+ ? ['appear', 'appear', 'appearing', 'appeared']
+ : ['appear', 'appearing', 'appeared']
+ );
log = [];
renderStrict(, container);
act(() => {
jest.runAllTimers();
});
- expect(log).toEqual(['enter', 'entering', 'entered']);
+ expect(log).toEqual(
+ // React 18 StrictEffects will call `componentDidMount` twice causing two `onEnter` calls.
+ React.useTransition !== undefined
+ ? ['enter', 'enter', 'entering', 'entered']
+ : ['enter', 'entering', 'entered']
+ );
log = [];
renderStrict(, container);
diff --git a/yarn.lock b/yarn.lock
index c860a8c8..5a9db17a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3503,10 +3503,10 @@
resolve-from "^5.0.0"
store2 "^2.12.0"
-"@testing-library/dom@^8.0.0":
- version "8.11.1"
- resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.1.tgz#03fa2684aa09ade589b460db46b4c7be9fc69753"
- integrity sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==
+"@testing-library/dom@^8.5.0":
+ version "8.11.2"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.2.tgz#fc110c665a066c2287be765e4a35ba8dad737015"
+ integrity sha512-idsS/cqbYudXcVWngc1PuWNmXs416oBy2g/7Q8QAUREt5Z3MUkAL2XJD7xazLJ6esDfqRDi/ZBxk+OPPXitHRw==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
@@ -3517,13 +3517,13 @@
lz-string "^1.4.4"
pretty-format "^27.0.2"
-"@testing-library/react@^12.1.2":
- version "12.1.2"
- resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76"
- integrity sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g==
+"@testing-library/react@alpha":
+ version "13.0.0-alpha.5"
+ resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.0.0-alpha.5.tgz#29bfc36b550e2a1025cbebf7254d5a0a46cb58c5"
+ integrity sha512-QrxKC/7pTE0ze3wLZNaenGJqsLcbAJL71XqU/ryJTL2pqZBjiJHuxZavl2ZQAxnBQkDQF9oh9my3bKPstWfnhA==
dependencies:
"@babel/runtime" "^7.12.5"
- "@testing-library/dom" "^8.0.0"
+ "@testing-library/dom" "^8.5.0"
"@tootallnate/once@1":
version "1.0.0"