From a9a651ffd58ca867ccdec8757f891ddad610d988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Tue, 9 Jun 2020 18:10:43 +0800 Subject: [PATCH] fix: Form List warning (#24867) * fix: Form List warning * clean reaf --- components/form/FormItem.tsx | 2 +- components/form/__tests__/index.test.js | 144 +------------------- components/form/__tests__/list.test.js | 167 ++++++++++++++++++++++++ components/form/util.ts | 20 +-- tests/utils.ts | 4 +- 5 files changed, 189 insertions(+), 148 deletions(-) create mode 100644 components/form/__tests__/list.test.js diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx index b4807c176d..077c2ebf23 100644 --- a/components/form/FormItem.tsx +++ b/components/form/FormItem.tsx @@ -258,7 +258,7 @@ function FormItem(props: FormItemProps): React.ReactElement { nameRef.current = [...mergedName]; if (fieldKey) { const fieldKeys = Array.isArray(fieldKey) ? fieldKey : [fieldKey]; - nameRef.current = [...mergedName.slice(-1), ...fieldKeys]; + nameRef.current = [...mergedName.slice(0, -1), ...fieldKeys]; } updateItemErrors(nameRef.current.join('__SPLIT__'), errors); } diff --git a/components/form/__tests__/index.test.js b/components/form/__tests__/index.test.js index 4e84bc26e5..b80f563a7f 100644 --- a/components/form/__tests__/index.test.js +++ b/components/form/__tests__/index.test.js @@ -10,11 +10,6 @@ import { sleep } from '../../../tests/utils'; jest.mock('scroll-into-view-if-needed'); -const delay = (timeout = 0) => - new Promise(resolve => { - setTimeout(resolve, timeout); - }); - describe('Form', () => { mountTest(Form); mountTest(Form.Item); @@ -27,7 +22,7 @@ describe('Form', () => { async function change(wrapper, index, value) { wrapper.find(Input).at(index).simulate('change', { target: { value } }); - await delay(50); + await sleep(100); wrapper.update(); } @@ -45,133 +40,6 @@ describe('Form', () => { scrollIntoView.mockRestore(); }); - describe('List', () => { - function testList(name, renderField) { - it(name, async () => { - const wrapper = mount( -
- - {(fields, { add, remove }) => ( - <> - {fields.map(field => renderField(field))} - - - - )} - -
, - ); - - async function operate(className) { - wrapper.find(className).last().simulate('click'); - await delay(); - wrapper.update(); - } - - await operate('.add'); - expect(wrapper.find(Input).length).toBe(1); - - await operate('.add'); - expect(wrapper.find(Input).length).toBe(2); - - await change(wrapper, 1, ''); - wrapper.update(); - await sleep(300); - expect(wrapper.find('.ant-form-item-explain').length).toBe(1); - - await operate('.remove'); - wrapper.update(); - expect(wrapper.find(Input).length).toBe(1); - expect(wrapper.find('.ant-form-item-explain').length).toBe(0); - }); - } - - testList('operation correctly', field => ( - - - - )); - - testList('nest noStyle', field => ( - - - - - - )); - - it('correct onFinish values', async () => { - async function click(wrapper, className) { - wrapper.find(className).last().simulate('click'); - await delay(); - wrapper.update(); - } - - const onFinish = jest.fn().mockImplementation(() => {}); - - const wrapper = mount( -
{ - if (typeof v.list[0] === 'object') { - /* old version led to SyntheticEvent be passed as an value here - that led to weird infinite loop somewhere and OutOfMemory crash */ - v = new Error('We expect value to be a primitive here'); - } - onFinish(v); - }} - > - - {(fields, { add, remove }) => ( - <> - {fields.map(field => ( - // key is in a field - // eslint-disable-next-line react/jsx-key - - - - ))} - - - - )} - -
, - ); - - await click(wrapper, '.add'); - await change(wrapper, 0, 'input1'); - wrapper.find('form').simulate('submit'); - await delay(); - expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1'] }); - - await click(wrapper, '.add'); - await change(wrapper, 1, 'input2'); - await click(wrapper, '.add'); - await change(wrapper, 2, 'input3'); - wrapper.find('form').simulate('submit'); - await delay(); - expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1', 'input2', 'input3'] }); - - await click(wrapper, '.remove'); // will remove first input - wrapper.find('form').simulate('submit'); - await delay(); - expect(onFinish).toHaveBeenLastCalledWith({ list: ['input2', 'input3'] }); - }); - }); - it('noStyle Form.Item', async () => { const onChange = jest.fn(); @@ -300,7 +168,7 @@ describe('Form', () => { expect(scrollIntoView).not.toHaveBeenCalled(); wrapper.find('form').simulate('submit'); - await delay(50); + await sleep(50); expect(scrollIntoView).toHaveBeenCalled(); expect(onFinishFailed).toHaveBeenCalled(); @@ -401,7 +269,7 @@ describe('Form', () => { expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual("'name' is required"); await change(wrapper, 0, 'p'); - await delay(100); + await sleep(100); wrapper.update(); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('not a p'); } @@ -585,9 +453,9 @@ describe('Form', () => { ); wrapper.find('form').simulate('submit'); - await delay(100); + await sleep(100); wrapper.update(); - await delay(100); + await sleep(100); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('Bamboo is good!'); }); @@ -642,7 +510,7 @@ describe('Form', () => { }, }); - await delay(); + await sleep(); expect(renderTimes).toEqual(1); expect(wrapper.find('input').props().value).toEqual('a'); diff --git a/components/form/__tests__/list.test.js b/components/form/__tests__/list.test.js new file mode 100644 index 0000000000..d966649a0b --- /dev/null +++ b/components/form/__tests__/list.test.js @@ -0,0 +1,167 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import raf from 'raf'; +import { act } from 'react-dom/test-utils'; +import Form from '..'; +import Input from '../../input'; +import Button from '../../button'; +import { sleep } from '../../../tests/utils'; + +jest.mock('raf'); + +describe('Form.List', () => { + raf.mockImplementation(callback => window.setTimeout(callback)); + + afterAll(() => { + raf.mockRestore(); + }); + + async function change(wrapper, index, value) { + wrapper.find(Input).at(index).simulate('change', { target: { value } }); + await sleep(); + wrapper.update(); + } + + function testList(name, renderField) { + it(name, async () => { + jest.useFakeTimers(); + + const wrapper = mount( +
+ + {(fields, { add, remove }) => ( + <> + {fields.map(field => renderField(field))} + + + + + )} +
+ , + ); + + await click(wrapper, '.add'); + await change(wrapper, 0, 'input1'); + wrapper.find('form').simulate('submit'); + await sleep(); + expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1'] }); + + await click(wrapper, '.add'); + await change(wrapper, 1, 'input2'); + await click(wrapper, '.add'); + await change(wrapper, 2, 'input3'); + wrapper.find('form').simulate('submit'); + await sleep(); + expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1', 'input2', 'input3'] }); + + await click(wrapper, '.remove'); // will remove first input + wrapper.find('form').simulate('submit'); + await sleep(); + expect(onFinish).toHaveBeenLastCalledWith({ list: ['input2', 'input3'] }); + }); +}); diff --git a/components/form/util.ts b/components/form/util.ts index 2ab1242cb6..93fd896890 100644 --- a/components/form/util.ts +++ b/components/form/util.ts @@ -21,19 +21,19 @@ export function useCacheErrors( const [, forceUpdate] = React.useState({}); - const update = () => { + const update = (newErrors: React.ReactNode[]) => { const prevVisible = cacheRef.current.visible; - const newVisible = !!errors.length; + const newVisible = !!newErrors.length; const prevErrors = cacheRef.current.errors; - cacheRef.current.errors = errors; + cacheRef.current.errors = newErrors; cacheRef.current.visible = newVisible; if (prevVisible !== newVisible) { changeTrigger(newVisible); } else if ( - prevErrors.length !== errors.length || - prevErrors.some((prevErr, index) => prevErr !== errors[index]) + prevErrors.length !== newErrors.length || + prevErrors.some((prevErr, index) => prevErr !== newErrors[index]) ) { forceUpdate({}); } @@ -41,13 +41,17 @@ export function useCacheErrors( React.useEffect(() => { if (!directly) { - const timeout = setTimeout(update, 10); - return () => clearTimeout(timeout); + const timeout = setTimeout(() => { + update(errors); + }, 10); + return () => { + clearTimeout(timeout); + }; } }, [errors]); if (directly) { - update(); + update(errors); } return [cacheRef.current.visible, cacheRef.current.errors]; diff --git a/tests/utils.ts b/tests/utils.ts index 53b05cc741..c6914d0afd 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -8,4 +8,6 @@ export function resetMockDate() { MockDate.reset(); } -export const sleep = (timeout = 0) => new Promise(resolve => setTimeout(resolve, timeout)); +const globalTimeout = global.setTimeout; + +export const sleep = (timeout = 0) => new Promise(resolve => globalTimeout(resolve, timeout));