diff --git a/.eslintrc.js b/.eslintrc.js
index 443f8748..f30d7a7e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -7,5 +7,6 @@ module.exports = {
'no-template-curly-in-string': 0,
'prefer-promise-reject-errors': 0,
'react/no-array-index-key': 0,
+ 'react/sort-comp': 0,
},
};
diff --git a/README.md b/README.md
index 3abe4b85..ca6c1043 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ open http://localhost:9001/
```js
import Form, { Field } from 'rc-field-form';
- {
console.log('Finish:', values);
}}
@@ -50,7 +50,7 @@ import Form, { Field } from 'rc-field-form';
-;
+;
export default Demo;
```
diff --git a/examples/StateForm-basic.tsx b/examples/StateForm-basic.tsx
index 9835622a..96b5cd27 100644
--- a/examples/StateForm-basic.tsx
+++ b/examples/StateForm-basic.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import StateForm, { Field } from '../src/';
+import Form, { Field } from '../src/';
import Input from './components/Input';
@@ -12,7 +12,7 @@ export default class Demo extends React.Component {
return (
State Form ({list.length} inputs)
-
+
+
);
}
diff --git a/examples/StateForm-context.tsx b/examples/StateForm-context.tsx
index 389a20cc..658a4be3 100644
--- a/examples/StateForm-context.tsx
+++ b/examples/StateForm-context.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import StateForm, { FormProvider } from '../src';
+import Form, { FormProvider } from '../src';
import Input from './components/Input';
import LabelField from './components/LabelField';
import { ValidateMessages } from '../src/interface';
@@ -16,10 +16,10 @@ const formStyle: React.CSSProperties = {
};
const Form1 = () => {
- const [form] = StateForm.useForm();
+ const [form] = Form.useForm();
return (
-
+
+
);
};
const Form2 = () => {
- const [form] = StateForm.useForm();
+ const [form] = Form.useForm();
return (
-
+
+
);
};
diff --git a/examples/StateForm-layout.tsx b/examples/StateForm-layout.tsx
index c4c84beb..334ed92e 100644
--- a/examples/StateForm-layout.tsx
+++ b/examples/StateForm-layout.tsx
@@ -1,7 +1,7 @@
/* eslint-disable jsx-a11y/label-has-associated-control, react/prop-types */
import React from 'react';
-import StateForm from '../src';
+import Form from '../src';
import Input from './components/Input';
import LabelField from './components/LabelField';
@@ -14,7 +14,7 @@ export default class Demo extends React.Component {
return (
State Form ({list.length} inputs)
-
+
+
);
}
diff --git a/examples/StateForm-list.tsx b/examples/StateForm-list.tsx
index beea04e5..5c02ce14 100644
--- a/examples/StateForm-list.tsx
+++ b/examples/StateForm-list.tsx
@@ -1,11 +1,11 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import StateForm from '../src/';
+import Form from '../src/';
import Input from './components/Input';
import LabelField from './components/LabelField';
-const { List, useForm } = StateForm;
+const { List, useForm } = Form;
const Demo = () => {
const [form] = useForm();
@@ -15,7 +15,7 @@ const Demo = () => {
List of Form
You can set Field as List
- {
console.log('values:', values);
@@ -62,7 +62,7 @@ const Demo = () => {
);
}}
-
+
Out Of Form
diff --git a/examples/StateForm-redux.tsx b/examples/StateForm-redux.tsx
index f3eac655..3c46c5fc 100644
--- a/examples/StateForm-redux.tsx
+++ b/examples/StateForm-redux.tsx
@@ -3,7 +3,7 @@
import React from 'react';
import { connect, Provider } from 'react-redux';
import { createStore } from 'redux';
-import StateForm from '../src/';
+import Form from '../src/';
import Input from './components/Input';
import LabelField from './components/LabelField';
@@ -22,7 +22,7 @@ let App: any = ({ dispatch, fields }) => {
console.log('=>', fields);
return (
-
{
console.log('Value Change:', changedValues, allValues);
@@ -42,8 +42,8 @@ let App: any = ({ dispatch, fields }) => {
-
-
+
+
-
+
);
};
App = connect((fields: any) => ({ fields }))(App);
diff --git a/examples/StateForm-renderProps.tsx b/examples/StateForm-renderProps.tsx
index ca192011..cf911558 100644
--- a/examples/StateForm-renderProps.tsx
+++ b/examples/StateForm-renderProps.tsx
@@ -1,9 +1,9 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import React from 'react';
-import StateForm from '../src';
+import Form from '../src';
import Input from './components/Input';
-const { Field } = StateForm;
+const { Field } = Form;
const list = new Array(1111).fill(() => undefined);
@@ -15,7 +15,7 @@ export default class Demo extends React.Component {
Render Props ({list.length} inputs)
Render Props is easy to use but bad performance
-
+
+
);
}
diff --git a/examples/StateForm-reset.tsx b/examples/StateForm-reset.tsx
index 4db55c1b..32b41660 100644
--- a/examples/StateForm-reset.tsx
+++ b/examples/StateForm-reset.tsx
@@ -1,9 +1,9 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import StateForm from '../src';
+import Form from '../src';
import Input from './components/Input';
-const { Field } = StateForm;
+const { Field } = Form;
function Item({ children, ...restProps }) {
return (
@@ -24,11 +24,11 @@ function Item({ children, ...restProps }) {
}
const Demo = () => {
- const [form] = StateForm.useForm();
+ const [form] = Form.useForm();
return (
Reset / Set Form
-
+
+
);
};
diff --git a/examples/StateForm-useForm.tsx b/examples/StateForm-useForm.tsx
index c2740df4..b66ed8c5 100644
--- a/examples/StateForm-useForm.tsx
+++ b/examples/StateForm-useForm.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import StateForm from '../src';
+import Form from '../src';
import Input from './components/Input';
-const { Field, useForm } = StateForm;
+const { Field, useForm } = Form;
const list = new Array(0).fill(() => undefined);
@@ -22,7 +22,7 @@ export default () => {
Fill Values
-
+
+
);
};
diff --git a/examples/StateForm-validate-perf.tsx b/examples/StateForm-validate-perf.tsx
index a2485d76..0523f493 100644
--- a/examples/StateForm-validate-perf.tsx
+++ b/examples/StateForm-validate-perf.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import StateForm, { FormInstance } from '../src/';
+import Form, { FormInstance } from '../src/';
import Input from './components/Input';
import LabelField from './components/LabelField';
import { ValidateMessages } from '../src/interface';
@@ -34,7 +34,7 @@ export default class Demo extends React.Component {
return (
High Perf Validate Form
-
Reset
-
+
);
}
diff --git a/examples/StateForm-validate.tsx b/examples/StateForm-validate.tsx
index 253efa53..2302bac9 100644
--- a/examples/StateForm-validate.tsx
+++ b/examples/StateForm-validate.tsx
@@ -1,10 +1,10 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import StateForm from '../src';
+import Form from '../src';
import Input from './components/Input';
-const { Field } = StateForm;
+const { Field } = Form;
const Error = ({ children }) => (
@@ -35,7 +35,7 @@ export default class Demo extends React.Component {
return (
Validate Form
-
+
+
);
}
diff --git a/examples/components/LabelField.tsx b/examples/components/LabelField.tsx
index 41d753ea..13451c57 100644
--- a/examples/components/LabelField.tsx
+++ b/examples/components/LabelField.tsx
@@ -1,8 +1,8 @@
import * as React from 'react';
-import StateForm, { FormInstance } from '../../src/';
+import Form, { FormInstance } from '../../src/';
import { FieldProps } from '../../src/Field';
-const { Field } = StateForm;
+const { Field } = Form;
const Error = ({ children }) => (
diff --git a/src/Field.tsx b/src/Field.tsx
index 9b639176..7583d0a9 100644
--- a/src/Field.tsx
+++ b/src/Field.tsx
@@ -69,6 +69,8 @@ class Field extends React.Component implements FieldEnti
private cancelRegisterFunc: () => void | null = null;
+ private destroy: boolean = false;
+
/**
* Follow state should not management in State since it will async update by React.
* This makes first render of form can not get correct state value.
@@ -91,6 +93,7 @@ class Field extends React.Component implements FieldEnti
public componentWillUnmount() {
this.cancelRegister();
+ this.destroy = true;
}
public cancelRegister = () => {
@@ -121,7 +124,14 @@ class Field extends React.Component implements FieldEnti
);
};
+ public reRender() {
+ if (this.destroy) return;
+ this.forceUpdate();
+ }
+
public refresh = () => {
+ if (this.destroy) return;
+
/**
* We update `reset` state twice to clean up current node.
* Which helps to reset value without define the type.
@@ -181,7 +191,7 @@ class Field extends React.Component implements FieldEnti
const validating = this.isFieldValidating();
if (this.prevValidating !== validating || !isSimilar(this.prevErrors, errors)) {
- this.forceUpdate();
+ this.reRender();
}
break;
}
@@ -195,7 +205,7 @@ class Field extends React.Component implements FieldEnti
(namePathList && containsNamePath(namePathList, namePath)) ||
dependencyList.some(dependency => containsNamePath(info.relatedFields, dependency))
) {
- this.forceUpdate();
+ this.reRender();
}
break;
}
@@ -214,7 +224,7 @@ class Field extends React.Component implements FieldEnti
) ||
(shouldUpdate ? shouldUpdate(prevStore, values, info) : prevValue !== curValue)
) {
- this.forceUpdate();
+ this.reRender();
}
break;
}
@@ -318,6 +328,9 @@ class Field extends React.Component implements FieldEnti
// Add trigger
control[trigger] = (...args: any[]) => {
+ // Mark as touched
+ this.touched = true;
+
let newValue = (getValueFromEvent || defaultGetValueFromEvent)(...args);
if (normalize) {
@@ -330,9 +343,6 @@ class Field extends React.Component implements FieldEnti
value: newValue,
});
- // Mark as touched
- this.touched = true;
-
if (originTriggerFunc) {
originTriggerFunc(...args);
}
diff --git a/src/FieldContext.ts b/src/FieldContext.ts
index 9f0819b0..11ccc319 100644
--- a/src/FieldContext.ts
+++ b/src/FieldContext.ts
@@ -1,10 +1,11 @@
import * as React from 'react';
+import warning from 'warning';
import { InternalFormInstance } from './interface';
export const HOOK_MARK = 'RC_FORM_INTERNAL_HOOKS';
const warningFunc: any = () => {
- throw new Error('StateForm is not defined.');
+ warning(false, 'Can not find FormContext. Please make sure you wrap Field under Form.');
};
const Context = React.createContext({
@@ -21,7 +22,19 @@ const Context = React.createContext({
setFieldsValue: warningFunc,
validateFields: warningFunc,
- getInternalHooks: warningFunc,
+ getInternalHooks: () => {
+ warningFunc();
+
+ return {
+ dispatch: warningFunc,
+ registerField: warningFunc,
+ useSubscribe: warningFunc,
+ setInitialValues: warningFunc,
+ setCallbacks: warningFunc,
+ getFields: warningFunc,
+ setValidateMessages: warningFunc,
+ };
+ },
});
export default Context;
diff --git a/src/Form.tsx b/src/Form.tsx
index 7ddf42c8..053dc81d 100644
--- a/src/Form.tsx
+++ b/src/Form.tsx
@@ -13,7 +13,7 @@ import FormContext, { FormContextProps } from './FormContext';
type BaseFormProps = Omit, 'onSubmit'>;
-export interface StateFormProps extends BaseFormProps {
+export interface FormProps extends BaseFormProps {
initialValues?: Store;
form?: FormInstance;
children?: (() => JSX.Element | React.ReactNode) | React.ReactNode;
@@ -25,7 +25,7 @@ export interface StateFormProps extends BaseFormProps {
onFinish?: (values: Store) => void;
}
-const StateForm: React.FunctionComponent = (
+const Form: React.FunctionComponent = (
{
name,
initialValues,
@@ -37,7 +37,7 @@ const StateForm: React.FunctionComponent = (
onFieldsChange,
onFinish,
...restProps
- }: StateFormProps,
+ }: FormProps,
ref,
) => {
const formContext: FormContextProps = React.useContext(FormContext);
@@ -128,4 +128,4 @@ const StateForm: React.FunctionComponent = (
);
};
-export default StateForm;
+export default Form;
diff --git a/src/List.tsx b/src/List.tsx
index 153f335f..35ffdbc7 100644
--- a/src/List.tsx
+++ b/src/List.tsx
@@ -1,11 +1,13 @@
import * as React from 'react';
-import { FormInstance, InternalNamePath, NamePath, InternalFormInstance } from './interface';
+import warning from 'warning';
+import { InternalNamePath, NamePath, InternalFormInstance } from './interface';
import FieldContext, { HOOK_MARK } from './FieldContext';
import Field from './Field';
import { getNamePath, setValue } from './utils/valueUtil';
interface ListField {
name: number;
+ key: number;
}
interface ListOperations {
@@ -26,6 +28,7 @@ interface ListRenderProps {
const List: React.FunctionComponent = ({ name, children }) => {
// User should not pass `children` as other type.
if (typeof children !== 'function') {
+ warning(false, 'Form.List only accepts function as children.');
return null;
}
@@ -75,7 +78,15 @@ const List: React.FunctionComponent = ({ name, children }) => {
nextValue.splice(index, 1);
setFieldsValue(setValue({}, prefixName, []));
- setFields(fields);
+
+ // Set value back.
+ // We should add current list name also to let it re-render
+ setFields([
+ ...fields,
+ {
+ name: prefixName,
+ },
+ ]);
},
};
@@ -83,6 +94,7 @@ const List: React.FunctionComponent = ({ name, children }) => {
value.map(
(__, index): ListField => ({
name: index,
+ key: index,
}),
),
operations,
diff --git a/src/index.tsx b/src/index.tsx
index 5ec866c6..a2f10335 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -3,26 +3,26 @@ import { FormInstance } from './interface';
import Field from './Field';
import List from './List';
import useForm from './useForm';
-import FieldForm, { StateFormProps } from './Form';
+import FieldForm, { FormProps } from './Form';
import { FormProvider } from './FormContext';
-const InternalStateForm = React.forwardRef(FieldForm);
+const InternalForm = React.forwardRef(FieldForm);
-type InternalStateForm = typeof InternalStateForm;
-interface RefStateForm extends InternalStateForm {
+type InternalForm = typeof InternalForm;
+interface RefForm extends InternalForm {
FormProvider: typeof FormProvider;
Field: typeof Field;
List: typeof List;
useForm: typeof useForm;
}
-const RefStateForm: RefStateForm = InternalStateForm as any;
+const RefForm: RefForm = InternalForm as any;
-RefStateForm.FormProvider = FormProvider;
-RefStateForm.Field = Field;
-RefStateForm.List = List;
-RefStateForm.useForm = useForm;
+RefForm.FormProvider = FormProvider;
+RefForm.Field = Field;
+RefForm.List = List;
+RefForm.useForm = useForm;
-export { FormInstance, Field, useForm, FormProvider };
+export { FormInstance, Field, List, useForm, FormProvider };
-export default RefStateForm;
+export default RefForm;
diff --git a/src/useForm.ts b/src/useForm.ts
index 19311d32..d31bd28f 100644
--- a/src/useForm.ts
+++ b/src/useForm.ts
@@ -139,7 +139,7 @@ export class FormStore {
return this.store;
}
- return nameList.map((name: NamePath) => this.getFieldValue(name));
+ return cloneByNamePathList(this.store, nameList.map(getNamePath));
};
private getFieldValue = (name: NamePath) => {
diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts
index 01f4efe2..9172a745 100644
--- a/src/utils/valueUtil.ts
+++ b/src/utils/valueUtil.ts
@@ -14,11 +14,8 @@ export function getNamePath(path: NamePath | null): (string | number)[] {
return toArray(path);
}
-export function getValue(store: Store, namePath: InternalNamePath, defaultValues?: Store) {
+export function getValue(store: Store, namePath: InternalNamePath) {
const value = get(store, namePath);
- if (value === undefined && defaultValues) {
- return get(defaultValues, namePath);
- }
return value;
}
@@ -31,7 +28,7 @@ export function cloneByNamePathList(store: any, namePathList: InternalNamePath[]
let newStore = {};
namePathList.forEach(namePath => {
const value = getValue(store, namePath);
- newStore = setValue(store, namePath, value);
+ newStore = setValue(newStore, namePath, value);
});
return newStore;
diff --git a/tests/context.test.js b/tests/context.test.js
index 7fd0f007..cae66aae 100644
--- a/tests/context.test.js
+++ b/tests/context.test.js
@@ -2,7 +2,7 @@ import React from 'react';
import { mount } from 'enzyme';
import Form, { FormProvider } from '../src';
import InfoField from './common/InfoField';
-import { changeValue, matchError } from './common';
+import { changeValue, matchError, getField } from './common';
describe('context', () => {
it('validateMessages', async () => {
@@ -17,4 +17,63 @@ describe('context', () => {
await changeValue(wrapper, '');
matchError(wrapper, "I'm global");
});
+
+ it('change event', async () => {
+ const onFormChange = jest.fn();
+
+ const wrapper = mount(
+
+
+ ,
+ );
+
+ await changeValue(getField(wrapper), 'Light');
+ expect(onFormChange).toHaveBeenCalledWith(
+ 'form1',
+ expect.objectContaining({
+ changedFields: [
+ { errors: [], name: ['username'], touched: true, validating: false, value: 'Light' },
+ ],
+ forms: {
+ form1: expect.objectContaining({}),
+ },
+ }),
+ );
+ });
+
+ it('adjust sub form', async () => {
+ const onFormChange = jest.fn();
+
+ const wrapper = mount(
+
+
+ ,
+ );
+
+ wrapper.setProps({
+ children: (
+
+ ),
+ });
+
+ await changeValue(getField(wrapper), 'Bamboo');
+ const { forms } = onFormChange.mock.calls[0][1];
+ expect(Object.keys(forms)).toEqual(['form2']);
+ });
+
+ it('do nothing if no Provider in use', () => {
+ const wrapper = mount(
+
+
+
,
+ );
+
+ wrapper.setProps({
+ children: null,
+ });
+ });
});
diff --git a/tests/control.test.js b/tests/control.test.js
new file mode 100644
index 00000000..21e245c7
--- /dev/null
+++ b/tests/control.test.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import Form from '../src';
+import InfoField from './common/InfoField';
+
+describe('Control', () => {
+ it('fields', () => {
+ const wrapper = mount(
+ ,
+ );
+
+ wrapper.setProps({
+ fields: [{ name: 'username', value: 'Bamboo' }],
+ });
+
+ expect(wrapper.find('input').props().value).toEqual('Bamboo');
+ });
+});
diff --git a/tests/index.test.js b/tests/index.test.js
index 5d8e3c61..68a6d63c 100644
--- a/tests/index.test.js
+++ b/tests/index.test.js
@@ -1,8 +1,9 @@
import React from 'react';
import { mount } from 'enzyme';
-import Form, { Field } from '../src';
+import Form, { Field, useForm } from '../src';
import InfoField, { Input } from './common/InfoField';
-import { changeValue, getField } from './common';
+import { changeValue, getField, matchError } from './common';
+import timeout from './common/timeout';
describe('Basic', () => {
describe('create form', () => {
@@ -38,6 +39,30 @@ describe('Basic', () => {
});
});
+ it('fields touched', async () => {
+ let form;
+
+ const wrapper = mount(
+
+
+
,
+ );
+
+ expect(form.isFieldsTouched()).toBeFalsy();
+ expect(form.isFieldsTouched(['username', 'password'])).toBeFalsy();
+
+ await changeValue(getField(wrapper, 0), 'Bamboo');
+ expect(form.isFieldsTouched()).toBeTruthy();
+ expect(form.isFieldsTouched(['username', 'password'])).toBeTruthy();
+ });
+
describe('reset form', () => {
function resetTest(name, ...args) {
it(name, async () => {
@@ -111,6 +136,20 @@ describe('Basic', () => {
path2: 'Bamboo',
},
});
+ expect(form.getFieldsValue(['username'])).toEqual({
+ username: 'Light',
+ });
+ expect(form.getFieldsValue(['path1'])).toEqual({
+ path1: {
+ path2: 'Bamboo',
+ },
+ });
+ expect(form.getFieldsValue(['username', ['path1', 'path2']])).toEqual({
+ username: 'Light',
+ path1: {
+ path2: 'Bamboo',
+ },
+ });
expect(
getField(wrapper, 'username')
.find('input')
@@ -174,4 +213,86 @@ describe('Basic', () => {
).toEqual('Light');
});
});
+
+ it('should throw if no Form in use', () => {
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ mount(
+
+
+ ,
+ );
+
+ expect(errorSpy).toHaveBeenCalledWith(
+ 'Warning: Can not find FormContext. Please make sure you wrap Field under Form.',
+ );
+
+ errorSpy.mockRestore();
+ });
+
+ it('keep origin input function', async () => {
+ const onChange = jest.fn();
+ const onValuesChange = jest.fn();
+ const wrapper = mount(
+ ,
+ );
+
+ await changeValue(getField(wrapper), 'Bamboo');
+ expect(onValuesChange).toHaveBeenCalledWith({ username: 'Bamboo' }, { username: 'Bamboo' });
+ expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 'Bamboo' } }));
+ });
+
+ it('submit', async () => {
+ const onFinish = jest.fn();
+
+ const wrapper = mount(
+ ,
+ );
+
+ // Not trigger
+ wrapper.find('button').simulate('submit');
+ await timeout();
+ wrapper.update();
+ matchError(wrapper, "'user' is required");
+ expect(onFinish).not.toHaveBeenCalled();
+
+ // Trigger
+ await changeValue(getField(wrapper), 'Bamboo');
+ wrapper.find('button').simulate('submit');
+ await timeout();
+ matchError(wrapper, false);
+ expect(onFinish).toHaveBeenCalledWith({ user: 'Bamboo' });
+ });
+
+ it('getInternalHooks should not usable by user', () => {
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ let form;
+ mount(
+
+
,
+ );
+
+ expect(form.getInternalHooks()).toEqual(null);
+
+ expect(errorSpy).toHaveBeenCalledWith(
+ 'Warning: `getInternalHooks` is internal usage. Should not call directly.',
+ );
+
+ errorSpy.mockRestore();
+ });
});
diff --git a/tests/list.test.js b/tests/list.test.js
new file mode 100644
index 00000000..08b34419
--- /dev/null
+++ b/tests/list.test.js
@@ -0,0 +1,132 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { mount } from 'enzyme';
+import Form, { Field, List } from '../src';
+import InfoField, { Input } from './common/InfoField';
+import { changeValue, matchError, getField } from './common';
+import timeout from './common/timeout';
+
+describe('list', () => {
+ let form;
+
+ function generateForm(renderList, formProps) {
+ const wrapper = mount(
+
+
+
,
+ );
+
+ return [wrapper, () => getField(wrapper).find('div')];
+ }
+
+ it('basic', async () => {
+ const [, getList] = generateForm(
+ fields => (
+
+ {fields.map(field => (
+
+
+
+ ))}
+
+ ),
+ {
+ initialValues: {
+ list: ['', '', ''],
+ },
+ },
+ );
+
+ const listNode = getList();
+
+ await changeValue(getField(listNode, 0), '111');
+ await changeValue(getField(listNode, 1), '222');
+ await changeValue(getField(listNode, 2), '333');
+
+ expect(form.getFieldsValue()).toEqual({
+ list: ['111', '222', '333'],
+ });
+ });
+
+ it('operation', async () => {
+ let operation;
+ const [wrapper, getList] = generateForm((fields, opt) => {
+ operation = opt;
+ return (
+
+ {fields.map(field => (
+
+
+
+ ))}
+
+ );
+ });
+
+ // Add
+ operation.add();
+ operation.add();
+ operation.add();
+ wrapper.update();
+ expect(getList().find(Field).length).toEqual(3);
+
+ // Modify
+ await changeValue(getField(getList(), 1), '222');
+ expect(form.getFieldsValue()).toEqual({
+ list: [undefined, '222', undefined],
+ });
+ expect(form.isFieldTouched(['list', 0])).toBeFalsy();
+ expect(form.isFieldTouched(['list', 1])).toBeTruthy();
+ expect(form.isFieldTouched(['list', 2])).toBeFalsy();
+
+ // Remove
+ act(() => {
+ operation.remove(1);
+ });
+ wrapper.update();
+ expect(getList().find(Field).length).toEqual(2);
+ expect(form.getFieldsValue()).toEqual({
+ list: [undefined, undefined],
+ });
+ expect(form.isFieldTouched(['list', 0])).toBeFalsy();
+ expect(form.isFieldTouched(['list', 2])).toBeFalsy();
+ });
+
+ it('validate', async () => {
+ const [, getList] = generateForm(
+ fields => (
+
+ {fields.map(field => (
+
+
+
+ ))}
+
+ ),
+ {
+ initialValues: { list: [''] },
+ },
+ );
+
+ await changeValue(getField(getList()), '');
+
+ expect(form.getFieldError(['list', 0])).toEqual(["'list.0' is required"]);
+ });
+
+ it('warning if children is not function', () => {
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ generateForm();
+
+ expect(errorSpy).toHaveBeenCalledWith('Warning: Form.List only accepts function as children.');
+
+ errorSpy.mockRestore();
+ });
+});
diff --git a/tests/utils.test.js b/tests/utils.test.js
new file mode 100644
index 00000000..82232e4e
--- /dev/null
+++ b/tests/utils.test.js
@@ -0,0 +1,44 @@
+import { isSimilar, setValues } from '../src/utils/valueUtil';
+import NameMap from '../src/utils/NameMap';
+
+describe('utils', () => {
+ describe('valueUtil', () => {
+ it('isSimilar', () => {
+ expect(isSimilar(1, 2)).toBeFalsy();
+ expect(isSimilar({}, {})).toBeTruthy();
+ expect(isSimilar({ a: 1 }, { a: 2 })).toBeFalsy();
+ expect(isSimilar({ a() {} }, { a() {} })).toBeTruthy();
+ expect(isSimilar({ a: 1 }, {})).toBeFalsy();
+ expect(isSimilar({}, { a: 1 })).toBeFalsy();
+ expect(isSimilar({}, null)).toBeFalsy();
+ expect(isSimilar(null, {})).toBeFalsy();
+ });
+
+ it('setValues', () => {
+ expect(setValues({}, { a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
+ expect(setValues([], [123])).toEqual([123]);
+ });
+ });
+
+ describe('NameMap', () => {
+ it('update should clean if empty', () => {
+ const map = new NameMap();
+ map.set(['user', 'name'], 'Bamboo');
+ map.set(['user', 'age'], 14);
+
+ expect(map.toJSON()).toEqual({
+ 'user.name': 'Bamboo',
+ 'user.age': 14,
+ });
+
+ map.update(['user', 'age'], prevValue => {
+ expect(prevValue).toEqual(14);
+ return null;
+ });
+
+ expect(map.toJSON()).toEqual({
+ 'user.name': 'Bamboo',
+ });
+ });
+ });
+});
diff --git a/tests/validate.test.js b/tests/validate.test.js
index 2cc351cd..ebb81190 100644
--- a/tests/validate.test.js
+++ b/tests/validate.test.js
@@ -8,14 +8,28 @@ import timeout from './common/timeout';
describe('validate', () => {
it('required', async () => {
+ let form;
const wrapper = mount(
- ,
+
+
+
,
);
await changeValue(wrapper, '');
matchError(wrapper, true);
+ expect(form.getFieldError('username')).toEqual(["'username' is required"]);
+ expect(form.getFieldsError()).toEqual([
+ {
+ name: ['username'],
+ errors: ["'username' is required"],
+ },
+ ]);
});
describe('validateMessages', () => {