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"