From 656579f3321ca99a3b5723c6645da8417bb363e9 Mon Sep 17 00:00:00 2001 From: zombiej Date: Wed, 27 Apr 2022 15:13:10 +0800 Subject: [PATCH 1/3] chore: init test env --- package.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fe9d0e1..787b1eb 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,9 @@ "rc-util": "^5.17.0" }, "devDependencies": { + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.0.0", "@types/classnames": "^2.2.9", - "@types/enzyme": "^3.10.11", "@types/jest": "^27.4.0", "@types/react": "^17.0.13", "@types/react-dom": "^16.9.0", @@ -59,8 +60,6 @@ "coveralls": "^3.0.6", "cross-env": "^7.0.2", "dumi": "^1.1.0", - "enzyme": "^3.0.0", - "enzyme-adapter-react-16": "^1.0.1", "enzyme-to-json": "^3.4.0", "eslint": "^7.0.0", "father": "^2.13.4", @@ -71,9 +70,8 @@ "np": "^7.0.0", "prettier": "^2.0.5", "pretty-quick": "^3.0.0", - "react": "^16.0.0", - "react-dom": "^16.0.0", - "react-test-renderer": "^16.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", "typescript": "^4.0.5" }, "peerDependencies": { From ab15881130d5d9d9e3eb210c448a385c25271dc7 Mon Sep 17 00:00:00 2001 From: zombiej Date: Wed, 27 Apr 2022 19:43:25 +0800 Subject: [PATCH 2/3] test: all test --- jest.config.js | 4 +- tests/__snapshots__/index.spec.tsx.snap | 158 ++++----- tests/index.spec.tsx | 408 +++++++++++------------- tests/setup.js | 54 ---- tests/setup.ts | 34 ++ tests/setupFilesAfterEnv.ts | 1 + 6 files changed, 304 insertions(+), 355 deletions(-) delete mode 100644 tests/setup.js create mode 100644 tests/setup.ts create mode 100644 tests/setupFilesAfterEnv.ts diff --git a/jest.config.js b/jest.config.js index 86627c3..7213768 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,4 @@ module.exports = { - setupFiles: ['./tests/setup.js'], - snapshotSerializers: [require.resolve('enzyme-to-json/serializer')], + setupFiles: ['/tests/setup.ts'], + setupFilesAfterEnv: ['/tests/setupFilesAfterEnv.ts'], }; diff --git a/tests/__snapshots__/index.spec.tsx.snap b/tests/__snapshots__/index.spec.tsx.snap index 012d81e..95de403 100644 --- a/tests/__snapshots__/index.spec.tsx.snap +++ b/tests/__snapshots__/index.spec.tsx.snap @@ -160,85 +160,6 @@ exports[`rc-segmented render segmented with CSSMotion 1`] = ` `; -exports[`rc-segmented render segmented with title 1`] = ` -
- - - - - -
-`; - exports[`rc-segmented render segmented with options 1`] = `
`; +exports[`rc-segmented render segmented with title 1`] = ` +
+ + + + + +
+`; + exports[`rc-segmented render segmented: disabled 1`] = `
{ }; }); -jest.useFakeTimers(); - describe('rc-segmented', () => { + function expectMatchChecked(container: HTMLElement, checkedList: boolean[]) { + const inputList = Array.from( + container.querySelectorAll('.rc-segmented-item-input'), + ); + expect(inputList).toHaveLength(checkedList.length); + + inputList.forEach((input, i) => { + const checked = checkedList[i]; + + expect(input.checked).toBe(checked); + }); + } + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('render empty segmented', () => { - const wrapper = mount(); - expect(wrapper.render()).toMatchSnapshot(); + const { asFragment } = render(); + expect(asFragment().firstChild).toMatchSnapshot(); }); it('render segmented ok', () => { - const wrapper = mount( + const { container, asFragment } = render( , ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false]); + expectMatchChecked(container, [true, false, false]); }); it('render label with ReactNode', () => { - const wrapper = mount( + const { container, asFragment } = render( { />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); + expectMatchChecked(container, [true, false, false]); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false]); - - expect(wrapper.find('#android').at(0).text()).toContain('Android'); - expect(wrapper.find('h2').at(0).text()).toContain('Web'); + expect(container.querySelector('#android')?.textContent).toContain( + 'Android', + ); + expect(container.querySelector('h2')?.textContent).toContain('Web'); }); it('render segmented with defaultValue', () => { - const wrapper = mount( + const { container } = render( , ); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([false, false, true]); + expectMatchChecked(container, [false, false, true]); }); it('render segmented with options', () => { const handleValueChange = jest.fn(); - const wrapper = mount( + const { container, asFragment } = render( handleValueChange(value)} />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); + expectMatchChecked(container, [true, false, false]); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false]); - expect( - wrapper - .find('.rc-segmented-item') - .at(0) - .hasClass('rc-segmented-item-selected'), - ).toBeTruthy(); + expect(container.querySelectorAll('.rc-segmented-item')[0]).toHaveClass( + 'rc-segmented-item-selected', + ); - wrapper.find('.rc-segmented-item-input').at(2).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[2]); expect(handleValueChange).toBeCalledWith('Web'); - - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([false, false, true]); + expectMatchChecked(container, [false, false, true]); }); it('render segmented with options: 1', () => { const handleValueChange = jest.fn(); - const wrapper = mount( + const { container, asFragment } = render( handleValueChange(value)} />, ); - expect(wrapper.render()).toMatchSnapshot(); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false, false, false]); + expect(asFragment().firstChild).toMatchSnapshot(); + expectMatchChecked(container, [true, false, false, false, false]); - wrapper.find('.rc-segmented-item-input').last().simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[4]); expect(handleValueChange).toBeCalledWith(5); - - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([false, false, false, false, true]); + expectMatchChecked(container, [false, false, false, false, true]); }); it('render segmented with options: 2', () => { const handleValueChange = jest.fn(); - const wrapper = mount( + const { container, asFragment } = render( handleValueChange(value)} />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); - wrapper.find('.rc-segmented-item-input').at(1).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[1]); expect(handleValueChange).toBeCalledWith('Android'); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([false, true, false]); + expectMatchChecked(container, [false, true, false]); }); it('render segmented with options: disabled', () => { const handleValueChange = jest.fn(); - const wrapper = mount( + const { container, asFragment } = render( { onChange={(value) => handleValueChange(value)} />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); expect( - wrapper - .find('label.rc-segmented-item') - .at(1) - .hasClass('rc-segmented-item-disabled'), - ).toBeTruthy(); + container.querySelectorAll('label.rc-segmented-item')[1], + ).toHaveClass('rc-segmented-item-disabled'); expect( - wrapper.find('.rc-segmented-item-input').at(1).prop('disabled'), + container.querySelectorAll( + '.rc-segmented-item-input', + )[1].disabled, ).toBeTruthy(); - wrapper.find('.rc-segmented-item-input').at(1).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[1]); expect(handleValueChange).not.toBeCalled(); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false]); + expectMatchChecked(container, [true, false, false]); - wrapper.find('.rc-segmented-item-input').at(2).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[2]); expect(handleValueChange).toBeCalledWith('Web'); expect(handleValueChange).toBeCalledTimes(1); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([false, false, true]); + expectMatchChecked(container, [false, false, true]); }); it('render segmented: disabled', () => { const handleValueChange = jest.fn(); - const wrapper = mount( + const { container, asFragment } = render( handleValueChange(value)} />, ); - expect(wrapper.render()).toMatchSnapshot(); - expect( - wrapper.find('.rc-segmented').hasClass('rc-segmented-disabled'), - ).toBeTruthy(); + expect(asFragment().firstChild).toMatchSnapshot(); + expect(container.querySelector('.rc-segmented')).toHaveClass( + 'rc-segmented-disabled', + ); - wrapper.find('.rc-segmented-item-input').at(1).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[1]); expect(handleValueChange).not.toBeCalled(); + expectMatchChecked(container, [true, false, false]); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false]); - - wrapper.find('.rc-segmented-item-input').at(2).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[2]); expect(handleValueChange).not.toBeCalled(); - - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false]); + expectMatchChecked(container, [true, false, false]); }); it('render segmented with className and other html attributes', () => { - const wrapper = mount( + const { container } = render( { />, ); - expect(wrapper.hasClass('mock-cls')).toBeTruthy(); - expect(wrapper.prop('data-test-id')).toBe('hello'); + expect(container.firstChild).toHaveClass('mock-cls'); + expect(container.firstChild).toHaveAttribute('data-test-id', 'hello'); }); it('render segmented with ref', () => { const ref = React.createRef(); - const wrapper = mount( + const { container } = render( { />, ); - const segmentedEl = wrapper.find(Segmented).getElement(); - expect((segmentedEl as any).ref).toBe(ref); + expect(ref.current).toBe(container.querySelector('.rc-segmented')); }); it('render segmented with controlled mode', () => { - class Demo extends React.Component<{}, { value: SegmentedValue }> { - state = { - value: 'Web3', - }; - - render() { - return ( - - this.setState({ - value, - }) - } + const Demo = () => { + const options = ['iOS', 'Android', 'Web3']; + + const [value, setValue] = React.useState('Web3'); + + return ( + <> + +
{value}
+ { + setValue(e.target.value); + }} /> - ); - } - } - const wrapper = mount(); - wrapper - .find('Segmented') - .find('.rc-segmented-item-input') - .at(0) - .simulate('change'); - expect(wrapper.state().value).toBe('iOS'); - - wrapper - .find('Segmented') - .find('.rc-segmented-item-input') - .at(1) - .simulate('change'); - expect(wrapper.state().value).toBe('Android'); + + ); + }; + const { container } = render(); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[0]); + expect(container.querySelector('.value')?.textContent).toBe('iOS'); + + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[1]); + expect(container.querySelector('.value')?.textContent).toBe('Android'); // change state directly - wrapper.find(Demo).setState({ value: 'Web3' }); + fireEvent.change(container.querySelector('.control')!, { + target: { value: 'Web3' }, + }); + expect(container.querySelector('.value')?.textContent).toBe('Web3'); + + // Motion to active + act(() => { + jest.runAllTimers(); + }); // Motion end - wrapper.triggerMotionEvent(); + fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!); act(() => { jest.runAllTimers(); - wrapper.update(); }); expect( - wrapper.find('.rc-segmented-item-selected').contains('Web3'), - ).toBeTruthy(); + container.querySelector('.rc-segmented-item-selected')?.textContent, + ).toContain('Web3'); // Motion end - wrapper.triggerMotionEvent(); + fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!); act(() => { jest.runAllTimers(); - wrapper.update(); }); // change it strangely - wrapper.find(Demo).setState({ value: 'Web4' }); + fireEvent.change(container.querySelector('.control')!, { + target: { value: 'Web4' }, + }); + // invalid changes expect( - wrapper.find('.rc-segmented-item-selected').contains('Web3'), - ).toBeTruthy(); + container.querySelector('.rc-segmented-item-selected')?.textContent, + ).toContain('Web3'); }); it('render segmented with CSSMotion', () => { const handleValueChange = jest.fn(); - const wrapper = mount( + const { container, asFragment } = render( handleValueChange(value)} />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([true, false, false]); - expect( - wrapper - .find('.rc-segmented-item') - .at(0) - .hasClass('rc-segmented-item-selected'), - ).toBeTruthy(); + expectMatchChecked(container, [true, false, false]); + expect(container.querySelectorAll('.rc-segmented-item')[0]).toHaveClass( + 'rc-segmented-item-selected', + ); - wrapper.find('.rc-segmented-item-input').at(2).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[2]); expect(handleValueChange).toBeCalledWith('Web3'); + expectMatchChecked(container, [false, false, true]); - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([false, false, true]); - - const thumb = wrapper.find('.rc-segmented-thumb').at(0); - expect(thumb.hasClass('rc-segmented-thumb-motion')); + expect(container.querySelectorAll('.rc-segmented-thumb')[0]).toHaveClass( + 'rc-segmented-thumb-motion', + ); // thumb appeared at `iOS` - const thumbDom = wrapper - .find('.rc-segmented-thumb') - .at(0) - .getDOMNode() as HTMLDivElement; - expect(thumbDom.style.transform).toBe('translateX(0px)'); - expect(thumbDom.style.width).toBe('62px'); + expect(container.querySelectorAll('.rc-segmented-thumb')[0]).toHaveStyle({ + transform: 'translateX(0px)', + width: '62px', + }); - // Motion end - wrapper.triggerMotionEvent(); + // Motion => active act(() => { jest.runAllTimers(); - wrapper.update(); }); + + // Motion enter end + fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!); + act(() => { + jest.runAllTimers(); + }); + + // Motion leave end + fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!); + act(() => { + jest.runAllTimers(); + }); + // thumb should disappear - expect(wrapper.find('.rc-segmented-thumb').length).toBe(0); + expect(container.querySelector('.rc-segmented-thumb')).toBeFalsy(); // change selection again - wrapper.find('.rc-segmented-item-input').at(1).simulate('change'); + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[1]); expect(handleValueChange).toBeCalledWith('Android'); - - expect( - wrapper - .find('.rc-segmented-item-input') - .map((el) => (el.getDOMNode() as HTMLInputElement).checked), - ).toEqual([false, true, false]); + expectMatchChecked(container, [false, true, false]); // thumb should move - const thumb1 = wrapper.find('.rc-segmented-thumb').at(0); - expect(thumb1.hasClass('rc-segmented-thumb-motion')); + expect(container.querySelector('.rc-segmented-thumb')).toHaveClass( + 'rc-segmented-thumb-motion', + ); // thumb appeared at `Web3` - const thumbDom1 = wrapper - .find('.rc-segmented-thumb') - .at(0) - .getDOMNode() as HTMLDivElement; - expect(thumbDom1.style.transform).toBe('translateX(180px)'); - expect(thumbDom1.style.width).toBe('76px'); + expect(container.querySelector('.rc-segmented-thumb')).toHaveStyle({ + transform: 'translateX(180px)', + width: '76px', + }); - // Motion end - wrapper.triggerMotionEvent(); + // Motion enter end act(() => { jest.runAllTimers(); - wrapper.update(); }); + fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!); + act(() => { + jest.runAllTimers(); + }); + + // Motion leave end + fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!); + act(() => { + jest.runAllTimers(); + }); + // thumb should disappear - expect(wrapper.find('.rc-segmented-thumb').length).toBe(0); + expect(container.querySelector('.rc-segmented-thumb')).toBeFalsy(); }); it('render segmented with options null/undefined', () => { const handleValueChange = jest.fn(); - const wrapper = mount( + const { asFragment, container } = render( handleValueChange(value)} />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); + expect( - wrapper - .find('.rc-segmented-item-label') - .map((n) => n.getDOMNode().textContent), + Array.from( + container.querySelectorAll( + '.rc-segmented-item-label', + ), + ).map((n) => n.textContent), ).toEqual(['', '', '']); }); it('render segmented with title', () => { - const wrapper = mount( + const { asFragment, container } = render( { ]} />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(asFragment().firstChild).toMatchSnapshot(); + expect( - wrapper - .find('.rc-segmented-item-label') - .map((n) => (n.getDOMNode() as HTMLElement).title), + Array.from( + container.querySelectorAll( + '.rc-segmented-item-label', + ), + ).map((n) => n.title), ).toEqual(['Web', 'hello1', '', 'hello1.5', '']); }); }); diff --git a/tests/setup.js b/tests/setup.js deleted file mode 100644 index 5878ced..0000000 --- a/tests/setup.js +++ /dev/null @@ -1,54 +0,0 @@ -const Enzyme = require('enzyme'); -const Adapter = require('enzyme-adapter-react-16'); -const { act } = require('react-dom/test-utils'); -require('regenerator-runtime/runtime'); - -window.requestAnimationFrame = (func) => { - window.setTimeout(func, 16); -}; - -Enzyme.configure({ adapter: new Adapter() }); - -Object.assign(Enzyme.ReactWrapper.prototype, { - triggerMotionEvent(target) { - const motionEvent = new Event('transitionend'); - if (target) { - Object.defineProperty(motionEvent, 'target', { - get: () => target.getDOMNode(), - }); - } - - act(() => { - const element = this.find('CSSMotion').getDOMNode(); - element?.dispatchEvent(motionEvent); - this.update(); - }); - - return this; - }, -}); - -// https://github.com/jsdom/jsdom/issues/135#issuecomment-68191941 -Object.defineProperties(window.HTMLElement.prototype, { - offsetLeft: { - get() { - let offsetLeft = 0; - const childList = Array.from(this.parentNode.children); - for (let i = 0; i < childList.length; i++) { - const child = childList[i]; - const lastChild = childList[i - 1]; - offsetLeft += lastChild?.clientWidth || 0; - if (child === this) { - break; - } - } - return offsetLeft; - }, - }, - clientWidth: { - get() { - // text length + vertical padding - return this.textContent.length * 14 + 20; - }, - }, -}); diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..8fa23d1 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,34 @@ +require('regenerator-runtime/runtime'); + +window.requestAnimationFrame = (func) => { + return window.setTimeout(func, 16); +}; + +window.cancelAnimationFrame = (id) => { + return window.clearTimeout(id); +}; + +// https://github.com/jsdom/jsdom/issues/135#issuecomment-68191941 +Object.defineProperties(window.HTMLElement.prototype, { + offsetLeft: { + get() { + let offsetLeft = 0; + const childList: HTMLElement[] = Array.from(this.parentNode.children); + for (let i = 0; i < childList.length; i++) { + const child = childList[i]; + const lastChild = childList[i - 1]; + offsetLeft += lastChild?.clientWidth || 0; + if (child === this) { + break; + } + } + return offsetLeft; + }, + }, + clientWidth: { + get() { + // text length + vertical padding + return this.textContent.length * 14 + 20; + }, + }, +}); diff --git a/tests/setupFilesAfterEnv.ts b/tests/setupFilesAfterEnv.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/tests/setupFilesAfterEnv.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; From 9b5cf9eba25838924695d820c213bbe5ca8bd6bd Mon Sep 17 00:00:00 2001 From: vagusx Date: Wed, 27 Apr 2022 20:02:31 +0800 Subject: [PATCH 3/3] chore: remove useless code and deps --- package.json | 1 - tests/wrapper.ts | 16 ---------------- 2 files changed, 17 deletions(-) delete mode 100644 tests/wrapper.ts diff --git a/package.json b/package.json index 787b1eb..50f0a49 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "coveralls": "^3.0.6", "cross-env": "^7.0.2", "dumi": "^1.1.0", - "enzyme-to-json": "^3.4.0", "eslint": "^7.0.0", "father": "^2.13.4", "father-build": "^1.18.6", diff --git a/tests/wrapper.ts b/tests/wrapper.ts deleted file mode 100644 index ff11307..0000000 --- a/tests/wrapper.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { mount as enzymeMount } from 'enzyme'; -import type { ReactWrapper } from 'enzyme'; - -export type MountParam = Parameters; - -export interface WrapperType extends ReactWrapper { - triggerMotionEvent: (target?: ReactWrapper) => WrapperType; -} - -export function mount< - C extends React.Component, - P = C['props'], - S = C['state'], ->(...args: MountParam) { - return enzymeMount(...args) as unknown as WrapperType; -}