From 3f197ec46d54bd93de6ad15fbac3eb33ecae6dea Mon Sep 17 00:00:00 2001 From: Benjy Cui Date: Thu, 2 Nov 2017 10:47:29 +0800 Subject: [PATCH 1/7] feat: remove option.exclusive --- README.md | 1 - examples/radio-group.js | 6 +-- src/createBaseForm.js | 68 ++++++++++++---------------------- src/createFieldsStore.js | 53 +++++---------------------- src/utils.js | 73 +++---------------------------------- tests/checkboxGroup.spec.js | 5 +-- tests/overview.spec.js | 8 ++-- tests/radioGroup.spec.js | 8 ++-- 8 files changed, 48 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index b3d128e0..b18e52ee 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,6 @@ This input's unique name. | option.validate | | Object[] | - | | option.validate[n].trigger | Event which is listened to validate. Set to falsy to only validate when call props.validateFields. | String|String[] | 'onChange' | | option.validate[n].rules | Validator rules. see: [async-validator](https://github.com/yiminghe/async-validator) | Object[] | - | -| option.exclusive(deprecated) | Whether set value exclusively. Used with radio. | boolean | false | ##### Default value of `getValueFromEvent` diff --git a/examples/radio-group.js b/examples/radio-group.js index ada4a305..0a2f8d77 100644 --- a/examples/radio-group.js +++ b/examples/radio-group.js @@ -31,9 +31,6 @@ class Form extends Component { render() { const { form } = this.props; const { getFieldProps } = form; - getFieldProps('normal', { - initialValue: 'b', - }); return (

radio-group

@@ -45,7 +42,7 @@ class Form extends Component { set(valuesAllSet, key, valuesAll[key])); onValuesChange(this.props, set({}, name, value), valuesAllSet); } - const nameKeyObj = getNameIfNested(name); - if (this.fieldsStore.getFieldMeta(nameKeyObj.name).exclusive) { - name = nameKeyObj.name; - } const field = this.fieldsStore.getField(name); return ({ name, field: { ...field, value, touched: true }, fieldMeta }); }, @@ -164,14 +163,15 @@ function createBaseForm(option = {}, mixins = []) { if (!name) { throw new Error('Must call `getFieldProps` with valid name string!'); } + warning( + !('exclusive' in usersFieldOption), + '`option.exclusive` of `getFieldProps`|`getFieldDecorator` had been remove.' + ); - const nameIfNested = getNameIfNested(name); - const leadingName = nameIfNested.name; const fieldOption = { valuePropName: 'value', validate: [], trigger: DEFAULT_TRIGGER, - leadingName, name, ...usersFieldOption, }; @@ -180,7 +180,6 @@ function createBaseForm(option = {}, mixins = []) { rules, trigger, validateTrigger = trigger, - exclusive, validate, } = fieldOption; @@ -189,15 +188,6 @@ function createBaseForm(option = {}, mixins = []) { fieldMeta.initialValue = fieldOption.initialValue; } - const leadingFieldMeta = this.fieldsStore.getFieldMeta(leadingName); - if (nameIfNested.isNested) { - leadingFieldMeta.virtual = !exclusive; - // exclusive allow getFieldProps('x', {initialValue}) - // non-exclusive does not allow getFieldProps('x', {initialValue}) - leadingFieldMeta.hidden = !exclusive; - leadingFieldMeta.exclusive = exclusive; - } - const inputProps = { ...this.fieldsStore.getFieldValuePropValue(fieldOption), ref: this.getCacheBind(name, `${name}__ref`, this.saveRef), @@ -273,22 +263,10 @@ function createBaseForm(option = {}, mixins = []) { onValuesChange(this.props, fieldsValue); } const newFields = {}; - const { fieldsMeta, fields } = this.fieldsStore; - const virtualPaths = getVirtualPaths(fieldsMeta); + const { fieldsMeta } = this.fieldsStore; Object.keys(fieldsValue).forEach((name) => { const value = fieldsValue[name]; - if (fieldsMeta[name] && fieldsMeta[name].virtual) { - clearVirtualField(name, fields, fieldsMeta); - for (let i = 0, len = virtualPaths[name].length; i < len; i++) { - const path = virtualPaths[name][i]; - if (has(fieldsValue, path)) { - newFields[path] = { - name: path, - value: get(fieldsValue, path), - }; - } - } - } else if (fieldsMeta[name]) { + if (fieldsMeta[name]) { newFields[name] = { name, value, @@ -360,7 +338,7 @@ function createBaseForm(option = {}, mixins = []) { }); if (callback && isEmptyObject(allFields)) { callback(isEmptyObject(alreadyErrors) ? null : alreadyErrors, - this.fieldsStore.getFieldsValue(flatFieldNames(fieldNames))); + this.fieldsStore.getFieldsValue(fieldNames)); return; } const validator = new AsyncValidator(allRules); @@ -415,7 +393,7 @@ function createBaseForm(option = {}, mixins = []) { } callback(isEmptyObject(errorsGroup) ? null : errorsGroup, - this.fieldsStore.getFieldsValue(flatFieldNames(fieldNames))); + this.fieldsStore.getFieldsValue(fieldNames)); } }); }, @@ -434,7 +412,7 @@ function createBaseForm(option = {}, mixins = []) { }); if (!fields.length) { if (callback) { - callback(null, this.fieldsStore.getFieldsValue(flatFieldNames(fieldNames))); + callback(null, this.fieldsStore.getFieldsValue(fieldNames)); } return; } diff --git a/src/createFieldsStore.js b/src/createFieldsStore.js index 4826360f..0138b2ea 100644 --- a/src/createFieldsStore.js +++ b/src/createFieldsStore.js @@ -1,11 +1,6 @@ -import get from 'lodash/get'; -import has from 'lodash/has'; import set from 'lodash/set'; import { - flatFieldNames, getErrorStrs, - getNameIfNested, - getVirtualPaths, } from './utils'; const atom = {}; @@ -26,13 +21,8 @@ class FieldsStore { ...fields, }; const nowValues = {}; - Object.keys(fieldsMeta).forEach((f) => { - const { name, isNested } = getNameIfNested(f); - if (isNested && fieldsMeta[name].exclusive) { - return; - } - nowValues[f] = this.getValueFromFields(f, nowFields); - }); + Object.keys(fieldsMeta) + .forEach((f) => nowValues[f] = this.getValueFromFields(f, nowFields)); Object.keys(nowValues).forEach((f) => { const value = nowValues[f]; const fieldMeta = fieldsMeta[f]; @@ -78,17 +68,6 @@ class FieldsStore { return fieldMeta && fieldMeta.initialValue; } getValueFromFields(name, fields) { - const { fieldsMeta } = this; - if (fieldsMeta[name] && fieldsMeta[name].virtual) { - const ret = {}; - Object.keys(fieldsMeta).forEach(fieldKey => { - const nameIfNested = getNameIfNested(fieldKey); - if (nameIfNested.name === name && nameIfNested.isNested) { - set(ret, fieldKey, this.getValueFromFieldsInternal(fieldKey, fields)); - } - }); - return ret[name]; - } return this.getValueFromFieldsInternal(name, fields); } getValueFromFieldsAll = () => { @@ -102,21 +81,18 @@ class FieldsStore { getValidFieldsName() { const fieldsMeta = this.fieldsMeta; - return fieldsMeta ? - Object.keys(fieldsMeta).filter(name => !fieldsMeta[name].hidden) : - []; + return fieldsMeta ? Object.keys(fieldsMeta) : []; } getFieldValuePropValue(fieldMeta) { - const { exclusive, leadingName, name, getValueProps, valuePropName } = fieldMeta; - const { fieldsMeta } = this; - const field = exclusive ? this.getField(leadingName) : this.getField(name); + const { name, getValueProps, valuePropName } = fieldMeta; + const field = this.getField(name); let fieldValue = atom; if (field && 'value' in field) { fieldValue = field.value; } if (fieldValue === atom) { - fieldValue = exclusive ? fieldsMeta[leadingName].initialValue : fieldMeta.initialValue; + fieldValue = fieldMeta.initialValue; } if (getValueProps) { return getValueProps(fieldValue); @@ -142,7 +118,7 @@ class FieldsStore { } getFieldsValue = (names) => { - const fields = names || flatFieldNames(this.getValidFieldsName()); + const fields = names || this.getValidFieldsName(); const allValues = {}; fields.forEach((f) => { set(allValues, f, this.getFieldValue(f)); @@ -156,7 +132,7 @@ class FieldsStore { } getFieldsError = (names) => { - const fields = names || flatFieldNames(this.getValidFieldsName()); + const fields = names || this.getValidFieldsName(); const allErrors = {}; fields.forEach((f) => { set(allErrors, f, this.getFieldError(f)); @@ -179,19 +155,8 @@ class FieldsStore { setFieldsInitialValue = (initialValues) => { const fieldsMeta = this.fieldsMeta; - const virtualPaths = getVirtualPaths(fieldsMeta); Object.keys(initialValues).forEach(name => { - if (fieldsMeta[name] && fieldsMeta[name].virtual) { - for (let i = 0, len = virtualPaths[name].length; i < len; i++) { - const path = virtualPaths[name][i]; - if (has(initialValues, path)) { - fieldsMeta[path] = { - ...fieldsMeta[path], - initialValue: get(initialValues, path), - }; - } - } - } else if (fieldsMeta[name]) { + if (fieldsMeta[name]) { fieldsMeta[name] = { ...fieldsMeta[name], initialValue: initialValues[name], diff --git a/src/utils.js b/src/utils.js index 51e12d2e..10d8c00d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -11,8 +11,12 @@ export function argumentContainer(Container, WrappedComponent) { return hoistStatics(Container, WrappedComponent); } +export function mirror(obj) { + return obj; +} + export function getValueFromEvent(e) { - // support custom element + // To support custom element if (!e || !e.target) { return e; } @@ -40,14 +44,10 @@ export function flattenArray(arr) { return Array.prototype.concat.apply([], arr); } -export function mirror(obj) { - return obj; -} - export function hasRules(validate) { if (validate) { return validate.some((item) => { - return !!item.rules && item.rules.length; + return item.rules && item.rules.length; }); } return false; @@ -86,67 +86,6 @@ export function getParams(ns, opt, cb) { }; } -const NAME_KEY_SEP = '.'; -const NAME_INDEX_OPEN_SEP = '['; - -export function getNameIfNested(str) { - const keyIndex = str.indexOf(NAME_KEY_SEP); - const arrayIndex = str.indexOf(NAME_INDEX_OPEN_SEP); - - let index; - - if (keyIndex === -1 && arrayIndex === -1) { - return { - name: str, - }; - } else if (keyIndex === -1) { - index = arrayIndex; - } else if (arrayIndex === -1) { - index = keyIndex; - } else { - index = Math.min(keyIndex, arrayIndex); - } - - return { - name: str.slice(0, index), - isNested: true, - }; -} - -export function flatFieldNames(names) { - const ret = {}; - names.forEach((n) => { - ret[getNameIfNested(n).name] = 1; - }); - return Object.keys(ret); -} - -export function clearVirtualField(name, fields, fieldsMeta) { - if (fieldsMeta[name] && fieldsMeta[name].virtual) { - /* eslint no-loop-func:0 */ - Object.keys(fields).forEach((ok) => { - if (getNameIfNested(ok).name === name) { - delete fields[ok]; - } - }); - } -} - -export function getVirtualPaths(fieldsMeta) { - const virtualPaths = {}; - Object.keys(fieldsMeta).forEach((name) => { - const leadingName = fieldsMeta[name].leadingName; - if (leadingName && fieldsMeta[leadingName].virtual) { - if (leadingName in virtualPaths) { - virtualPaths[leadingName].push(name); - } else { - virtualPaths[leadingName] = [name]; - } - } - }); - return virtualPaths; -} - export function normalizeValidateRules(validate, rules, validateTrigger) { const validateRules = validate.map((item) => { const newItem = { diff --git a/tests/checkboxGroup.spec.js b/tests/checkboxGroup.spec.js index 7ab135bb..807e3d05 100644 --- a/tests/checkboxGroup.spec.js +++ b/tests/checkboxGroup.spec.js @@ -1,5 +1,4 @@ /* eslint-disable no-undef, react/prop-types */ - import React from 'react'; import ReactDOM from 'react-dom'; import { Simulate } from 'react-dom/test-utils'; @@ -66,7 +65,7 @@ describe('checkbox-group usage', () => { document.body.removeChild(container); }); - it('collect value', () => { + it.skip('collect value', () => { expect(form.getFieldValue('normal')).toEqual({ a: false, b: undefined }); form.getFieldInstance('normal.a').checked = true; Simulate.change(form.getFieldInstance('normal.a')); @@ -87,7 +86,7 @@ describe('checkbox-group usage', () => { }); }); - it('resetFields works', () => { + it.skip('resetFields works', () => { expect(form.getFieldValue('normal')).toEqual({ a: false, b: undefined }); form.getFieldInstance('normal.a').checked = true; Simulate.change(form.getFieldInstance('normal.a')); diff --git a/tests/overview.spec.js b/tests/overview.spec.js index 4a1515a3..5b5c1fe6 100644 --- a/tests/overview.spec.js +++ b/tests/overview.spec.js @@ -74,7 +74,7 @@ describe('overview usage', () => { expect(form.getFieldValue('normal')).toBe('1'); }); - it('collect nested array value', () => { + it.skip('collect nested array value', () => { form.getFieldInstance('a[0][1].b.c[0]').value = '0'; form.getFieldInstance('a[0][1].b.c[1]').value = '1'; Simulate.change(form.getFieldInstance('a[0][1].b.c[0]')); @@ -90,7 +90,7 @@ describe('overview usage', () => { expect(form.getFieldValue('a[0][1].b.c[1]')).toBe('1'); }); - it('collect nested value', () => { + it.skip('collect nested value', () => { form.getFieldInstance('foo.a.x').value = '1'; form.getFieldInstance('foo.a.y').value = '2'; form.getFieldInstance('foo.b[0]').value = '3'; @@ -190,7 +190,7 @@ describe('overview usage', () => { expect(form.getFieldValue('normal')).toBe('4'); }); - it('setFieldsValue and setFieldsInitialValue for nested field works', () => { + it.skip('setFieldsValue and setFieldsInitialValue for nested field works', () => { form.setFieldsInitialValue({ foo: { a: { @@ -239,7 +239,7 @@ describe('overview usage', () => { }); }); - it('setFieldsValue and setFieldsInitialValue for nested array works', () => { + it.skip('setFieldsValue and setFieldsInitialValue for nested array works', () => { form.setFieldsInitialValue({ a: [ [undefined, { diff --git a/tests/radioGroup.spec.js b/tests/radioGroup.spec.js index 7dc96d78..497967d4 100644 --- a/tests/radioGroup.spec.js +++ b/tests/radioGroup.spec.js @@ -18,7 +18,6 @@ class Test extends React.Component { { document.body.removeChild(container); }); - it('collect value', () => { + it.skip('collect value', () => { expect(form.getFieldValue('normal')).toEqual('b'); form.getFieldInstance('normal.a').checked = true; Simulate.change(form.getFieldInstance('normal.a')); @@ -88,7 +86,7 @@ describe('radio-group usage', () => { expect(form.getFieldInstance('normal.a').checked).toBe(false); }); - it('validateFields works for ok', (callback) => { + it.skip('validateFields works for ok', (callback) => { form.getFieldInstance('normal.a').checked = true; Simulate.change(form.getFieldInstance('normal.a')); form.validateFields((errors, values) => { @@ -98,7 +96,7 @@ describe('radio-group usage', () => { }); }); - it('resetFields works', () => { + it.skip('resetFields works', () => { expect(form.getFieldValue('normal')).toEqual('b'); form.getFieldInstance('normal.a').checked = true; Simulate.change(form.getFieldInstance('normal.a')); From 56519f6820f42fc77e6bca95705a6f0c68644871 Mon Sep 17 00:00:00 2001 From: Benjy Cui Date: Thu, 2 Nov 2017 17:40:42 +0800 Subject: [PATCH 2/7] chore: add limitation to fieldsStore.fields and update test cases --- src/createBaseForm.js | 44 +++-- src/createFieldsStore.js | 40 ++--- src/createFormField.js | 13 ++ src/utils.js | 120 ++++++++----- ...async.spec.js => async-validation.spec.js} | 44 ++--- .../{scroll.spec.js => createDOMForm.spec.js} | 2 +- tests/createForm.spec.js | 167 ++++++++++++++++++ tests/map.spec.js | 137 -------------- tests/message.spec.js | 76 -------- tests/onValuesChange.spec.js | 50 ------ tests/overview.spec.js | 30 +++- tests/utils.spec.js | 33 ++++ ...alidate.spec.js => validateFields.spec.js} | 28 +-- 13 files changed, 408 insertions(+), 376 deletions(-) create mode 100644 src/createFormField.js rename tests/{async.spec.js => async-validation.spec.js} (92%) rename tests/{scroll.spec.js => createDOMForm.spec.js} (100%) create mode 100644 tests/createForm.spec.js delete mode 100644 tests/map.spec.js delete mode 100644 tests/message.spec.js delete mode 100644 tests/onValuesChange.spec.js create mode 100644 tests/utils.spec.js rename tests/{forceValidate.spec.js => validateFields.spec.js} (85%) diff --git a/src/createBaseForm.js b/src/createBaseForm.js index 8cefb2d6..2535058c 100644 --- a/src/createBaseForm.js +++ b/src/createBaseForm.js @@ -8,13 +8,14 @@ import set from 'lodash/set'; import createFieldsStore from './createFieldsStore'; import { argumentContainer, - mirror, + identity, + normalizeValidateRules, + getValidateTriggers, getValueFromEvent, hasRules, getParams, isEmptyObject, flattenArray, - normalizeValidateRules, } from './utils'; const DEFAULT_TRIGGER = 'onChange'; @@ -24,7 +25,7 @@ function createBaseForm(option = {}, mixins = []) { validateMessages, onFieldsChange, onValuesChange, - mapProps = mirror, + mapProps = identity, mapPropsToFields, fieldNameProp, fieldMetaProp, @@ -34,7 +35,7 @@ function createBaseForm(option = {}, mixins = []) { withRef, } = option; - function decorate(WrappedComponent) { + return function decorate(WrappedComponent) { const Form = createReactClass({ mixins, @@ -97,12 +98,12 @@ function createBaseForm(option = {}, mixins = []) { onCollect(name_, action, ...args) { const { name, field, fieldMeta } = this.onCollectCommon(name_, action, args); const { validate } = fieldMeta; - const fieldContent = { + const newField = { ...field, dirty: hasRules(validate), }; this.setFields({ - [name]: fieldContent, + [name]: newField, }); }, @@ -121,7 +122,10 @@ function createBaseForm(option = {}, mixins = []) { }, getCacheBind(name, action, fn) { - const cache = this.cachedBind[name] = this.cachedBind[name] || {}; + if (!this.cachedBind[name]) { + this.cachedBind[name] = {}; + } + const cache = this.cachedBind[name]; if (!cache[action]) { cache[action] = fn.bind(this, name, action); } @@ -169,10 +173,10 @@ function createBaseForm(option = {}, mixins = []) { ); const fieldOption = { + name, + trigger: DEFAULT_TRIGGER, valuePropName: 'value', validate: [], - trigger: DEFAULT_TRIGGER, - name, ...usersFieldOption, }; @@ -197,10 +201,7 @@ function createBaseForm(option = {}, mixins = []) { } const validateRules = normalizeValidateRules(validate, rules, validateTrigger); - const validateTriggers = validateRules - .filter(item => !!item.rules && item.rules.length) - .map(item => item.trigger) - .reduce((pre, curr) => pre.concat(curr), []); + const validateTriggers = getValidateTriggers(validateRules); validateTriggers.forEach((action) => { if (inputProps[action]) return; inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate); @@ -260,7 +261,8 @@ function createBaseForm(option = {}, mixins = []) { setFieldsValue(fieldsValue) { if (onValuesChange) { - onValuesChange(this.props, fieldsValue); + const valuesAll = this.fieldsStore.getValueFromFieldsAll(); + onValuesChange(this.props, fieldsValue, { ...valuesAll, ...fieldsValue }); } const newFields = {}; const { fieldsMeta } = this.fieldsStore; @@ -429,10 +431,20 @@ function createBaseForm(option = {}, mixins = []) { }, isSubmitting() { + warning( + false, + '`isSubmitting` is deprecated. ' + + 'Actually, it\'s more convenient to handle submitting status by yourself.' + ); return this.state.submitting; }, submit(callback) { + warning( + false, + '`submit` is deprecated.' + + 'Actually, it\'s more convenient to handle submitting status by yourself.' + ); const fn = () => { this.setState({ submitting: false, @@ -468,9 +480,7 @@ function createBaseForm(option = {}, mixins = []) { }); return argumentContainer(Form, WrappedComponent); - } - - return decorate; + }; } export default createBaseForm; diff --git a/src/createFieldsStore.js b/src/createFieldsStore.js index 0138b2ea..163d8403 100644 --- a/src/createFieldsStore.js +++ b/src/createFieldsStore.js @@ -1,17 +1,22 @@ import set from 'lodash/set'; +import { isFormField } from './createFormField'; import { + flattenFields, getErrorStrs, } from './utils'; -const atom = {}; - class FieldsStore { constructor(fields) { - this.fields = fields; + this.fields = flattenFields(fields, this.isFieldTypeLeaf); this.fieldsMeta = {}; } + updateFields(fields) { - Object.assign(this.fields, fields); + this.fields = flattenFields(fields, this.isFieldTypeLeaf); + } + + isFieldTypeLeaf(_, node) { + return isFormField(node); } setFields(fields) { @@ -39,14 +44,6 @@ class FieldsStore { }); this.fields = nowFields; } - getUndirtyFields() { - const undirtyFields = Object.keys(this.fieldsMeta) - .filter(key => !this.fields[key]) - .map(key => ({ dirty: false, name: key, value: this.fieldsMeta[key].initialValue })); - const ret = {}; - undirtyFields.forEach(value => set(ret, value.name, value)); - return ret; - } resetFields(ns) { const newFields = {}; const { fields } = this; @@ -59,6 +56,14 @@ class FieldsStore { }); return newFields; } + getUndirtyFields() { + const undirtyFields = Object.keys(this.fieldsMeta) + .filter(key => !this.fields[key]) + .map(key => ({ dirty: false, name: key, value: this.fieldsMeta[key].initialValue })); + const ret = {}; + undirtyFields.forEach(value => set(ret, value.name, value)); + return ret; + } getValueFromFieldsInternal(name, fields) { const field = fields[name]; if (field && 'value' in field) { @@ -87,20 +92,14 @@ class FieldsStore { getFieldValuePropValue(fieldMeta) { const { name, getValueProps, valuePropName } = fieldMeta; const field = this.getField(name); - let fieldValue = atom; - if (field && 'value' in field) { - fieldValue = field.value; - } - if (fieldValue === atom) { - fieldValue = fieldMeta.initialValue; - } + const fieldValue = 'value' in field ? + field.value : fieldMeta.initialValue; if (getValueProps) { return getValueProps(fieldValue); } return { [valuePropName]: fieldValue }; } - getField(name) { return { ...this.fields[name], @@ -149,6 +148,7 @@ class FieldsStore { } return this.fieldsMeta[name]; } + setFieldMeta(name, meta) { this.fieldsMeta[name] = meta; } diff --git a/src/createFormField.js b/src/createFormField.js new file mode 100644 index 00000000..501a9be6 --- /dev/null +++ b/src/createFormField.js @@ -0,0 +1,13 @@ +class Field { + constructor(fields) { + Object.assign(this, fields); + } +} + +export function isFormField(obj) { + return obj instanceof Field; +} + +export default function createFormField(field) { + return new Field(field); +} diff --git a/src/utils.js b/src/utils.js index 10d8c00d..426c8ea7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -11,10 +11,72 @@ export function argumentContainer(Container, WrappedComponent) { return hoistStatics(Container, WrappedComponent); } -export function mirror(obj) { +export function identity(obj) { return obj; } +export function flattenArray(arr) { + return Array.prototype.concat.apply([], arr); +} + +function treeTraverse(path, tree, isLeafNode, callback) { + if (isLeafNode(path, tree)) { + callback(path, tree); + } else if (Array.isArray(tree)) { + tree.forEach((subTree, index) => treeTraverse( + `${path}[${index}]`, + subTree, + isLeafNode, + callback + )); + } else { // It's object and not a leaf node + Object.keys(tree).forEach(subTreeKey => { + const subTree = tree[subTreeKey]; + treeTraverse( + `${path}${path ? '.' : ''}${subTreeKey}`, + subTree, + isLeafNode, + callback + ); + }); + } +} + +export function flattenFields(maybeNestedFields, isLeafNode) { + const fields = {}; + treeTraverse('', maybeNestedFields, isLeafNode, (path, node) => { + fields[path] = node; + }); + return fields; +} + +export function normalizeValidateRules(validate, rules, validateTrigger) { + const validateRules = validate.map((item) => { + const newItem = { + ...item, + trigger: item.trigger || [], + }; + if (typeof newItem.trigger === 'string') { + newItem.trigger = [newItem.trigger]; + } + return newItem; + }); + if (rules) { + validateRules.push({ + trigger: validateTrigger ? [].concat(validateTrigger) : [], + rules, + }); + } + return validateRules; +} + +export function getValidateTriggers(validateRules) { + return validateRules + .filter(item => !!item.rules && item.rules.length) + .map(item => item.trigger) + .reduce((pre, curr) => pre.concat(curr), []); +} + export function getValueFromEvent(e) { // To support custom element if (!e || !e.target) { @@ -36,37 +98,16 @@ export function getErrorStrs(errors) { return errors; } -export function isEmptyObject(obj) { - return Object.keys(obj).length === 0; -} - -export function flattenArray(arr) { - return Array.prototype.concat.apply([], arr); -} - -export function hasRules(validate) { - if (validate) { - return validate.some((item) => { - return item.rules && item.rules.length; - }); - } - return false; -} - -export function startsWith(str, prefix) { - return str.lastIndexOf(prefix, 0) === 0; -} - export function getParams(ns, opt, cb) { let names = ns; - let callback = cb; let options = opt; + let callback = cb; if (cb === undefined) { if (typeof names === 'function') { callback = names; options = {}; names = undefined; - } else if (Array.isArray(ns)) { + } else if (Array.isArray(names)) { if (typeof options === 'function') { callback = options; options = {}; @@ -81,27 +122,24 @@ export function getParams(ns, opt, cb) { } return { names, - callback, options, + callback, }; } -export function normalizeValidateRules(validate, rules, validateTrigger) { - const validateRules = validate.map((item) => { - const newItem = { - ...item, - trigger: item.trigger || [], - }; - if (typeof newItem.trigger === 'string') { - newItem.trigger = [newItem.trigger]; - } - return newItem; - }); - if (rules) { - validateRules.push({ - trigger: validateTrigger ? [].concat(validateTrigger) : [], - rules, +export function isEmptyObject(obj) { + return Object.keys(obj).length === 0; +} + +export function hasRules(validate) { + if (validate) { + return validate.some((item) => { + return item.rules && item.rules.length; }); } - return validateRules; + return false; +} + +export function startsWith(str, prefix) { + return str.lastIndexOf(prefix, 0) === 0; } diff --git a/tests/async.spec.js b/tests/async-validation.spec.js similarity index 92% rename from tests/async.spec.js rename to tests/async-validation.spec.js index 9a6439e4..6bdae170 100644 --- a/tests/async.spec.js +++ b/tests/async-validation.spec.js @@ -19,13 +19,16 @@ class Test extends React.Component { render() { const { getFieldProps } = this.props.form; - return (
- - -
); + return ( +
+ + +
+ ); } } @@ -33,7 +36,7 @@ Test = createForm({ withRef: true, })(Test); -describe('async usage', () => { +describe('Async Validation', () => { let container; let component; let form; @@ -105,18 +108,6 @@ describe('async usage', () => { }); }); - it('submit works', (done) => { - expect(form.isSubmitting()).toBe(false); - form.submit((callback) => { - expect(form.isSubmitting()).toBe(true); - setTimeout(() => { - callback(); - expect(form.isSubmitting()).toBe(false); - done(); - }, 100); - }); - }); - it('will error if change when validating', (done) => { form.validateFields((errors) => { expect(Object.keys(errors).length).toBe(1); @@ -128,4 +119,17 @@ describe('async usage', () => { form.getFieldInstance('async').value = '1'; Simulate.change(form.getFieldInstance('async')); }); + + // TODO: submit and isSubmitting are deprecated + it('submit works', (done) => { + expect(form.isSubmitting()).toBe(false); + form.submit((callback) => { + expect(form.isSubmitting()).toBe(true); + setTimeout(() => { + callback(); + expect(form.isSubmitting()).toBe(false); + done(); + }, 100); + }); + }); }); diff --git a/tests/scroll.spec.js b/tests/createDOMForm.spec.js similarity index 100% rename from tests/scroll.spec.js rename to tests/createDOMForm.spec.js index 2ad0fcde..e60847f6 100644 --- a/tests/scroll.spec.js +++ b/tests/createDOMForm.spec.js @@ -2,8 +2,8 @@ jest.mock('dom-scroll-into-view', () => jest.fn()); import React from 'react'; -import scrollIntoView from 'dom-scroll-into-view'; import { mount } from 'enzyme'; +import scrollIntoView from 'dom-scroll-into-view'; import createDOMForm from '../src/createDOMForm'; class Test extends React.Component { diff --git a/tests/createForm.spec.js b/tests/createForm.spec.js new file mode 100644 index 00000000..6f11cfaa --- /dev/null +++ b/tests/createForm.spec.js @@ -0,0 +1,167 @@ +/* eslint-disable no-undef, react/prop-types, react/no-multi-comp */ + +import React from 'react'; +import { mount } from 'enzyme'; +import createForm from '../src/createForm'; +import createFormField from '../src/createFormField'; + +class TestComponent extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + +
+ ); + } +} + +describe('createForm', () => { + describe('validateMessage', () => { + it('works', () => { + class Test extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ +
+ ); + } + } + Test = createForm({ + withRef: true, + validateMessages: { + required: '%s required!', + }, + })(Test); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').simulate('change'); + expect(form.getFieldError('required').length).toBe(1); + expect(form.getFieldError('required')[0]).toBe('required required!'); + }); + }); + + describe('onFieldsChange', () => { + it('trigger `onFieldsChange` when value change', () => { + const onFieldsChange = jest.fn(); + const Test = createForm({ + onFieldsChange, + })(TestComponent); + const wrapper = mount(); + + wrapper.find('input').first().simulate('change', { target: { value: '2' } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal: { value: '2' } }); + expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ + normal: { value: '2' }, + normal2: { value: undefined }, + }); + + onFieldsChange.mockClear(); + wrapper.find('input').last().simulate('change', { target: { value: 'B' } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal2: { value: 'B' } }); + expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ + normal: { value: '2' }, + normal2: { value: 'B' }, + }); + }); + + it('trigger `onFieldsChange` when `setFields`', () => { + const onFieldsChange = jest.fn(); + const Test = createForm({ + withRef: true, + onFieldsChange, + })(TestComponent); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + + form.setFields({ normal: { value: '2' } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal: { value: '2' } }); + expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ + normal: { value: '2' }, + normal2: { value: undefined }, + }); + + onFieldsChange.mockClear(); + form.setFields({ normal2: { value: 'B' } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal2: { value: 'B' } }); + expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ + normal: { value: '2' }, + normal2: { value: 'B' }, + }); + }); + }); + + describe('onValuesChange', () => { + it('trigger `onValuesChange` when value change', () => { + const onValuesChange = jest.fn(); + const Test = createForm({ + onValuesChange, + })(TestComponent); + const wrapper = mount(); + wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } }); + expect(onValuesChange.mock.calls[0][1]).toMatchObject({ normal: 'Benjy' }); + expect(onValuesChange.mock.calls[0][2]) + .toMatchObject({ normal: 'Benjy', normal2: undefined }); + }); + + it('trigger `onValuesChange` when `setFieldsValue`', () => { + const onValuesChange = jest.fn(); + const Test = createForm({ + withRef: true, + onValuesChange, + })(TestComponent); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + form.setFieldsValue({ normal: 'Benjy' }); + expect(onValuesChange.mock.calls[0][1]).toMatchObject({ normal: 'Benjy' }); + expect(onValuesChange.mock.calls[0][2]) + .toMatchObject({ normal: 'Benjy', normal2: undefined }); + }); + }); + + describe('mapProps', () => { + it('works', () => { + const Test = createForm({ + withRef: true, + mapProps(props) { + return { + ...props, + x: props.x + 1, + }; + }, + })(TestComponent); + const wrapper = mount(); + const wrappedComponent = wrapper.ref('wrappedComponent'); + expect(wrappedComponent.prop('x')).toBe(3); + }); + }); + + describe('mapPropsToFields', () => { + it('returned value will replace current fields', () => { + const Test = createForm({ + withRef: true, + mapPropsToFields(props) { + return { + normal: createFormField({ + value: props.formState.normal, + }), + }; + }, + })(TestComponent); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').last().simulate('change', { target: { value: '3' } }); + wrapper.setProps({ formState: { normal: '1' } }); + expect(form.getFieldValue('normal')).toBe('1'); + expect(form.getFieldValue('normal2')).toBe(undefined); + }); + }); +}); diff --git a/tests/map.spec.js b/tests/map.spec.js deleted file mode 100644 index fd5f1999..00000000 --- a/tests/map.spec.js +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint-disable no-undef, react/prop-types */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -class TestComponent extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return (
- - -
); - } -} - - -describe('map usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - - describe('onFieldsChange works', () => { - it('onFieldsChange works: fields', () => { - const Test = createForm({ - withRef: true, - onFieldsChange(props, fields) { - expect(Object.keys(fields).length).toBe(1); - const field = fields.normal; - expect(field.name).toBe('normal'); - expect(field.value).toBe('3'); - }, - mapPropsToFields(props) { - return { - normal: { - value: props.formState.normal, - }, - }; - }, - })(TestComponent); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - expect(form.getFieldInstance('normal').value).toBe('2'); - expect(form.getFieldValue('normal')).toBe('2'); - expect(form.getFieldInstance('normal2').value).toBe(''); - expect(form.getFieldValue('normal2')).toBe(undefined); - form.getFieldInstance('normal').value = '3'; - Simulate.change(form.getFieldInstance('normal')); - expect(form.getFieldValue('normal')).toBe('3'); - }); - it('onFieldsChange works: allFields', () => { - const Test = createForm({ - withRef: true, - onFieldsChange(props, fields, allFields) { - if (fields.normal) return; - expect(Object.keys(allFields).length).toBe(2); - const fileChanged = fields.normal2; - expect(fileChanged.name).toBe('normal2'); - expect(fileChanged.value).toBe('B'); - const fieldNotChanged = allFields.normal; - expect(fieldNotChanged.name).toBe('normal'); - expect(fieldNotChanged.value).toBe('3'); - }, - mapPropsToFields(props) { - return { - normal: { - value: props.formState.normal, - }, - normal2: { - value: props.formState.normal2, - }, - }; - }, - })(TestComponent); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - expect(form.getFieldInstance('normal').value).toBe('2'); - expect(form.getFieldValue('normal')).toBe('2'); - expect(form.getFieldInstance('normal2').value).toBe('A'); - expect(form.getFieldValue('normal2')).toBe('A'); - form.getFieldInstance('normal').value = '3'; - Simulate.change(form.getFieldInstance('normal')); - form.getFieldInstance('normal2').value = 'B'; - Simulate.change(form.getFieldInstance('normal2')); - }); - }); - - - it('mapPropsToFields\'s return value will be merge to current fields', () => { - const Test = createForm({ - withRef: true, - mapPropsToFields(props) { - return { - normal: { - value: props.formState.normal, - }, - }; - }, - })(TestComponent); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - form.getFieldInstance('normal2').value = '3'; - Simulate.change(form.getFieldInstance('normal2')); - component = ReactDOM.render(, container); - expect(form.getFieldValue('normal2')).toBe('3'); - }); - - it('mapProps works', () => { - const Test = createForm({ - withRef: true, - mapProps(props) { - return { - ...props, - x: props.x + 1, - }; - }, - })(TestComponent); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - expect(component.props.x).toBe(3); - }); -}); diff --git a/tests/message.spec.js b/tests/message.spec.js deleted file mode 100644 index eee4090a..00000000 --- a/tests/message.spec.js +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-disable react/no-multi-comp, no-undef, react/prop-types */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -describe('message usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - it('validateMessages works', () => { - class Test extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return (
- -
); - } - } - Test = createForm({ - withRef: true, - validateMessages: { - required: '%s required!', - }, - })(Test); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - Simulate.change(form.getFieldInstance('required')); - expect(form.getFieldError('required').length).toBe(1); - expect(form.getFieldError('required')[0]).toBe('required required!'); - }); - - it('jsx works', () => { - class Test extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return (
- 1, - }], - })} - /> -
); - } - } - Test = createForm({ - withRef: true, - })(Test); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - Simulate.change(form.getFieldInstance('required')); - expect(form.getFieldError('required').length).toBe(1); - expect(form.getFieldError('required')[0].type).toBe('b'); - }); -}); diff --git a/tests/onValuesChange.spec.js b/tests/onValuesChange.spec.js deleted file mode 100644 index 66bee4f0..00000000 --- a/tests/onValuesChange.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable no-undef, react/prop-types */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -class TestComponent extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ; - } -} - -describe('onValuesChange', () => { - let container; - let component; - let form; - let values; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - const Test = createForm({ - withRef: true, - onValuesChange(props, changedValues) { - values = changedValues; - }, - })(TestComponent); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - values = undefined; - }); - - it('should trigger `onValuesChange` when value change', () => { - Simulate.change(form.getFieldInstance('employee.name'), { target: { value: 'Benjy' } }); - expect(values).toEqual({ employee: { name: 'Benjy' } }); - }); - - it('should trigger `onValuesChange` when `setFieldsValue`', () => { - form.setFieldsValue({ employee: { name: 'Benjy' } }); - expect(values).toEqual({ employee: { name: 'Benjy' } }); - }); -}); diff --git a/tests/overview.spec.js b/tests/overview.spec.js index 5b5c1fe6..045672ba 100644 --- a/tests/overview.spec.js +++ b/tests/overview.spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef, react/prop-types */ +/* eslint-disable no-undef, react/prop-types, react/no-multi-comp */ import React from 'react'; import ReactDOM from 'react-dom'; @@ -285,4 +285,32 @@ describe('overview usage', () => { }], ]); }); + + it('jsx works', () => { + class TestComponent extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ 1, + }], + })} + /> +
+ ); + } + } + TestComponent = createForm({ + withRef: true, + })(TestComponent); + component = ReactDOM.render(, container); + component = component.refs.wrappedComponent; + form = component.props.form; + Simulate.change(form.getFieldInstance('required')); + expect(form.getFieldError('required').length).toBe(1); + expect(form.getFieldError('required')[0].type).toBe('b'); + }); }); diff --git a/tests/utils.spec.js b/tests/utils.spec.js new file mode 100644 index 00000000..97ebabfe --- /dev/null +++ b/tests/utils.spec.js @@ -0,0 +1,33 @@ +import createFormField, { isFormField } from '../src/createFormField'; +import { flattenFields } from '../src/utils'; + +describe('utils.flattenFields', () => { + it('works', () => { + const fields = { + user: { + name: createFormField({ + value: 'benjycui', + }), + age: createFormField({ + value: 18, + }), + hobbies: [ + createFormField({ + value: 'Archery', + }), + createFormField({ + value: 'Roller Skating', + }), + ], + }, + }; + + expect(flattenFields(fields, (_, node) => isFormField(node))) + .toEqual({ + 'user.name': { value: 'benjycui' }, + 'user.age': { value: 18 }, + 'user.hobbies[0]': { value: 'Archery' }, + 'user.hobbies[1]': { value: 'Roller Skating' }, + }); + }); +}); diff --git a/tests/forceValidate.spec.js b/tests/validateFields.spec.js similarity index 85% rename from tests/forceValidate.spec.js rename to tests/validateFields.spec.js index 34a85264..a50aeb6d 100644 --- a/tests/forceValidate.spec.js +++ b/tests/validateFields.spec.js @@ -33,18 +33,20 @@ class Test extends React.Component { render() { const { getFieldProps } = this.props.form; - return (
- - - -
); + return ( +
+ + + +
+ ); } } @@ -52,7 +54,7 @@ Test = createForm({ withRef: true, })(Test); -describe('async usage', () => { +describe('validateFields', () => { let container; let component; let form; From 8d8cb5713779e122e43671f7eb72efb152e98cfe Mon Sep 17 00:00:00 2001 From: Benjy Cui Date: Fri, 3 Nov 2017 17:10:00 +0800 Subject: [PATCH 3/7] feat: make nested fields works for createFrom's options --- src/createBaseForm.js | 53 +++++---- src/createFieldsStore.js | 50 ++++---- src/createFormField.js | 3 + src/utils.js | 7 +- tests/createForm.spec.js | 245 ++++++++++++++++++++++++++++++--------- 5 files changed, 256 insertions(+), 102 deletions(-) diff --git a/src/createBaseForm.js b/src/createBaseForm.js index 2535058c..d8ebb6e1 100644 --- a/src/createBaseForm.js +++ b/src/createBaseForm.js @@ -85,7 +85,7 @@ function createBaseForm(option = {}, mixins = []) { fieldMeta.getValueFromEvent(...args) : getValueFromEvent(...args); if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) { - const valuesAll = this.fieldsStore.getValueFromFieldsAll(); + const valuesAll = this.fieldsStore.getAllValues(); const valuesAllSet = {}; valuesAll[name] = value; Object.keys(valuesAll).forEach(key => set(valuesAllSet, key, valuesAll[key])); @@ -109,11 +109,11 @@ function createBaseForm(option = {}, mixins = []) { onCollectValidate(name_, action, ...args) { const { field, fieldMeta } = this.onCollectCommon(name_, action, args); - const fieldContent = { + const newField = { ...field, dirty: true, }; - this.validateFieldsInternal([fieldContent], { + this.validateFieldsInternal([newField], { action, options: { firstFields: !!fieldMeta.validateFirst, @@ -240,14 +240,13 @@ function createBaseForm(option = {}, mixins = []) { return flattenArray(actionRules); }, - setFields(fields) { + setFields(maybeNestedFields) { + const fields = this.fieldsStore.flattenRegisteredFields(maybeNestedFields); this.fieldsStore.setFields(fields); if (onFieldsChange) { - const changedFields = {}; - Object.keys(fields).forEach((f) => { - changedFields[f] = this.fieldsStore.getField(f); - }); - onFieldsChange(this.props, changedFields, this.fieldsStore.getFieldAll()); + const changedFields = Object.keys(fields) + .reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {}); + onFieldsChange(this.props, changedFields, this.fieldsStore.getAllFields()); } this.forceUpdate(); }, @@ -259,29 +258,29 @@ function createBaseForm(option = {}, mixins = []) { } }, - setFieldsValue(fieldsValue) { - if (onValuesChange) { - const valuesAll = this.fieldsStore.getValueFromFieldsAll(); - onValuesChange(this.props, fieldsValue, { ...valuesAll, ...fieldsValue }); - } - const newFields = {}; + setFieldsValue(changedValues) { const { fieldsMeta } = this.fieldsStore; - Object.keys(fieldsValue).forEach((name) => { - const value = fieldsValue[name]; - if (fieldsMeta[name]) { - newFields[name] = { - name, + const values = this.fieldsStore.flattenRegisteredFields(changedValues); + const newFields = Object.keys(values).reduce((acc, name) => { + const isRegistered = fieldsMeta[name]; + warning( + isRegistered, + 'Cannot use `setFieldsValue` until ' + + 'you use `getFieldDecorator` or `getFieldProps` to register it.' + ); + if (isRegistered) { + const value = values[name]; + acc[name] = { value, }; - } else { - warning( - false, - 'Cannot use `setFieldsValue` until ' + - 'you use `getFieldDecorator` or `getFieldProps` to register it.' - ); } - }); + return acc; + }, {}); this.setFields(newFields); + if (onValuesChange) { + const allValues = this.fieldsStore.getAllValues(); + onValuesChange(this.props, changedValues, allValues); + } }, saveRef(name, _, component) { diff --git a/src/createFieldsStore.js b/src/createFieldsStore.js index 163d8403..bb302681 100644 --- a/src/createFieldsStore.js +++ b/src/createFieldsStore.js @@ -1,5 +1,5 @@ import set from 'lodash/set'; -import { isFormField } from './createFormField'; +import createFormField, { isFormField } from './createFormField'; import { flattenFields, getErrorStrs, @@ -19,6 +19,11 @@ class FieldsStore { return isFormField(node); } + flattenRegisteredFields(fields) { + const validFieldsName = this.getValidFieldsName(); + return flattenFields(fields, path => validFieldsName.includes(path)); + } + setFields(fields) { const fieldsMeta = this.fieldsMeta; const nowFields = { @@ -56,14 +61,6 @@ class FieldsStore { }); return newFields; } - getUndirtyFields() { - const undirtyFields = Object.keys(this.fieldsMeta) - .filter(key => !this.fields[key]) - .map(key => ({ dirty: false, name: key, value: this.fieldsMeta[key].initialValue })); - const ret = {}; - undirtyFields.forEach(value => set(ret, value.name, value)); - return ret; - } getValueFromFieldsInternal(name, fields) { const field = fields[name]; if (field && 'value' in field) { @@ -75,17 +72,14 @@ class FieldsStore { getValueFromFields(name, fields) { return this.getValueFromFieldsInternal(name, fields); } - getValueFromFieldsAll = () => { + getAllValues = () => { const { fieldsMeta, fields } = this; - const ret = {}; - Object.keys(fieldsMeta).forEach(fieldKey => { - ret[fieldKey] = this.getValueFromFieldsInternal(fieldKey, fields); - }); - return ret; + return Object.keys(fieldsMeta) + .reduce((acc, name) => set(acc, name, this.getValueFromFieldsInternal(name, fields)), {}); } getValidFieldsName() { - const fieldsMeta = this.fieldsMeta; + const { fieldsMeta } = this; return fieldsMeta ? Object.keys(fieldsMeta) : []; } @@ -106,12 +100,26 @@ class FieldsStore { name, }; } - getFieldAll() { - return { - ...this.fields, - ...this.getUndirtyFields(), - }; + + getNotCollectedFields() { + return this.getValidFieldsName() + .filter(name => !this.fields[name]) + .map(name => ({ + name, + dirty: false, + value: this.fieldsMeta[name].initialValue, + })) + .reduce((acc, field) => set(acc, field.name, createFormField(field)), {}); } + + getAllFields() { + return Object.keys(this.fields) + .reduce( + (acc, name) => set(acc, name, createFormField(this.fields[name])), + this.getNotCollectedFields() + ); + } + getFieldMember(name, member) { return this.getField(name)[member]; } diff --git a/src/createFormField.js b/src/createFormField.js index 501a9be6..2519ea52 100644 --- a/src/createFormField.js +++ b/src/createFormField.js @@ -9,5 +9,8 @@ export function isFormField(obj) { } export default function createFormField(field) { + if (isFormField(field)) { + return field; + } return new Field(field); } diff --git a/src/utils.js b/src/utils.js index 426c8ea7..e1e61351 100644 --- a/src/utils.js +++ b/src/utils.js @@ -19,7 +19,7 @@ export function flattenArray(arr) { return Array.prototype.concat.apply([], arr); } -function treeTraverse(path, tree, isLeafNode, callback) { +export function treeTraverse(path = '', tree, isLeafNode, callback) { if (isLeafNode(path, tree)) { callback(path, tree); } else if (Array.isArray(tree)) { @@ -30,6 +30,9 @@ function treeTraverse(path, tree, isLeafNode, callback) { callback )); } else { // It's object and not a leaf node + if (typeof tree !== 'object') { + throw new Error('You must wrap field data with `createFormField`.'); + } Object.keys(tree).forEach(subTreeKey => { const subTree = tree[subTreeKey]; treeTraverse( @@ -44,7 +47,7 @@ function treeTraverse(path, tree, isLeafNode, callback) { export function flattenFields(maybeNestedFields, isLeafNode) { const fields = {}; - treeTraverse('', maybeNestedFields, isLeafNode, (path, node) => { + treeTraverse(undefined, maybeNestedFields, isLeafNode, (path, node) => { fields[path] = node; }); return fields; diff --git a/tests/createForm.spec.js b/tests/createForm.spec.js index 6f11cfaa..66e25a90 100644 --- a/tests/createForm.spec.js +++ b/tests/createForm.spec.js @@ -1,22 +1,10 @@ -/* eslint-disable no-undef, react/prop-types, react/no-multi-comp */ +/* eslint-disable no-undef, space-before-keywords, react/prop-types, react/no-multi-comp */ import React from 'react'; import { mount } from 'enzyme'; import createForm from '../src/createForm'; import createFormField from '../src/createFormField'; -class TestComponent extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - -
- ); - } -} - describe('createForm', () => { describe('validateMessage', () => { it('works', () => { @@ -54,22 +42,28 @@ describe('createForm', () => { const onFieldsChange = jest.fn(); const Test = createForm({ onFieldsChange, - })(TestComponent); - const wrapper = mount(); - - wrapper.find('input').first().simulate('change', { target: { value: '2' } }); - expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal: { value: '2' } }); - expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ - normal: { value: '2' }, - normal2: { value: undefined }, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( + + + + + + ); + } }); + const wrapper = mount(); - onFieldsChange.mockClear(); - wrapper.find('input').last().simulate('change', { target: { value: 'B' } }); - expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal2: { value: 'B' } }); + wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } }); expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ - normal: { value: '2' }, - normal2: { value: 'B' }, + user: { + name: { value: 'Benjy' }, + age: { value: undefined }, + }, + agreement: { value: undefined }, }); }); @@ -78,23 +72,81 @@ describe('createForm', () => { const Test = createForm({ withRef: true, onFieldsChange, - })(TestComponent); + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + }); const wrapper = mount(); const form = wrapper.ref('wrappedComponent').prop('form'); - form.setFields({ normal: { value: '2' } }); - expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal: { value: '2' } }); + form.setFields({ user: { name: { value: 'Benjy' } } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } }); expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ - normal: { value: '2' }, - normal2: { value: undefined }, + user: { + name: { value: 'Benjy' }, + age: { value: undefined }, + }, + agreement: { value: undefined }, }); + }); - onFieldsChange.mockClear(); - form.setFields({ normal2: { value: 'B' } }); - expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ normal2: { value: 'B' } }); - expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ - normal: { value: '2' }, - normal2: { value: 'B' }, + it('fields in arguemnts can be passed to `mapPropsToFields` directly', () => { + const Test = createForm({ + withRef: true, + onFieldsChange(props, changed, all) { + props.onChange(all); + }, + mapPropsToFields({ fields }) { + return fields; + }, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + }); + class TestWrapper extends React.Component { + state = { + fields: {}, + }; + handleFieldsChange = (fields) => { + this.setState({ fields }); + } + render() { + return ( + + ); + } + } + const wrapper = mount(); + wrapper.find('input').at(0).simulate('change', { target: { value: 'Benjy' } }); + wrapper.find('input').at(1).simulate('change', { target: { value: 18 } }); + wrapper.find('input').at(2) + .simulate('change', { target: { type: 'checkbox', checked: true } }); + expect(wrapper.state('fields')).toMatchObject({ + user: { + age: { value: 18 }, + name: { value: 'Benjy' }, + }, + agreement: { value: true }, }); }); }); @@ -104,12 +156,29 @@ describe('createForm', () => { const onValuesChange = jest.fn(); const Test = createForm({ onValuesChange, - })(TestComponent); + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + }); const wrapper = mount(); wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } }); - expect(onValuesChange.mock.calls[0][1]).toMatchObject({ normal: 'Benjy' }); + expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } }); expect(onValuesChange.mock.calls[0][2]) - .toMatchObject({ normal: 'Benjy', normal2: undefined }); + .toMatchObject({ + user: { + name: 'Benjy', + age: undefined, + }, + agreement: undefined, + }); }); it('trigger `onValuesChange` when `setFieldsValue`', () => { @@ -117,13 +186,30 @@ describe('createForm', () => { const Test = createForm({ withRef: true, onValuesChange, - })(TestComponent); + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + }); const wrapper = mount(); const form = wrapper.ref('wrappedComponent').prop('form'); - form.setFieldsValue({ normal: 'Benjy' }); - expect(onValuesChange.mock.calls[0][1]).toMatchObject({ normal: 'Benjy' }); + form.setFieldsValue({ user: { name: 'Benjy' } }); + expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } }); expect(onValuesChange.mock.calls[0][2]) - .toMatchObject({ normal: 'Benjy', normal2: undefined }); + .toMatchObject({ + user: { + name: 'Benjy', + age: undefined, + }, + agreement: undefined, + }); }); }); @@ -137,7 +223,7 @@ describe('createForm', () => { x: props.x + 1, }; }, - })(TestComponent); + })(class extends React.Component { render() { return null; } }); const wrapper = mount(); const wrappedComponent = wrapper.ref('wrappedComponent'); expect(wrappedComponent.prop('x')).toBe(3); @@ -145,23 +231,78 @@ describe('createForm', () => { }); describe('mapPropsToFields', () => { + it('works', () => { + const Test = createForm({ + withRef: true, + mapPropsToFields({ formState }) { + return { + user: { + name: createFormField({ + value: formState.userName, + }), + age: createFormField({ + value: formState.userAge, + }), + }, + agreement: createFormField({ + value: formState.agreement, + }), + }; + }, + })(class extends React.Component { + render() { + const { form } = this.props; + const { getFieldProps } = form; + return ( +
+ + + +
+ ); + } + }); + const wrapper = mount( + + ); + const form = wrapper.ref('wrappedComponent').prop('form'); + expect(form.getFieldValue('user.name')).toBe('Benjy'); + expect(form.getFieldValue('user.age')).toBe(18); + expect(form.getFieldValue('agreement')).toBe(false); + + wrapper.setProps({ formState: { userName: 'Benjy', userAge: 18, agreement: true } }); + expect(form.getFieldValue('user.name')).toBe('Benjy'); + expect(form.getFieldValue('user.age')).toBe(18); + expect(form.getFieldValue('agreement')).toBe(true); + }); + it('returned value will replace current fields', () => { const Test = createForm({ withRef: true, mapPropsToFields(props) { return { - normal: createFormField({ - value: props.formState.normal, + field1: createFormField({ + value: props.formState.field1, }), }; }, - })(TestComponent); - const wrapper = mount(); + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + +
+ ); + } + }); + const wrapper = mount(); const form = wrapper.ref('wrappedComponent').prop('form'); wrapper.find('input').last().simulate('change', { target: { value: '3' } }); - wrapper.setProps({ formState: { normal: '1' } }); - expect(form.getFieldValue('normal')).toBe('1'); - expect(form.getFieldValue('normal2')).toBe(undefined); + wrapper.setProps({ formState: { field1: '1' } }); + expect(form.getFieldValue('field1')).toBe('1'); + expect(form.getFieldValue('field2')).toBe(undefined); }); }); }); From 399cf69cf7cdf1992a5c9034bfd54d687cc5a7c2 Mon Sep 17 00:00:00 2001 From: Benjy Cui Date: Mon, 6 Nov 2017 10:43:16 +0800 Subject: [PATCH 4/7] test: clean test cases --- package.json | 2 +- src/createBaseForm.js | 36 ++- tests/createForm.spec.js | 536 ++++++++++++++++---------------- tests/getFieldProps.spec.js | 103 ++++++ tests/getValueFromEvent.spec.js | 59 ---- tests/initialValue.spec.js | 61 ---- tests/normalize.spec.js | 51 --- 7 files changed, 392 insertions(+), 456 deletions(-) create mode 100644 tests/getFieldProps.spec.js delete mode 100644 tests/getValueFromEvent.spec.js delete mode 100644 tests/initialValue.spec.js delete mode 100644 tests/normalize.spec.js diff --git a/package.json b/package.json index a49db225..e486327b 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "start": "rc-tools run server", "pub": "rc-tools run pub --babel-runtime", "lint": "rc-tools run lint", - "test": "jest", + "test": "NODE_ENV=test jest", "coverage": "jest --coverage && cat ./coverage/lcov.info | coveralls" }, "jest": { diff --git a/src/createBaseForm.js b/src/createBaseForm.js index d8ebb6e1..109f1351 100644 --- a/src/createBaseForm.js +++ b/src/createBaseForm.js @@ -430,20 +430,24 @@ function createBaseForm(option = {}, mixins = []) { }, isSubmitting() { - warning( - false, - '`isSubmitting` is deprecated. ' + - 'Actually, it\'s more convenient to handle submitting status by yourself.' - ); + if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { + warning( + false, + '`isSubmitting` is deprecated. ' + + 'Actually, it\'s more convenient to handle submitting status by yourself.' + ); + } return this.state.submitting; }, submit(callback) { - warning( - false, - '`submit` is deprecated.' + - 'Actually, it\'s more convenient to handle submitting status by yourself.' - ); + if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { + warning( + false, + '`submit` is deprecated.' + + 'Actually, it\'s more convenient to handle submitting status by yourself.' + ); + } const fn = () => { this.setState({ submitting: false, @@ -461,11 +465,13 @@ function createBaseForm(option = {}, mixins = []) { [formPropName]: this.getForm(), }; if (withRef) { - warning( - false, - '`withRef` is deprecated, please use `wrappedComponentRef` instead. ' + - 'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140' - ); + if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { + warning( + false, + '`withRef` is deprecated, please use `wrappedComponentRef` instead. ' + + 'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140' + ); + } formProps.ref = 'wrappedComponent'; } else if (wrappedComponentRef) { formProps.ref = wrappedComponentRef; diff --git a/tests/createForm.spec.js b/tests/createForm.spec.js index 66e25a90..26c7e952 100644 --- a/tests/createForm.spec.js +++ b/tests/createForm.spec.js @@ -5,204 +5,172 @@ import { mount } from 'enzyme'; import createForm from '../src/createForm'; import createFormField from '../src/createFormField'; -describe('createForm', () => { - describe('validateMessage', () => { - it('works', () => { - class Test extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- -
- ); - } +describe('validateMessage', () => { + it('works', () => { + class Test extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ +
+ ); } - Test = createForm({ - withRef: true, - validateMessages: { - required: '%s required!', - }, - })(Test); - const wrapper = mount(); - const form = wrapper.ref('wrappedComponent').prop('form'); - wrapper.find('input').simulate('change'); - expect(form.getFieldError('required').length).toBe(1); - expect(form.getFieldError('required')[0]).toBe('required required!'); - }); + } + Test = createForm({ + withRef: true, + validateMessages: { + required: '%s required!', + }, + })(Test); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').simulate('change'); + expect(form.getFieldError('required').length).toBe(1); + expect(form.getFieldError('required')[0]).toBe('required required!'); }); +}); - describe('onFieldsChange', () => { - it('trigger `onFieldsChange` when value change', () => { - const onFieldsChange = jest.fn(); - const Test = createForm({ - onFieldsChange, - })(class extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - - -
- ); - } - }); - const wrapper = mount(); +describe('onFieldsChange', () => { + it('trigger `onFieldsChange` when value change', () => { + const onFieldsChange = jest.fn(); + const Test = createForm({ + onFieldsChange, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + }); + const wrapper = mount(); - wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } }); - expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } }); - expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ - user: { - name: { value: 'Benjy' }, - age: { value: undefined }, - }, - agreement: { value: undefined }, - }); + wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } }); + expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ + user: { + name: { value: 'Benjy' }, + age: { value: undefined }, + }, + agreement: { value: undefined }, }); + }); - it('trigger `onFieldsChange` when `setFields`', () => { - const onFieldsChange = jest.fn(); - const Test = createForm({ - withRef: true, - onFieldsChange, - })(class extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - - -
- ); - } - }); - const wrapper = mount(); - const form = wrapper.ref('wrappedComponent').prop('form'); + it('trigger `onFieldsChange` when `setFields`', () => { + const onFieldsChange = jest.fn(); + const Test = createForm({ + withRef: true, + onFieldsChange, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + }); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); - form.setFields({ user: { name: { value: 'Benjy' } } }); - expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } }); - expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ - user: { - name: { value: 'Benjy' }, - age: { value: undefined }, - }, - agreement: { value: undefined }, - }); + form.setFields({ user: { name: { value: 'Benjy' } } }); + expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } }); + expect(onFieldsChange.mock.calls[0][2]).toMatchObject({ + user: { + name: { value: 'Benjy' }, + age: { value: undefined }, + }, + agreement: { value: undefined }, }); + }); - it('fields in arguemnts can be passed to `mapPropsToFields` directly', () => { - const Test = createForm({ - withRef: true, - onFieldsChange(props, changed, all) { - props.onChange(all); - }, - mapPropsToFields({ fields }) { - return fields; - }, - })(class extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - - -
- ); - } - }); - class TestWrapper extends React.Component { - state = { - fields: {}, - }; - handleFieldsChange = (fields) => { - this.setState({ fields }); - } - render() { - return ( - - ); - } + it('fields in arguemnts can be passed to `mapPropsToFields` directly', () => { + const Test = createForm({ + withRef: true, + onFieldsChange(props, changed, all) { + props.onChange(all); + }, + mapPropsToFields({ fields }) { + return fields; + }, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); } - const wrapper = mount(); - wrapper.find('input').at(0).simulate('change', { target: { value: 'Benjy' } }); - wrapper.find('input').at(1).simulate('change', { target: { value: 18 } }); - wrapper.find('input').at(2) - .simulate('change', { target: { type: 'checkbox', checked: true } }); - expect(wrapper.state('fields')).toMatchObject({ - user: { - age: { value: 18 }, - name: { value: 'Benjy' }, - }, - agreement: { value: true }, - }); + }); + class TestWrapper extends React.Component { + state = { + fields: {}, + }; + handleFieldsChange = (fields) => { + this.setState({ fields }); + } + render() { + return ( + + ); + } + } + const wrapper = mount(); + wrapper.find('input').at(0).simulate('change', { target: { value: 'Benjy' } }); + wrapper.find('input').at(1).simulate('change', { target: { value: 18 } }); + wrapper.find('input').at(2) + .simulate('change', { target: { type: 'checkbox', checked: true } }); + expect(wrapper.state('fields')).toMatchObject({ + user: { + age: { value: 18 }, + name: { value: 'Benjy' }, + }, + agreement: { value: true }, }); }); +}); - describe('onValuesChange', () => { - it('trigger `onValuesChange` when value change', () => { - const onValuesChange = jest.fn(); - const Test = createForm({ - onValuesChange, - })(class extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - - -
- ); - } - }); - const wrapper = mount(); - wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } }); - expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } }); - expect(onValuesChange.mock.calls[0][2]) - .toMatchObject({ - user: { - name: 'Benjy', - age: undefined, - }, - agreement: undefined, - }); +describe('onValuesChange', () => { + it('trigger `onValuesChange` when value change', () => { + const onValuesChange = jest.fn(); + const Test = createForm({ + onValuesChange, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } }); - - it('trigger `onValuesChange` when `setFieldsValue`', () => { - const onValuesChange = jest.fn(); - const Test = createForm({ - withRef: true, - onValuesChange, - })(class extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - - -
- ); - } - }); - const wrapper = mount(); - const form = wrapper.ref('wrappedComponent').prop('form'); - form.setFieldsValue({ user: { name: 'Benjy' } }); - expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } }); - expect(onValuesChange.mock.calls[0][2]) + const wrapper = mount(); + wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } }); + expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } }); + expect(onValuesChange.mock.calls[0][2]) .toMatchObject({ user: { name: 'Benjy', @@ -210,99 +178,129 @@ describe('createForm', () => { }, agreement: undefined, }); - }); }); - describe('mapProps', () => { - it('works', () => { - const Test = createForm({ - withRef: true, - mapProps(props) { - return { - ...props, - x: props.x + 1, - }; - }, - })(class extends React.Component { render() { return null; } }); - const wrapper = mount(); - const wrappedComponent = wrapper.ref('wrappedComponent'); - expect(wrappedComponent.prop('x')).toBe(3); + it('trigger `onValuesChange` when `setFieldsValue`', () => { + const onValuesChange = jest.fn(); + const Test = createForm({ + withRef: true, + onValuesChange, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + }); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + form.setFieldsValue({ user: { name: 'Benjy' } }); + expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } }); + expect(onValuesChange.mock.calls[0][2]) + .toMatchObject({ + user: { + name: 'Benjy', + age: undefined, + }, + agreement: undefined, }); }); +}); - describe('mapPropsToFields', () => { - it('works', () => { - const Test = createForm({ - withRef: true, - mapPropsToFields({ formState }) { - return { - user: { - name: createFormField({ - value: formState.userName, - }), - age: createFormField({ - value: formState.userAge, - }), - }, - agreement: createFormField({ - value: formState.agreement, - }), - }; - }, - })(class extends React.Component { - render() { - const { form } = this.props; - const { getFieldProps } = form; - return ( -
- - - -
- ); - } - }); - const wrapper = mount( - - ); - const form = wrapper.ref('wrappedComponent').prop('form'); - expect(form.getFieldValue('user.name')).toBe('Benjy'); - expect(form.getFieldValue('user.age')).toBe(18); - expect(form.getFieldValue('agreement')).toBe(false); +describe('mapProps', () => { + it('works', () => { + const Test = createForm({ + withRef: true, + mapProps(props) { + return { + ...props, + x: props.x + 1, + }; + }, + })(class extends React.Component { render() { return null; } }); + const wrapper = mount(); + const wrappedComponent = wrapper.ref('wrappedComponent'); + expect(wrappedComponent.prop('x')).toBe(3); + }); +}); - wrapper.setProps({ formState: { userName: 'Benjy', userAge: 18, agreement: true } }); - expect(form.getFieldValue('user.name')).toBe('Benjy'); - expect(form.getFieldValue('user.age')).toBe(18); - expect(form.getFieldValue('agreement')).toBe(true); +describe('mapPropsToFields', () => { + it('works', () => { + const Test = createForm({ + withRef: true, + mapPropsToFields({ formState }) { + return { + user: { + name: createFormField({ + value: formState.userName, + }), + age: createFormField({ + value: formState.userAge, + }), + }, + agreement: createFormField({ + value: formState.agreement, + }), + }; + }, + })(class extends React.Component { + render() { + const { form } = this.props; + const { getFieldProps } = form; + return ( +
+ + + +
+ ); + } }); + const wrapper = mount( + + ); + const form = wrapper.ref('wrappedComponent').prop('form'); + expect(form.getFieldValue('user.name')).toBe('Benjy'); + expect(form.getFieldValue('user.age')).toBe(18); + expect(form.getFieldValue('agreement')).toBe(false); - it('returned value will replace current fields', () => { - const Test = createForm({ - withRef: true, - mapPropsToFields(props) { - return { - field1: createFormField({ - value: props.formState.field1, - }), - }; - }, - })(class extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - -
- ); - } - }); - const wrapper = mount(); - const form = wrapper.ref('wrappedComponent').prop('form'); - wrapper.find('input').last().simulate('change', { target: { value: '3' } }); - wrapper.setProps({ formState: { field1: '1' } }); - expect(form.getFieldValue('field1')).toBe('1'); - expect(form.getFieldValue('field2')).toBe(undefined); + wrapper.setProps({ formState: { userName: 'Benjy', userAge: 18, agreement: true } }); + expect(form.getFieldValue('user.name')).toBe('Benjy'); + expect(form.getFieldValue('user.age')).toBe(18); + expect(form.getFieldValue('agreement')).toBe(true); + }); + + it('returned value will replace current fields', () => { + const Test = createForm({ + withRef: true, + mapPropsToFields(props) { + return { + field1: createFormField({ + value: props.formState.field1, + }), + }; + }, + })(class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + +
+ ); + } }); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').last().simulate('change', { target: { value: '3' } }); + wrapper.setProps({ formState: { field1: '1' } }); + expect(form.getFieldValue('field1')).toBe('1'); + expect(form.getFieldValue('field2')).toBe(undefined); }); }); diff --git a/tests/getFieldProps.spec.js b/tests/getFieldProps.spec.js new file mode 100644 index 00000000..61a5d282 --- /dev/null +++ b/tests/getFieldProps.spec.js @@ -0,0 +1,103 @@ +/* eslint-disable no-undef, react/prop-types, react/no-multi-comp */ + +import React from 'react'; +import { mount } from 'enzyme'; +import createForm from '../src/createForm'; + +describe('initialValue', () => { + it('works', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ; + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + expect(form.getFieldValue('normal')).toBe('1'); + wrapper.find('input').simulate('change', { target: { value: '2' } }); + expect(form.getFieldValue('normal')).toBe('2'); + form.resetFields(); + expect(form.getFieldValue('normal')).toBe('1'); + }); +}); + +describe('getValueProps', () => { + it('works', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( + + ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').simulate('change', { target: { value: '2' } }); + expect(form.getFieldValue('normal')).toBe('2'); + expect(form.getFieldInstance('normal').value).toBe('21'); + }); +}); + +describe('getValueFromEvent', () => { + it('works', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( + + ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').simulate('change', { target: { value: '2' } }); + expect(form.getFieldValue('normal')).toBe('21'); + }); +}); + +describe('normalize', () => { + it('works', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + toUpper = (v) => { + return v && v.toUpperCase(); + } + render() { + const { getFieldProps } = this.props.form; + return ( + + ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').simulate('change', { target: { value: 'a' } }); + expect(form.getFieldValue('normal')).toBe('A'); + expect(form.getFieldInstance('normal').value).toBe('A'); + }); +}); diff --git a/tests/getValueFromEvent.spec.js b/tests/getValueFromEvent.spec.js deleted file mode 100644 index da23482f..00000000 --- a/tests/getValueFromEvent.spec.js +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-disable no-undef, react/prop-types */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -class Test extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return (
- -
); - } -} - -Test = createForm({ - withRef: true, -})(Test); - -describe('getValueFromEvent usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - it('works', () => { - form.getFieldInstance('normal').value = '3'; - Simulate.change(form.getFieldInstance('normal')); - expect(form.getFieldValue('normal')).toBe('32'); - expect(form.getFieldInstance('normal').value).toBe('321'); - form.resetFields(); - expect(form.getFieldValue('normal')).toBe('0'); - expect(form.getFieldInstance('normal').value).toBe('01'); - }); -}); diff --git a/tests/initialValue.spec.js b/tests/initialValue.spec.js deleted file mode 100644 index 71f652c5..00000000 --- a/tests/initialValue.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable no-undef, react/prop-types */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -class Test extends React.Component { - check = (rule, value, callback) => { - setTimeout(() => { - if (value === '1') { - callback(); - } else { - callback('must be 1'); - } - }, 100); - } - - render() { - const { getFieldProps } = this.props.form; - return (
- -
); - } -} - -Test = createForm({ - withRef: true, -})(Test); - -describe('initialValue usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - it('resetFields works', () => { - form.getFieldInstance('normal').value = '2'; - Simulate.change(form.getFieldInstance('normal')); - expect(form.getFieldValue('normal')).toBe('2'); - form.resetFields(); - expect(form.getFieldValue('normal')).toBe('1'); - }); -}); diff --git a/tests/normalize.spec.js b/tests/normalize.spec.js deleted file mode 100644 index 96429852..00000000 --- a/tests/normalize.spec.js +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable no-undef, react/prop-types */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -class Test extends React.Component { - upper = (v) => { - return v && v.toUpperCase(); - } - render() { - const { getFieldProps } = this.props.form; - return (
- -
); - } -} - -Test = createForm({ - withRef: true, -})(Test); - -describe('normalize usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - it('works', () => { - form.getFieldInstance('normal').value = 'a'; - Simulate.change(form.getFieldInstance('normal')); - expect(form.getFieldValue('normal')).toBe('A'); - expect(form.getFieldInstance('normal').value).toBe('A'); - }); -}); From 4472774db4a091d03d7b54545de59e66bac20186 Mon Sep 17 00:00:00 2001 From: Benjy Cui Date: Mon, 6 Nov 2017 15:52:46 +0800 Subject: [PATCH 5/7] feat: make nested fields work --- src/createBaseForm.js | 6 +- src/createFieldsStore.js | 143 ++++++----- tests/getFieldProps.spec.js | 61 +++++ tests/overview.spec.js | 478 +++++++++++++++--------------------- 4 files changed, 347 insertions(+), 341 deletions(-) diff --git a/src/createBaseForm.js b/src/createBaseForm.js index 109f1351..142aa891 100644 --- a/src/createBaseForm.js +++ b/src/createBaseForm.js @@ -246,7 +246,7 @@ function createBaseForm(option = {}, mixins = []) { if (onFieldsChange) { const changedFields = Object.keys(fields) .reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {}); - onFieldsChange(this.props, changedFields, this.fieldsStore.getAllFields()); + onFieldsChange(this.props, changedFields, this.fieldsStore.getNestedAllFields()); } this.forceUpdate(); }, @@ -401,7 +401,9 @@ function createBaseForm(option = {}, mixins = []) { validateFields(ns, opt, cb) { const { names, callback, options } = getParams(ns, opt, cb); - const fieldNames = names || this.fieldsStore.getValidFieldsName(); + const fieldNames = names ? + this.fieldsStore.getValidFieldsFullName(names) : + this.fieldsStore.getValidFieldsName(); const fields = fieldNames .filter(name => { const fieldMeta = this.fieldsStore.getFieldMeta(name); diff --git a/src/createFieldsStore.js b/src/createFieldsStore.js index bb302681..ecf4b4b2 100644 --- a/src/createFieldsStore.js +++ b/src/createFieldsStore.js @@ -7,16 +7,16 @@ import { class FieldsStore { constructor(fields) { - this.fields = flattenFields(fields, this.isFieldTypeLeaf); + this.fields = this.flattenFields(fields); this.fieldsMeta = {}; } updateFields(fields) { - this.fields = flattenFields(fields, this.isFieldTypeLeaf); + this.fields = this.flattenFields(fields); } - isFieldTypeLeaf(_, node) { - return isFormField(node); + flattenFields(fields) { + return flattenFields(fields, (_, node) => isFormField(node)); } flattenRegisteredFields(fields) { @@ -24,6 +24,19 @@ class FieldsStore { return flattenFields(fields, path => validFieldsName.includes(path)); } + setFieldsInitialValue = (initialValues) => { + const flattenedInitialValues = this.flattenRegisteredFields(initialValues); + const fieldsMeta = this.fieldsMeta; + Object.keys(flattenedInitialValues).forEach(name => { + if (fieldsMeta[name]) { + fieldsMeta[name] = { + ...fieldsMeta[name], + initialValue: flattenedInitialValues[name], + }; + } + }); + } + setFields(fields) { const fieldsMeta = this.fieldsMeta; const nowFields = { @@ -49,19 +62,33 @@ class FieldsStore { }); this.fields = nowFields; } + resetFields(ns) { - const newFields = {}; const { fields } = this; - const names = ns || Object.keys(fields); - names.forEach((name) => { + const names = ns ? + this.getValidFieldsFullName(ns) : + this.getValidFieldsName(); + return names.reduce((acc, name) => { const field = fields[name]; if (field && 'value' in field) { - newFields[name] = {}; + acc[name] = {}; } - }); - return newFields; + return acc; + }, {}); + } + + setFieldMeta(name, meta) { + this.fieldsMeta[name] = meta; } - getValueFromFieldsInternal(name, fields) { + + getFieldMeta(name) { + if (!this.fieldsMeta[name]) { + this.fieldsMeta[name] = {}; + } + return this.fieldsMeta[name]; + } + + getValueFromFields(name, fields) { const field = fields[name]; if (field && 'value' in field) { return field.value; @@ -69,13 +96,11 @@ class FieldsStore { const fieldMeta = this.fieldsMeta[name]; return fieldMeta && fieldMeta.initialValue; } - getValueFromFields(name, fields) { - return this.getValueFromFieldsInternal(name, fields); - } + getAllValues = () => { const { fieldsMeta, fields } = this; return Object.keys(fieldsMeta) - .reduce((acc, name) => set(acc, name, this.getValueFromFieldsInternal(name, fields)), {}); + .reduce((acc, name) => set(acc, name, this.getValueFromFields(name, fields)), {}); } getValidFieldsName() { @@ -83,6 +108,18 @@ class FieldsStore { return fieldsMeta ? Object.keys(fieldsMeta) : []; } + getValidFieldsFullName(maybePartialName) { + const maybePartialNames = Array.isArray(maybePartialName) ? + maybePartialName : [maybePartialName]; + return this.getValidFieldsName() + .filter(fullName => maybePartialNames.some(partialName => ( + fullName === partialName || ( + fullName.startsWith(partialName) && + ['.', '['].includes(fullName[partialName.length]) + ) + ))); + } + getFieldValuePropValue(fieldMeta) { const { name, getValueProps, valuePropName } = fieldMeta; const field = this.getField(name); @@ -112,7 +149,7 @@ class FieldsStore { .reduce((acc, field) => set(acc, field.name, createFormField(field)), {}); } - getAllFields() { + getNestedAllFields() { return Object.keys(this.fields) .reduce( (acc, name) => set(acc, name, createFormField(this.fields[name])), @@ -124,53 +161,47 @@ class FieldsStore { return this.getField(name)[member]; } - getFieldsValue = (names) => { + getNestedFields(names, getter) { const fields = names || this.getValidFieldsName(); - const allValues = {}; - fields.forEach((f) => { - set(allValues, f, this.getFieldValue(f)); - }); - return allValues; + return fields.reduce((acc, f) => set(acc, f, getter(f)), {}); } - getFieldValue = (name) => { - const { fields } = this; - return this.getValueFromFields(name, fields); + getNestedField(name, getter) { + const fullNames = this.getValidFieldsFullName(name); + if (fullNames.length === 1 && fullNames[0] === name) { + return getter(name); + } + const isArrayValue = fullNames[0][name.length] === '['; + const suffixNameStartIndex = isArrayValue ? name.length : name.length + 1; + return fullNames + .reduce( + (acc, fullName) => set( + acc, + fullName.slice(suffixNameStartIndex), + getter(fullName) + ), + isArrayValue ? [] : {} + ); } - getFieldsError = (names) => { - const fields = names || this.getValidFieldsName(); - const allErrors = {}; - fields.forEach((f) => { - set(allErrors, f, this.getFieldError(f)); - }); - return allErrors; - } - getFieldError = (name) => { - return getErrorStrs(this.getFieldMember(name, 'errors')); + getFieldsValue = (names) => { + return this.getNestedFields(names, this.getFieldValue); } - getFieldMeta(name) { - if (!this.fieldsMeta[name]) { - this.fieldsMeta[name] = {}; - } - return this.fieldsMeta[name]; + getFieldValue = (name) => { + const { fields } = this; + return this.getNestedField(name, (fullName) => this.getValueFromFields(fullName, fields)); } - setFieldMeta(name, meta) { - this.fieldsMeta[name] = meta; + getFieldsError = (names) => { + return this.getNestedFields(names, this.getFieldError); } - setFieldsInitialValue = (initialValues) => { - const fieldsMeta = this.fieldsMeta; - Object.keys(initialValues).forEach(name => { - if (fieldsMeta[name]) { - fieldsMeta[name] = { - ...fieldsMeta[name], - initialValue: initialValues[name], - }; - } - }); + getFieldError = (name) => { + return this.getNestedField( + name, + (fullName) => getErrorStrs(this.getFieldMember(fullName, 'errors')) + ); } isFieldValidating = (name) => { @@ -179,9 +210,7 @@ class FieldsStore { isFieldsValidating = (ns) => { const names = ns || this.getValidFieldsName(); - return names.some((n) => { - return this.isFieldValidating(n); - }); + return names.some((n) => this.isFieldValidating(n)); } isFieldTouched = (name) => { @@ -190,9 +219,7 @@ class FieldsStore { isFieldsTouched = (ns) => { const names = ns || this.getValidFieldsName(); - return names.some((n) => { - return this.isFieldTouched(n); - }); + return names.some((n) => this.isFieldTouched(n)); } clearField(name) { diff --git a/tests/getFieldProps.spec.js b/tests/getFieldProps.spec.js index 61a5d282..6cec3617 100644 --- a/tests/getFieldProps.spec.js +++ b/tests/getFieldProps.spec.js @@ -101,3 +101,64 @@ describe('normalize', () => { expect(form.getFieldInstance('normal').value).toBe('A'); }); }); + +describe('validate', () => { + it('works', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( + + ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + expect(form.getFieldValue('normal')).toBe(undefined); + wrapper.find('input').simulate('change', { target: { value: '' } }); + expect(form.getFieldValue('normal')).toBe(''); + expect(form.getFieldError('normal')).toBe(undefined); + wrapper.find('input').simulate('blur', { target: { value: '' } }); + expect(form.getFieldValue('normal')).toBe(''); + expect(form.getFieldError('normal')).toEqual(['normal is required']); + wrapper.find('input').simulate('blur', { target: { value: '1' } }); + expect(form.getFieldValue('normal')).toBe('1'); + expect(form.getFieldError('normal')).toBe(undefined); + }); + + it('suport jsx message', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( + 1, + }], + })} + /> + ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').simulate('change'); + expect(form.getFieldError('required').length).toBe(1); + expect(form.getFieldError('required')[0].type).toBe('b'); + }); +}); diff --git a/tests/overview.spec.js b/tests/overview.spec.js index 045672ba..920c6947 100644 --- a/tests/overview.spec.js +++ b/tests/overview.spec.js @@ -1,316 +1,232 @@ /* eslint-disable no-undef, react/prop-types, react/no-multi-comp */ import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; +import { mount } from 'enzyme'; import createForm from '../src/createForm'; -class Test extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- - - - - - - - - - - - - -
+describe('getFieldProps\' behaviors', () => { + it('collect value and relative getters', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + } ); - } -} - -Test = createForm({ - withRef: true, -})(Test); - -describe('overview usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - it('collect value', () => { - expect(form.getFieldValue('normal')).toBe(undefined); - Simulate.change(form.getFieldInstance('normal')); - expect(form.getFieldValue('normal')).toBe(''); - form.getFieldInstance('normal').value = '1'; - Simulate.change(form.getFieldInstance('normal')); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').at(0).simulate('change', { target: { value: '1' } }); expect(form.getFieldValue('normal')).toBe('1'); + wrapper.find('input').at(1).simulate('change', { target: { value: 'a' } }); + expect(form.getFieldValue('nested1.a[0]')).toBe('a'); + expect(form.getFieldValue('nested1')).toEqual({ a: ['a'] }); + wrapper.find('input').at(2).simulate('change', { target: { value: 'b' } }); + expect(form.getFieldValue('nested2[0].b')).toBe('b'); + expect(form.getFieldValue('nested2')).toEqual([{ b: 'b' }]); + + expect(form.getFieldsValue(['normal', 'nested1', 'nested2[0]'])) + .toEqual({ + normal: '1', + nested1: { a: ['a'] }, + nested2: [{ b: 'b' }], + }); }); - it.skip('collect nested array value', () => { - form.getFieldInstance('a[0][1].b.c[0]').value = '0'; - form.getFieldInstance('a[0][1].b.c[1]').value = '1'; - Simulate.change(form.getFieldInstance('a[0][1].b.c[0]')); - Simulate.change(form.getFieldInstance('a[0][1].b.c[1]')); - expect(form.getFieldValue('a')).toEqual([ - [undefined, { - b: { - c: ['0', '1'], - }, - }], - ]); - expect(form.getFieldValue('a[0][1].b.c[0]')).toBe('0'); - expect(form.getFieldValue('a[0][1].b.c[1]')).toBe('1'); + it('validate value and relative getters', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + + wrapper.find('input').at(0).simulate('change'); + expect(form.getFieldError('normal')).toEqual(['normal is required']); + wrapper.find('input').at(0).simulate('change', { target: { value: '1' } }); + expect(form.getFieldError('normal')).toBe(undefined); + + wrapper.find('input').at(1).simulate('change'); + expect(form.getFieldError('nested1.a[0]')).toEqual(['nested1.a[0] is required']); + expect(form.getFieldError('nested1')).toEqual({ a: [['nested1.a[0] is required']] }); + wrapper.find('input').at(1).simulate('change', { target: { value: '1' } }); + expect(form.getFieldError('nested1.a[0]')).toBe(undefined); + expect(form.getFieldError('nested1')).toEqual({ a: [undefined] }); + + wrapper.find('input').at(2).simulate('change'); + expect(form.getFieldError('nested2[0].b')).toEqual(['nested2[0].b is required']); + expect(form.getFieldError('nested2')).toEqual([{ b: ['nested2[0].b is required'] }]); + wrapper.find('input').at(2).simulate('change', { target: { value: '1' } }); + expect(form.getFieldError('nested2[0].b')).toBe(undefined); + expect(form.getFieldError('nested2')).toEqual([{ b: undefined }]); + + expect(form.getFieldsError(['normal', 'nested1', 'nested2[0]'])) + .toEqual({ + normal: undefined, + nested1: { a: [undefined] }, + nested2: [{ b: undefined }], + }); }); +}); - it.skip('collect nested value', () => { - form.getFieldInstance('foo.a.x').value = '1'; - form.getFieldInstance('foo.a.y').value = '2'; - form.getFieldInstance('foo.b[0]').value = '3'; - form.getFieldInstance('foo.b[1]').value = '4'; - Simulate.change(form.getFieldInstance('foo.a.x')); - Simulate.change(form.getFieldInstance('foo.a.y')); - Simulate.change(form.getFieldInstance('foo.b[0]')); - Simulate.change(form.getFieldInstance('foo.b[1]')); - expect(form.getFieldValue('foo')).toEqual({ - a: { - x: '1', - y: '2', - }, - b: ['3', '4'], +describe('createForm\'s form behavior', () => { + it('setFieldsInitialValue works', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + form.setFieldsInitialValue({ + normal: '1', + nested1: { a: ['2'] }, + nested2: [{ b: '3' }], + }); + expect(form.getFieldsValue()).toEqual({ + normal: '1', + nested1: { a: ['2'] }, + nested2: [{ b: '3' }], }); - expect(form.getFieldValue('foo.a.x')).toBe('1'); - expect(form.getFieldValue('foo.a.y')).toBe('2'); - expect(form.getFieldValue('foo.b[0]')).toBe('3'); - expect(form.getFieldValue('foo.b[1]')).toBe('4'); - }); - - it('validate value', () => { - expect(form.getFieldValue('required')).toBe(undefined); - Simulate.change(form.getFieldInstance('required')); - expect(form.getFieldValue('required')).toBe(''); - expect(form.getFieldError('required')).toEqual(['required is required']); - form.getFieldInstance('required').value = '1'; - Simulate.change(form.getFieldInstance('required')); - expect(form.getFieldValue('required')).toBe('1'); - expect(form.getFieldError('required')).toBe(undefined); }); + it('resetFields works', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); - it('validate trigger value', () => { - expect(form.getFieldValue('blurRequired')).toBe(undefined); - Simulate.change(form.getFieldInstance('blurRequired')); - expect(form.getFieldValue('blurRequired')).toBe(''); - expect(form.getFieldError('blurRequired')).toBe(undefined); - Simulate.blur(form.getFieldInstance('blurRequired')); - expect(form.getFieldValue('blurRequired')).toBe(''); - expect(form.getFieldError('blurRequired')).toEqual(['blurRequired is required']); - form.getFieldInstance('blurRequired').value = '1'; - Simulate.change(form.getFieldInstance('blurRequired')); - Simulate.blur(form.getFieldInstance('blurRequired')); - expect(form.getFieldValue('blurRequired')).toBe('1'); - expect(form.getFieldError('blurRequired')).toBe(undefined); + wrapper.find('input').at(0).simulate('change', { target: { value: '' } }); + expect(form.getFieldValue('normal')).toBe(''); + expect(form.getFieldError('normal')).toEqual(['normal is required']); + wrapper.find('input').at(1).simulate('change', { target: { value: '' } }); + expect(form.getFieldValue('nested1.a[0]')).toBe(''); + expect(form.getFieldError('nested1.a[0]')).toEqual(['nested1.a[0] is required']); + wrapper.find('input').at(2).simulate('change', { target: { value: '' } }); + expect(form.getFieldValue('nested2[0].b')).toBe(''); + expect(form.getFieldError('nested2[0].b')).toEqual(['nested2[0].b is required']); + form.resetFields(['normal', 'nested1', 'nested2[0]']); + expect(form.getFieldValue('normal')).toBe(undefined); + expect(form.getFieldError('normal')).toBe(undefined); + expect(form.getFieldValue('nested1.a[0]')).toBe(undefined); + expect(form.getFieldError('nested1.a[0]')).toBe(undefined); + expect(form.getFieldValue('nested2[0].b')).toBe(undefined); + expect(form.getFieldError('nested2[0].b')).toBe(undefined); }); - it('validateFields works for error', (callback) => { + it('validateFields works for errors', (callback) => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ; + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); form.validateFields((errors, values) => { - expect(Object.keys(errors).length).toBe(2); - expect(errors.required.errors.map(e => e.message)).toEqual(['required is required']); - expect(errors.blurRequired.errors.map(e => e.message)).toEqual(['blurRequired is required']); - expect(values.normal).toBe(undefined); - expect(values.blurRequired).toBe(undefined); - expect(values.required).toBe(undefined); + expect(errors).toEqual({ + normal: { + errors: [{ field: 'normal', message: 'normal is required' }], + }, + }); + expect(values).toEqual({ normal: undefined }); callback(); }); }); it('validateFields works for ok', (callback) => { - form.getFieldInstance('required').value = '2'; - form.getFieldInstance('blurRequired').value = '1'; - Simulate.change(form.getFieldInstance('blurRequired')); - Simulate.change(form.getFieldInstance('required')); + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ; + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + wrapper.find('input').simulate('change', { target: { value: '1' } }); form.validateFields((errors, values) => { - expect(errors).toBeFalsy(); - expect(values.normal).toBe(undefined); - expect(values.blurRequired).toBe('1'); - expect(values.required).toBe('2'); - expect(values.foo.a.x).toBe(undefined); - expect(values.foo.a.y).toBe(undefined); - expect(values.foo.b[0]).toBe(undefined); - expect(values.foo.b[1]).toBe(undefined); + expect(errors).toBe(null); + expect(values).toEqual({ normal: '1' }); callback(); }); }); - it('resetFields works', () => { - form.getFieldInstance('required').value = '1'; - Simulate.change(form.getFieldInstance('required')); - expect(form.getFieldValue('required')).toBe('1'); - expect(form.getFieldError('required')).toBe(undefined); - form.resetFields(); - expect(form.getFieldValue('required')).toBe(undefined); - expect(form.getFieldError('required')).toBe(undefined); - }); - - it('setFieldsInitialValue works', () => { - form.setFieldsInitialValue({ - normal: '4', - }); - form.getFieldInstance('normal').value = '2'; - Simulate.change(form.getFieldInstance('normal')); - expect(form.getFieldValue('normal')).toBe('2'); - form.resetFields(); - expect(form.getFieldValue('normal')).toBe('4'); - }); - - it.skip('setFieldsValue and setFieldsInitialValue for nested field works', () => { - form.setFieldsInitialValue({ - foo: { - a: { - x: '1', - y: '2', - }, - b: ['3', '4'], - }, - }); - form.setFieldsValue({ - foo: { - a: { - x: '5', - y: '6', + it('validateFields(names, callback) works', (callback) => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( +
+ + + +
+ ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + form.validateFields(['nested1', 'nested2[0]'], (errors, values) => { + expect(errors).toEqual({ + nested1: { + a: [{ + errors: [{ field: 'nested1.a[0]', message: 'nested1.a[0] is required' }], + }], }, - b: ['7', '8'], - }, - }); - Simulate.change(form.getFieldInstance('foo.a.x')); - Simulate.change(form.getFieldInstance('foo.a.y')); - Simulate.change(form.getFieldInstance('foo.b[0]')); - Simulate.change(form.getFieldInstance('foo.b[1]')); - expect(form.getFieldValue('foo')).toEqual({ - a: { - x: '5', - y: '6', - }, - b: ['7', '8'], - }); - form.setFieldsValue({ 'foo.a.x': '9' }); - Simulate.change(form.getFieldInstance('foo.a.x')); - expect(form.getFieldValue('foo')).toEqual({ - a: { - x: '9', - y: '6', - }, - b: ['7', '8'], - }); - form.resetFields(); - expect(form.getFieldValue('foo')).toEqual({ - a: { - x: '1', - y: '2', - }, - b: ['3', '4'], - }); - }); - - it.skip('setFieldsValue and setFieldsInitialValue for nested array works', () => { - form.setFieldsInitialValue({ - a: [ - [undefined, { - b: { - c: ['0', '1'], - }, - }], - ], - }); - form.setFieldsValue({ - a: [ - [undefined, { + nested2: [{ b: { - c: ['2', '3'], + errors: [{ field: 'nested2[0].b', message: 'nested2[0].b is required' }], }, }], - ], + }); + expect(values).toEqual({ + nested1: { a: [undefined] }, + nested2: [{ b: undefined }], + }); + callback(); }); - Simulate.change(form.getFieldInstance('a[0][1].b.c[0]')); - Simulate.change(form.getFieldInstance('a[0][1].b.c[1]')); - expect(form.getFieldValue('a')).toEqual([ - [undefined, { - b: { - c: ['2', '3'], - }, - }], - ]); - form.setFieldsValue({ 'a[0][1].b.c[0]': '9' }); - Simulate.change(form.getFieldInstance('a[0][1].b.c[0]')); - expect(form.getFieldValue('a')).toEqual([ - [undefined, { - b: { - c: ['9', '3'], - }, - }], - ]); - form.resetFields(); - expect(form.getFieldValue('a')).toEqual([ - [undefined, { - b: { - c: ['0', '1'], - }, - }], - ]); - }); - - it('jsx works', () => { - class TestComponent extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return ( -
- 1, - }], - })} - /> -
- ); - } - } - TestComponent = createForm({ - withRef: true, - })(TestComponent); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - Simulate.change(form.getFieldInstance('required')); - expect(form.getFieldError('required').length).toBe(1); - expect(form.getFieldError('required')[0].type).toBe('b'); }); }); From 5f7413f95e9b8ec994bc82f5d5e1c8d627604156 Mon Sep 17 00:00:00 2001 From: Benjy Cui Date: Mon, 6 Nov 2017 18:28:56 +0800 Subject: [PATCH 6/7] chore: test manually and fix some bugs --- examples/checkbox-group.html | 1 - examples/checkbox-group.js | 85 --------------------------- examples/data-binding-form.js | 7 ++- examples/data-binding.js | 6 +- examples/radio-group.html | 1 - examples/radio-group.js | 91 ----------------------------- examples/redux.js | 4 +- examples/router.js | 7 ++- index.js | 4 +- src/createFieldsStore.js | 9 ++- src/index.js | 3 +- src/utils.js | 2 + tests/checkboxGroup.spec.js | 97 ------------------------------ tests/getFieldProps.spec.js | 29 +++++++++ tests/overview.spec.js | 11 ++++ tests/radioGroup.spec.js | 107 ---------------------------------- tests/utils.spec.js | 20 +++++++ 17 files changed, 89 insertions(+), 395 deletions(-) delete mode 100644 examples/checkbox-group.html delete mode 100644 examples/checkbox-group.js delete mode 100644 examples/radio-group.html delete mode 100644 examples/radio-group.js delete mode 100644 tests/checkboxGroup.spec.js delete mode 100644 tests/radioGroup.spec.js diff --git a/examples/checkbox-group.html b/examples/checkbox-group.html deleted file mode 100644 index b3a42524..00000000 --- a/examples/checkbox-group.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/checkbox-group.js b/examples/checkbox-group.js deleted file mode 100644 index 2874e921..00000000 --- a/examples/checkbox-group.js +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint react/no-multi-comp:0, no-console:0 */ - -import createDOMForm from 'rc-form/src/createDOMForm'; -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ReactDOM from 'react-dom'; -import { regionStyle } from './styles'; - -class Form extends Component { - static propTypes = { - form: PropTypes.object, - }; - - onSubmit = (e) => { - console.log('submit'); - e.preventDefault(); - this.props.form.validateFieldsAndScroll({ scroll: { offsetTop: 20 } }, (error, values) => { - if (!error) { - console.log('ok', values); - } else { - console.log('error', error, values); - } - }); - } - - reset = (e) => { - e.preventDefault(); - this.props.form.resetFields(); - } - - render() { - const { form } = this.props; - const { getFieldProps } = form; - return (
-

checkbox-group

-
-
-
checkbox-group
-
- - -
- - -
-
- -
- -   - -
-
-
); - } -} - -const NewForm = createDOMForm()(Form); - -ReactDOM.render(, document.getElementById('__react-content')); diff --git a/examples/data-binding-form.js b/examples/data-binding-form.js index e41d57d2..86c82ad3 100644 --- a/examples/data-binding-form.js +++ b/examples/data-binding-form.js @@ -1,6 +1,6 @@ /* eslint react/no-multi-comp:0, no-console:0 */ -import { createForm } from 'rc-form'; +import { createForm, createFormField } from 'rc-form'; import React from 'react'; import PropTypes from 'prop-types'; import { createRootContainer, createContainer } from 'react-data-binding'; @@ -87,7 +87,10 @@ class Form extends React.Component { Form = createForm({ mapPropsToFields(props) { console.log('mapPropsToFields', props); - return props.formState; + return { + on: createFormField(props.formState.on), + email: createFormField(props.formState.email), + }; }, onFieldsChange(props, fields) { console.log('onFieldsChange', fields); diff --git a/examples/data-binding.js b/examples/data-binding.js index e0ac9fd5..ca95b586 100644 --- a/examples/data-binding.js +++ b/examples/data-binding.js @@ -1,6 +1,6 @@ /* eslint react/no-multi-comp:0, no-console:0 */ -import { createForm } from 'rc-form'; +import { createForm, createFormField } from 'rc-form'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { createRootContainer, createContainer } from 'react-data-binding'; @@ -85,7 +85,9 @@ const NewForm = createContainer((state) => { })(createForm({ mapPropsToFields(props) { console.log('mapPropsToFields', props); - return props.formState; + return { + email: props.formState && createFormField(props.formState.email), + }; }, onFieldsChange(props, fields) { console.log('onFieldsChange', fields); diff --git a/examples/radio-group.html b/examples/radio-group.html deleted file mode 100644 index b3a42524..00000000 --- a/examples/radio-group.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/radio-group.js b/examples/radio-group.js deleted file mode 100644 index 0a2f8d77..00000000 --- a/examples/radio-group.js +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint react/no-multi-comp:0, no-console:0 */ - -import createDOMForm from 'rc-form/src/createDOMForm'; -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ReactDOM from 'react-dom'; -import { regionStyle } from './styles'; - -class Form extends Component { - static propTypes = { - form: PropTypes.object, - }; - - onSubmit = (e) => { - console.log('submit'); - e.preventDefault(); - this.props.form.validateFieldsAndScroll({ scroll: { offsetTop: 20 } }, (error, values) => { - if (!error) { - console.log('ok', values); - } else { - console.log('error', error, values); - } - }); - }; - - reset = (e) => { - e.preventDefault(); - this.props.form.resetFields(); - }; - - render() { - const { form } = this.props; - const { getFieldProps } = form; - return (
-

radio-group

-
-
-
radio-group
-
- - -
- - -
-
- -
- -   - -
-
-
); - } -} - -const NewForm = createDOMForm()(Form); - -ReactDOM.render(, document.getElementById('__react-content')); diff --git a/examples/redux.js b/examples/redux.js index 9da95fbc..b46273f9 100644 --- a/examples/redux.js +++ b/examples/redux.js @@ -1,6 +1,6 @@ /* eslint react/no-multi-comp:0, no-console:0 */ -import { createForm } from 'rc-form'; +import { createForm, createFormField } from 'rc-form'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { combineReducers } from 'redux'; @@ -95,7 +95,7 @@ const NewForm = connect((state) => { mapPropsToFields(props) { console.log('mapPropsToFields', props); return { - email: props.formState.email, + email: createFormField(props.formState.email), }; }, onFieldsChange(props, fields) { diff --git a/examples/router.js b/examples/router.js index c73e22ae..461bca7d 100644 --- a/examples/router.js +++ b/examples/router.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { createForm } from 'rc-form'; +import { createForm, createFormField } from 'rc-form'; import { render } from 'react-dom'; import { Router } from 'react-router'; import { createRootContainer, createContainer } from 'react-data-binding'; @@ -142,7 +142,10 @@ class Form extends React.Component { Form = createForm({ mapPropsToFields(props) { - return props.formState; + return props.formState ? { + city: createFormField(props.formState.city), + user: createFormField(props.formState.user), + } : {}; }, onFieldsChange(props, fields) { props.setStoreState({ diff --git a/index.js b/index.js index 8f4d2b0c..21825bf0 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,3 @@ // export this package's api -import { createForm } from './src/'; -export { createForm }; +import { createForm, createFormField } from './src/'; +export { createForm, createFormField }; diff --git a/src/createFieldsStore.js b/src/createFieldsStore.js index ecf4b4b2..fbf1baa9 100644 --- a/src/createFieldsStore.js +++ b/src/createFieldsStore.js @@ -105,7 +105,9 @@ class FieldsStore { getValidFieldsName() { const { fieldsMeta } = this; - return fieldsMeta ? Object.keys(fieldsMeta) : []; + return fieldsMeta ? + Object.keys(fieldsMeta).filter(name => !fieldsMeta[name].hidden) : + []; } getValidFieldsFullName(maybePartialName) { @@ -168,7 +170,10 @@ class FieldsStore { getNestedField(name, getter) { const fullNames = this.getValidFieldsFullName(name); - if (fullNames.length === 1 && fullNames[0] === name) { + if ( + fullNames.length === 0 || // Not registered + (fullNames.length === 1 && fullNames[0] === name) // Name already is full name. + ) { return getter(name); } const isArrayValue = fullNames[0][name.length] === '['; diff --git a/src/index.js b/src/index.js index a6e46ce0..e1ef8eb3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ // export this package's api import createForm from './createForm'; -export { createForm }; +import createFormField from './createFormField'; +export { createForm, createFormField }; diff --git a/src/utils.js b/src/utils.js index e1e61351..c295e9ee 100644 --- a/src/utils.js +++ b/src/utils.js @@ -22,6 +22,8 @@ export function flattenArray(arr) { export function treeTraverse(path = '', tree, isLeafNode, callback) { if (isLeafNode(path, tree)) { callback(path, tree); + } else if (tree === undefined) { + return; } else if (Array.isArray(tree)) { tree.forEach((subTree, index) => treeTraverse( `${path}[${index}]`, diff --git a/tests/checkboxGroup.spec.js b/tests/checkboxGroup.spec.js deleted file mode 100644 index 807e3d05..00000000 --- a/tests/checkboxGroup.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable no-undef, react/prop-types */ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -class Test extends React.Component { - render() { - const { getFieldProps } = this.props.form; - return (
-

- - -
- - -

-
); - } -} - -Test = createForm({ - withRef: true, -})(Test); - -describe('checkbox-group usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - it.skip('collect value', () => { - expect(form.getFieldValue('normal')).toEqual({ a: false, b: undefined }); - form.getFieldInstance('normal.a').checked = true; - Simulate.change(form.getFieldInstance('normal.a')); - expect(form.getFieldValue('normal')).toEqual({ a: true, b: undefined }); - form.getFieldInstance('normal.b').checked = true; - Simulate.change(form.getFieldInstance('normal.b')); - expect(form.getFieldValue('normal')).toEqual({ a: true, b: 'b' }); - expect(form.getFieldInstance('normal.a').checked).toBe(true); - }); - - it('validateFields works for ok', (callback) => { - form.getFieldInstance('normal.a').checked = true; - Simulate.change(form.getFieldInstance('normal.a')); - form.validateFields((errors, values) => { - expect(errors).toBeFalsy(); - expect(values.normal).toEqual({ a: true, b: undefined }); - callback(); - }); - }); - - it.skip('resetFields works', () => { - expect(form.getFieldValue('normal')).toEqual({ a: false, b: undefined }); - form.getFieldInstance('normal.a').checked = true; - Simulate.change(form.getFieldInstance('normal.a')); - expect(form.getFieldValue('normal')).toEqual({ a: true, b: undefined }); - form.resetFields(); - expect(form.getFieldValue('normal')).toEqual({ a: false, b: undefined }); - }); -}); diff --git a/tests/getFieldProps.spec.js b/tests/getFieldProps.spec.js index 6cec3617..947ac689 100644 --- a/tests/getFieldProps.spec.js +++ b/tests/getFieldProps.spec.js @@ -162,3 +162,32 @@ describe('validate', () => { expect(form.getFieldError('required')[0].type).toBe('b'); }); }); + +describe('hidden', () => { + it('works', (callback) => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { + const { getFieldProps } = this.props.form; + return ( + + ); + } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + expect(form.getFieldsValue()).toEqual({}); + form.validateFields((errors, values) => { + expect(errors).toBe(null); + expect(values).toEqual({}); + callback(); + }); + }); +}); diff --git a/tests/overview.spec.js b/tests/overview.spec.js index 920c6947..ac86805b 100644 --- a/tests/overview.spec.js +++ b/tests/overview.spec.js @@ -86,6 +86,17 @@ describe('getFieldProps\' behaviors', () => { }); describe('createForm\'s form behavior', () => { + it('getFieldValue should return `undefined` when `name` is not registered', () => { + const Test = createForm({ withRef: true })( + class extends React.Component { + render() { return null; } + } + ); + const wrapper = mount(); + const form = wrapper.ref('wrappedComponent').prop('form'); + expect(form.getFieldValue('not-registered')).toBe(undefined); + }); + it('setFieldsInitialValue works', () => { const Test = createForm({ withRef: true })( class extends React.Component { diff --git a/tests/radioGroup.spec.js b/tests/radioGroup.spec.js deleted file mode 100644 index 497967d4..00000000 --- a/tests/radioGroup.spec.js +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint-disable no-undef, react/prop-types */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import createForm from '../src/createForm'; - -class Test extends React.Component { - render() { - const { getFieldProps } = this.props.form; - getFieldProps('normal', { - initialValue: 'b', - }); - return (
-

- - -
- - -

-
); - } -} - -Test = createForm({ - withRef: true, -})(Test); - -describe('radio-group usage', () => { - let container; - let component; - let form; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - component = ReactDOM.render(, container); - component = component.refs.wrappedComponent; - form = component.props.form; - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }); - - it.skip('collect value', () => { - expect(form.getFieldValue('normal')).toEqual('b'); - form.getFieldInstance('normal.a').checked = true; - Simulate.change(form.getFieldInstance('normal.a')); - expect(form.getFieldValue('normal')).toEqual('a'); - form.getFieldInstance('normal.b').checked = true; - Simulate.change(form.getFieldInstance('normal.b')); - expect(form.getFieldValue('normal')).toEqual('b'); - expect(form.getFieldInstance('normal.a').checked).toBe(false); - }); - - it.skip('validateFields works for ok', (callback) => { - form.getFieldInstance('normal.a').checked = true; - Simulate.change(form.getFieldInstance('normal.a')); - form.validateFields((errors, values) => { - expect(errors).toBeFalsy(); - expect(values.normal).toBe('a'); - callback(); - }); - }); - - it.skip('resetFields works', () => { - expect(form.getFieldValue('normal')).toEqual('b'); - form.getFieldInstance('normal.a').checked = true; - Simulate.change(form.getFieldInstance('normal.a')); - expect(form.getFieldValue('normal')).toEqual('a'); - form.resetFields(); - expect(form.getFieldValue('normal')).toBe('b'); - }); -}); diff --git a/tests/utils.spec.js b/tests/utils.spec.js index 97ebabfe..d0ede2a1 100644 --- a/tests/utils.spec.js +++ b/tests/utils.spec.js @@ -30,4 +30,24 @@ describe('utils.flattenFields', () => { 'user.hobbies[1]': { value: 'Roller Skating' }, }); }); + + it('just ignore `undefined` when `undefined` is not a valid leaf node', () => { + const fields = { + user: { + name: undefined, + age: undefined, + hobbies: [ + undefined, + createFormField({ + value: 'Roller Skating', + }), + ], + }, + }; + + expect(flattenFields(fields, (_, node) => isFormField(node))) + .toEqual({ + 'user.hobbies[1]': { value: 'Roller Skating' }, + }); + }); }); From 82ddab1339699d2da27c5d987b84cf606ff80c1d Mon Sep 17 00:00:00 2001 From: Benjy Cui Date: Tue, 7 Nov 2017 09:33:50 +0800 Subject: [PATCH 7/7] docs: add changelog for 2.0.0 --- HISTORY.md | 29 +++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 30 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 3a37aae0..0e4cc508 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,35 @@ # History ---- +## 2.0.0 / 2017-11-07 + +- Remove `option.exclusive` of `getFieldProps` and `getFieldDecorator`, just use something like [`antd.Radio.Group`](https://ant.design/components/radio/#components-radio-demo-radiogroup) or [`antd.Checkbox.Group`](https://ant.design/components/checkbox/#components-checkbox-demo-group) as workaround. +- Add `createFormField`, and you must use it to wrap field data in `option.mapPropsToFields` of `createForm` or `createDOMForm`: + Before rc-form@2.0.0: + ```jsx + import { createForm } from 'rc-form'; + createFrom({ + mapPropsToFields() { + return { + name: { value: 'rc-form' }, + }; + }, + }) + ``` + After rc-form@2.0.0: + ```jsx + import { createForm, createFormField } from 'rc-form'; + createFrom({ + mapPropsToFields() { + return { + name: createFormField({ value: 'rc-form' }), + }; + }, + }) + ``` +- Deprecate `form.isSubmitting` and `form.submit`, just handle submit status in your own code. + + ## 1.4.0 / 2017-06-13 - support wrappedComponentRef and deprecate withRef [#87](https://github.com/react-component/form/pull/87) diff --git a/README.md b/README.md index b18e52ee..b5d75666 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,7 @@ This input's unique name. | option.validate | | Object[] | - | | option.validate[n].trigger | Event which is listened to validate. Set to falsy to only validate when call props.validateFields. | String|String[] | 'onChange' | | option.validate[n].rules | Validator rules. see: [async-validator](https://github.com/yiminghe/async-validator) | Object[] | - | +| option.hidden | Ignore current field while validating or gettting fields | boolean | false | ##### Default value of `getValueFromEvent`