diff --git a/docs/field/demo/topath-defaults.md b/docs/field/demo/topath-defaults.md deleted file mode 100644 index 49405ba251..0000000000 --- a/docs/field/demo/topath-defaults.md +++ /dev/null @@ -1,74 +0,0 @@ -# 结构化解析 - -- order: 12 - -使用 parseName 时设置默认值。 -输入名称是 “值” 道具内的路径 - -:::lang=en-us -# Parse Array or Object - -- order: 12 - -Set default values when using parseName. -Input name is path inside `values` prop - -::: ---- - -````jsx -import {Input, Button, Field, Form } from '@alifd/next'; - - - -class Demo extends React.Component { - field = new Field(this, { - parseName: true, - values: { - aaa: ['xxx', 'yyy'], - bbb: { - ccc: 'value c' - } - } - }); - - _handleValidate = () => { - const { - field: { - validate - } - } = this; - - validate(['bbb.ccc']); - }; - - render() { - const { - field, - field: { - init, - } - } = this; - - console.log(this); - - return (
- - - - - - - -
); - } -} - -ReactDOM.render(, mountNode); -```` - -````css -.demo .next-btn { - margin-right: 5px; -} -```` diff --git a/docs/field/demo/topath.md b/docs/field/demo/topath.md index fd3a5e8fda..7d8cdde19e 100644 --- a/docs/field/demo/topath.md +++ b/docs/field/demo/topath.md @@ -25,7 +25,13 @@ import { Input, Button, Field } from '@alifd/next'; class App extends React.Component { field = new Field(this, { - parseName: true + parseName: true, + values: { + objWithDefaults: { + a: 1, + b: 2 + } + } }); onGetValue() { @@ -43,7 +49,7 @@ class App extends React.Component { } render() { - const { init, reset } = this.field; + const { init, reset, resetToDefault } = this.field; return (

Object transfer

@@ -57,6 +63,11 @@ class App extends React.Component { arr.1:

+

Object with Defaults

+ objWithDefaults.a:   + objWithDefaults.b: +

+ result:
{JSON.stringify(this.field.getValues(), null, 2)}
@@ -65,6 +76,7 @@ class App extends React.Component { +
); } } diff --git a/src/field/index.js b/src/field/index.js index ec6284c5aa..96b0c5927d 100644 --- a/src/field/index.js +++ b/src/field/index.js @@ -8,6 +8,7 @@ import { getParams, setIn, getIn, + deleteIn, mapValidateRules, } from './utils'; @@ -29,7 +30,9 @@ class Field { this.fieldsMeta = {}; this.cachedBind = {}; this.instance = {}; - this.initValue = options.values || {}; + // holds constructor values. Used for setting field defaults on init if no other value or initValue is passed. + // Also used caching values when using `parseName: true` before a field is initialized + this.values = options.values || {}; this.options = Object.assign( { @@ -89,6 +92,8 @@ class Field { getValueFromEvent = null, autoValidate = true, } = fieldOption; + const { parseName } = this.options; + const originalProps = Object.assign({}, props, rprops); const defaultValueName = `default${valueName[0].toUpperCase()}${valueName.slice( 1 @@ -100,11 +105,10 @@ class Field { defaultValue = initValue; } else if (originalProps[defaultValueName]) { defaultValue = originalProps[defaultValueName]; - } else if (this.options.parseName) { - defaultValue = getIn(this.initValue, name); + } else if (parseName) { + defaultValue = getIn(this.values, name); } else { - defaultValue = - (this.initValue && this.initValue[name]) || undefined; + defaultValue = (this.values && this.values[name]) || undefined; } Object.assign(field, { @@ -125,6 +129,11 @@ class Field { if (!('value' in field)) { field.value = defaultValue; } + if (parseName && !getIn(this.values, name)) { + this.values = setIn(this.values, name, field.value); + } else if (!parseName && !this.values[name]) { + this.values[name] = field.value; + } // Component props const inputProps = { @@ -201,6 +210,12 @@ class Field { ? field.getValueFromEvent.apply(this, others) : getValueFromEvent(e); + if (this.options.parseName) { + this.values = setIn(this.values, name, field.value); + } else { + this.values[name] = field.value; + } + this._resetError(name); // validate while onChange @@ -248,14 +263,19 @@ class Field { const cache = this.fieldsMeta[name]; this._setCache(name, key, cache); // after destroy, delete data - delete this.fieldsMeta[name]; delete this.instance[name]; + this.remove(name); return; } // 2. _saveRef(B, ref) (eg: same name but different compoent may be here) if (autoUnmount && !this.fieldsMeta[name]) { this.fieldsMeta[name] = this._getCache(name, key); + this.setValue( + name, + this.fieldsMeta[name] && this.fieldsMeta[name].value, + false + ); } // only one time here @@ -312,45 +332,39 @@ class Field { } getValue(name) { - const field = this._get(name); - - if (field && 'value' in field) { - return field.value; + if (this.options.parseName) { + return getIn(this.values, name); } - - return undefined; + return this.values[name]; } /** * 1. get values by names. - * 2. ignore disabled value. + * 2. If no names passed, return shallow copy of `field.values` * @param {Array} names */ getValues(names) { - const fields = names || this.getNames(); - let allValues = {}; + const allValues = {}; + + if (names && names.length) { + names.forEach(name => { + allValues[name] = this.getValue(name); + }); + } else { + Object.assign(allValues, this.values); + } - fields.forEach(f => { - if (f.disabled) { - return; - } - if (!this.options.parseName) { - allValues[f] = this.getValue(f); - } else { - allValues = setIn(allValues, f, this.getValue(f)); - } - }); return allValues; } setValue(name, value, reRender = true) { if (name in this.fieldsMeta) { this.fieldsMeta[name].value = value; + } + if (this.options.parseName) { + this.values = setIn(this.values, name, value); } else { - // if not exist, then new one - this.fieldsMeta[name] = { - value, - }; + this.values[name] = value; } reRender && this._reRender(); } @@ -361,11 +375,22 @@ class Field { this.setValue(name, fieldsValue[name], false); }); } else { + // NOTE: this is a shallow merge + // Ex. we have two values a.b.c=1 ; a.b.d=2, and use setValues({a:{b:{c:3}}}) , then because of shallow merge a.b.d will be lost, we will get only {a:{b:{c:3}}} + this.values = Object.assign({}, this.values, fieldsValue); const fields = this.getNames(); fields.forEach(name => { - const value = getIn(fieldsValue, name); + const value = getIn(this.values, name); if (value !== undefined) { - this.setValue(name, value, false); + // copy over values that are in this.values + this.fieldsMeta[name].value = value; + } else { + // if no value then copy values from fieldsMeta to keep initialized component data + this.values = setIn( + this.values, + name, + this.fieldsMeta[name].value + ); } }); } @@ -586,9 +611,12 @@ class Field { let changed = false; const names = ns || Object.keys(this.fieldsMeta); + + if (!ns) { + this.values = {}; + } names.forEach(name => { const field = this._get(name); - this.getValue(name); if (field) { changed = true; @@ -598,6 +626,12 @@ class Field { delete field.errors; delete field.rules; delete field.rulesMap; + + if (this.options.parseName) { + this.values = setIn(this.values, name, field.value); + } else { + this.values[name] = field.value; + } } }); @@ -641,11 +675,20 @@ class Field { if (typeof ns === 'string') { ns = [ns]; } + if (!ns) { + this.values = {}; + } + const names = ns || Object.keys(this.fieldsMeta); names.forEach(name => { if (name in this.fieldsMeta) { delete this.fieldsMeta[name]; } + if (this.options.parseName) { + this.values = deleteIn(this.values, name); + } else { + delete this.values[name]; + } }); } @@ -660,12 +703,14 @@ class Field { return; } + // regex to match field names in the same target array const reg = keyMatch.replace('{index}', '(\\d+)'); const keyReg = new RegExp(`^${reg}$`); let list = []; const names = this.getNames(); names.forEach(n => { + // is name in the target array? const ret = keyReg.exec(n); if (ret) { const index = parseInt(ret[1]); @@ -684,12 +729,20 @@ class Field { if (list.length > 0 && list[0].index === startIndex + 1) { list.forEach(l => { const n = keyMatch.replace('{index}', l.index - 1); - this.fieldsMeta[n] = this.fieldsMeta[l.name]; + const v = this.getValue(l.name); + this.setValue(n, v, false); }); + this.remove(list[list.length - 1].name); - delete this.fieldsMeta[list[list.length - 1].name]; + let parentName = keyMatch.replace('.{index}', ''); + parentName = parentName.replace('[{index}]', ''); + const parent = this.getValue(parentName); - this._reRender(); + if (parent) { + // if parseName=true then parent is an Array object but does not know an element was removed + // this manually decrements the array length + parent.length--; + } } } diff --git a/src/field/utils.js b/src/field/utils.js index 1ceda8ae89..ce4ac31ff2 100644 --- a/src/field/utils.js +++ b/src/field/utils.js @@ -119,6 +119,32 @@ export function getIn(state, name) { return result; } +export function deleteIn(state, name) { + if (!state) { + return; + } + + const path = name + .replace(/\[/, '.') + .replace(/\]/, '') + .split('.'); + const length = path.length; + if (!length) { + return state; + } + + let result = state; + for (let i = 0; i < length && !!result; ++i) { + if (i === length - 1) { + delete result[path[i]]; + } else { + result = result[path[i]]; + } + } + + return state; +} + function validateMap(rulesMap, rule, defaultTrigger) { const nrule = Object.assign({}, rule); diff --git a/test/field/index-spec.js b/test/field/index-spec.js index 994c4980ea..b2350c9108 100644 --- a/test/field/index-spec.js +++ b/test/field/index-spec.js @@ -334,6 +334,56 @@ describe('field', () => { done(); }); + it('should return `undefined` for `getValue` on uninitialized field', function() { + const field = new Field(this); + assert.equal(field.getValue('input'), undefined); + }); + + it('should return empty object for `getValues` on uninitialized field', function() { + const field = new Field(this); + assert.equal(Object.keys(field.getValues()).length, 0); + }); + + it('should set value with `setValue` on uninitialized field', function() { + const field = new Field(this); + field.setValue('input', 1) + field.init('input'); + assert.equal(field.getValue('input'), 1); + }); + + it('should set value with `setValues` on uninitialized field', function() { + const field = new Field(this); + field.setValues({input: 1}) + field.init('input'); + assert.equal(field.getValue('input'), 1); + }); + + it('should return value from `setValue` when calling `getValue` on uninitialized field', function() { + const field = new Field(this); + field.setValue('input', 1) + assert.equal(field.getValue('input'), 1); + }); + + it('should return value from `setValue` when calling `getValues` on uninitialized field', function() { + const field = new Field(this); + field.setValue('input', 1) + assert.equal(field.getValues().input, 1); + }); + + it('should return values from `setValue` and init when calling `getValues`', function() { + const field = new Field(this); + field.setValue('input', 1) + field.init('input2', {initValue: 2}) + assert.deepEqual(field.getValues(), { input: 1, input2: 2}); + }); + + it('should return `setValue` value instead of initValue', function() { + const field = new Field(this); + field.setValue('input', 1) + field.init('input', {initValue: 2}) + assert.deepEqual(field.getValues(), { input: 1 }); + }); + it('setError & setErrors & getError & getErrors', function(done) { const field = new Field(this); field.setError('input', 'error1'); @@ -466,22 +516,46 @@ describe('field', () => { done(); }); }); + + describe('reset', function() { + it('should set value to `undefined` on `reset()` if init with `initValue`', function() { + const field = new Field(this); + field.init('input', { initValue: '1' }); + + field.reset(); + assert(field.getValue('input') === undefined); + }); - it('reset', function(done) { - const field = new Field(this); - field.init('input', { initValue: '1' }); - - field.reset(); - assert(field.getValue('input') === undefined); - - field.init('input2', { initValue: '4' }); - field.setValue('input2', '33'); - - field.resetToDefault(); - assert(field.getValue('input2') === '4'); + it('should set only named value to `undefined` on `reset()` if init with `initValue`', function() { + const field = new Field(this); + field.init('input', { initValue: '1' }); + field.init('input2', { initValue: '2' }); + + field.reset('input'); + assert.deepEqual(field.getValues(), { input: undefined, input2: '2'}); + }); + + it('should set value to `initValue` on `resetToDefaults()` if init with `initValue`', function() { + const field = new Field(this); + field.init('input', { initValue: '4' }); + field.setValue('input', '33'); + + field.resetToDefault(); + assert(field.getValue('input') === '4'); + }); - done(); + it('should set only named value to `initValue` on `resetToDefaults()` if init with `initValue`', function() { + const field = new Field(this); + field.init('input', { initValue: '4' }); + field.setValue('input', '33'); + field.init('input2', { initValue: '4' }); + field.setValue('input2', '33'); + + field.resetToDefault('input'); + assert.deepEqual(field.getValues(), { input: '4', input2: '33'}); + }); }); + it('remove', function(done) { const field = new Field(this); field.init('input', { initValue: 1 }); @@ -498,24 +572,58 @@ describe('field', () => { done(); }); - it('spliceArray', function(done) { - const field = new Field(this); - field.init('input.0', { initValue: 0 }); - field.init('input.1', { initValue: 1 }); - field.init('input.2', { initValue: 2 }); + describe('spliceArray', function() { + it('should remove the middle field item', () => { + const field = new Field(this); + field.init('input.0', { initValue: 0 }); + field.init('input.1', { initValue: 1 }); + field.init('input.2', { initValue: 2 }); + + field.spliceArray('input.{index}', 1); + + assert(field.getValue('input.0') === 0); + assert(field.getValue('input.1') === 2); + assert(field.getValue('input.2') === undefined); + }); - field.spliceArray('input.{index}', 1); + it('should remove the first 2 field items', () => { + const field = new Field(this); + field.init('input.0', { initValue: 0 }); + field.init('input.1', { initValue: 1 }); + field.init('input.2', { initValue: 2 }); + + field.spliceArray('input.{index}', 1); + field.spliceArray('input.{index}', 0); + assert(field.getValue('input.0') === 2); + assert(field.getValue('input.1') === undefined); + assert(field.getValue('input.2') === undefined); + }); - assert(field.getValue('input.0') === 0); - assert(field.getValue('input.1') === 2); - assert(field.getValue('input.2') === undefined); + it('should make no change `keymatch` does not contain `{index}', () => { + const field = new Field(this); + field.init('input.0', { initValue: 0 }); + field.init('input.1', { initValue: 1 }); + field.init('input.2', { initValue: 2 }); - field.spliceArray('input.{index}', 0); - assert(field.getValue('input.0') === 2); - assert(field.getValue('input.1') === undefined); - assert(field.getValue('input.2') === undefined); + field.spliceArray('input', 0); + assert(field.getValue('input.0') === 0); + assert(field.getValue('input.1') === 1); + assert(field.getValue('input.2') === 2); + }); - done(); + it('should remove the middle field item when parseName=true', () => { + const field = new Field(this, { parseName: true }); + field.init('input.0', { initValue: 0 }); + field.init('input.1', { initValue: 1 }); + field.init('input.2', { initValue: 2 }); + + field.spliceArray('input.{index}', 1); + + assert(field.getValue('input.0') === 0); + assert(field.getValue('input.1') === 2); + assert(field.getValue('input.2') === undefined); + assert(field.getValue('input').length === 2); + }); }); }); }); diff --git a/test/field/options-spec.js b/test/field/options-spec.js index 38bb40fc42..47d7ea271e 100644 --- a/test/field/options-spec.js +++ b/test/field/options-spec.js @@ -171,7 +171,6 @@ describe('options', () => { input: inputValue }, }); - field.init('input'); assert.equal(field.getValue('input'), inputValue); }); @@ -189,7 +188,7 @@ describe('options', () => { assert.equal(field.getValue('input.child'), inputValue); }); - it('should allow access to field values before init when given `values` in constructor', function() { + it('should allow access with `getValue` before init when given `values` in constructor', function() { const inputValue = 'my value'; const field = new Field(this, { values: { @@ -199,6 +198,27 @@ describe('options', () => { assert.equal(field.getValue('input'), inputValue); }); + it('should allow access to with `getValues` before init when given `values` in constructor', function() { + const inputValue = 'my value'; + const field = new Field(this, { + values: { + input: inputValue, + }, + }); + assert.equal(field.getValues().input, inputValue); + }); + + it('should use setValues instead of constructor values on field that has not been initialized', function() { + const inputValue = 'my value'; + const field = new Field(this, { + values: { + input: inputValue, + }, + }); + field.setValue('input', 1) + assert.equal(field.getValue('input'), 1); + }); + it('should reset `input` to undefined when given `values` in constructor and call `reset`', function() { const fieldDefault = 'field default value'; const field = new Field(this, { @@ -223,35 +243,38 @@ describe('options', () => { assert.equal(field.getValue('input'), fieldDefault); }); - it('should reset `input` to undefined when given `values` and `parseName` = true in constructor and call `reset`', function() { + it('should reset `input` to undefined when given `values` in constructor and call `reset`', function() { const fieldDefault = 'field default value'; const field = new Field(this, { - parseName: true, values: { - input: { - child: fieldDefault - }, + input: fieldDefault }, }); - field.init('input.child'); + field.init('input'); field.reset(); - assert.equal(field.getValue('input.child'), undefined); + assert.equal(field.getValue('input'), undefined); }); - it('should reset `input` to undefined when given `values` and `parseName` = true in constructor and call `resetToDefault`', function() { + it('should return `{}` for `getValues after all fields are removed', function() { const fieldDefault = 'field default value'; const field = new Field(this, { - parseName: true, values: { - input: { - child: fieldDefault - }, + input: fieldDefault }, }); - field.init('input.child'); - field.resetToDefault('input.child'); - assert.equal(field.getValue('input.child'), fieldDefault); + field.init('input'); + field.remove('input'); + assert.equal(Object.keys(field.getValues()).length, 0); }); + + it('should return `undefined` after `remove` then re-`init`', function() { + const field = new Field(this, {values: {input: 4}}); + field.init('input'); + field.remove('input'); + field.init('input'); + + assert(field.getValue('input') === undefined); + }) }); describe('should support parseName', () => { @@ -261,7 +284,6 @@ describe('options', () => { field.init('user.pwd', { initValue: 12345 }); field.init('option[0]', { initValue: 'option1' }); field.init('option[1]', { initValue: 'option2' }); - const values = field.getValues(); assert(Object.keys(values).length === 2); @@ -273,6 +295,27 @@ describe('options', () => { done(); }); + it('should get constructor value of `name` if `getValue` called before init', function() { + const field = new Field(this, { + parseName: true, + values: { a: { b: 1 } }, + }); + assert(field.getValue('a.b') === 1); + }); + + it('should return constructor value for `names` if `getValues` called before init', function() { + const field = new Field(this, {parseName: true, values: {a: 1, b: 2, c: 3}}); + const {a, b} = field.getValues(['a', 'b']); + assert(a === 1); + assert(b === 2); + }); + it('should return all of constructor value if `getValues` called with no names before init', function() { + const field = new Field(this, {parseName: true, values: {a: 1, b: 2, c: 3}}); + const {a, b, c} = field.getValues(); + assert(a === 1); + assert(b === 2); + assert(c === 3); + }); it('setValues', function(done) { const field = new Field(this, { parseName: true }); field.init('user.name', { initValue: 'frankqian' }); @@ -297,6 +340,235 @@ describe('options', () => { done(); }); + + it('should allow access with `getValue` before init when given `values` in constructor', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault + }, + }, + }); + assert.equal(field.getValue('input.myValue'), fieldDefault); + }); + + it('should allow access to with `getValues` before init when given `values` in constructor', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault + }, + }, + }); + assert.equal(field.getValues().input.myValue, fieldDefault); + }); + + it('should use setValue instead of constructor values on field that has not been initialized', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault + }, + }, + }); + field.setValue('input.myValue', 1) + assert.equal(field.getValue('input.myValue'), 1); + }); + + + + it('should remove top level field after removed', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault + } + }, + }); + field.init('input.myValue'); + field.remove('input.myValue'); + assert.equal(Object.keys(field.getValues()).input, undefined); + }); + + it('should return `{}` for `getValues after `remove()`', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault + } + }, + }); + field.init('input.myValue'); + field.setValue('input.value2', fieldDefault); + field.remove(); + assert.equal(Object.keys(field.getValues()).length, 0); + }); + + it('should return `undefined` after `remove` then re-`init`', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault + } + }, + }); + field.init('input.myValue'); + field.remove('input.myValue'); + field.init('input.myValue'); + + assert(field.getValue('input.myValue') === undefined); + }); + + it('should return all setValues', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + }); + field.setValues({ + input: { + myValue: fieldDefault, + }, + }); + + assert.deepEqual(field.getValues(), { + input: { myValue: fieldDefault }, + }); + }); + + it('should return all setValues and initValues', function() { + const fieldDefault = 'field default value'; + const otherDefault = 'other default value'; + const field = new Field(this, { + parseName: true, + }); + field.setValues({ + input: { + myValue: fieldDefault, + }, + }); + + field.init('input.otherValue', { initValue: otherDefault }); + + assert.deepEqual(field.getValues(), { + input: { + myValue: fieldDefault, + otherValue: otherDefault, + }, + }); + }); + describe('reset', function() { + it('should reset all to undefined when call `reset`', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + }); + field.setValue('input.myValue', fieldDefault); + field.setValue('input.otherValue', fieldDefault); + field.reset(); + assert.equal(field.getValue('input.myValue'), undefined); + assert.equal(field.getValue('input.otherValue'), undefined); + }); + + it('should reset all to undefined when given `values` in constructor and call `reset`', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault, + otherValue: fieldDefault + }, + }, + }); + field.init('input.myValue'); + field.init('input.otherValue'); + field.reset(); + assert.equal(field.getValue('input.myValue'), undefined); + assert.equal(field.getValue('input.otherValue'), undefined); + }); + + it('should reset only `input.myValue` to undefined when given `values` in constructor and pass `input.myValue` to `reset`', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault, + otherValue: fieldDefault + }, + }, + }); + field.init('input.myValue'); + field.reset('input.myValue'); + assert.equal(field.getValue('input.myValue'), undefined); + assert.equal(field.getValue('input.otherValue'), fieldDefault); + }); + + it('should reset all to undefined when call `resetToDefault` with no defaults', function() { + const fieldDefault = 'field default value'; + const field = new Field(this, { + parseName: true, + }); + field.setValue('input.myValue', fieldDefault); + field.setValue('input.otherValue', fieldDefault); + field.resetToDefault(); + assert.equal(field.getValue('input.myValue'), undefined); + assert.equal(field.getValue('input.otherValue'), undefined); + }); + + it('should reset all to undefined when given `values` in constructor and call `resetToDefault`', function() { + const fieldDefault = 'field default value'; + const secondValue = 'second'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault, + otherValue: fieldDefault + }, + }, + }); + field.init('input.myValue'); + field.init('input.otherValue'); + field.setValue('input.myValue', secondValue); + field.setValue('input.otherValue', secondValue); + field.resetToDefault(); + assert.equal(field.getValue('input.myValue'), fieldDefault); + assert.equal(field.getValue('input.otherValue'), fieldDefault); + }); + + it('should reset `input` to undefined when given `values` in constructor and call `resetToDefault`', function() { + const fieldDefault = 'field default value'; + const secondValue = 'second'; + const field = new Field(this, { + parseName: true, + values: { + input: { + myValue: fieldDefault, + otherValue: fieldDefault + }, + }, + }); + field.init('input.myValue'); + field.setValue('input.myValue', secondValue); + field.setValue('input.otherValue', secondValue); + field.resetToDefault('input.myValue'); + assert.equal(field.getValue('input.myValue'), fieldDefault); + assert.equal(field.getValue('input.otherValue'), secondValue); + }); + }); }); describe('should support autoValidate=false', () => { diff --git a/test/field/utils-spec.js b/test/field/utils-spec.js index 40fe2b1fc4..3f3f99d422 100644 --- a/test/field/utils-spec.js +++ b/test/field/utils-spec.js @@ -1,6 +1,6 @@ import React from 'react'; import assert from 'power-assert'; -import {getErrorStrs} from '../../src/field/utils'; +import {getErrorStrs, getIn, setIn, deleteIn} from '../../src/field/utils'; /* eslint-disable react/jsx-filename-extension */ /* global describe it */ @@ -55,4 +55,110 @@ describe('Field Utils', () => { assert.deepEqual(result[1].props.children, 'message 2'); }); }) + + describe('getIn', () => { + it('should return state when state is falsy', () => { + assert(getIn(undefined, 'a') === undefined); + }); + + it('should return undefind when no name', () => { + assert(getIn({a: 1}, '') === undefined); + }); + + it('should return undefind when value for name', () => { + assert(getIn({a: 1}, 'b') === undefined); + }); + + it('should get top level element', () => { + assert(getIn({a: 1}, 'a') === 1); + }); + + it('should get deep level element', () => { + assert(getIn({a: {b: {c: 1}}}, 'a.b.c') === 1); + }); + + it('should get array element with dot notation', () => { + assert(getIn({a: [1, 2]}, 'a.1') === 2); + }); + + it('should get array element with bracket notation', () => { + assert(getIn({a: [1, 2]}, 'a[1]') === 2); + }); + + it('should get element that is deep array combination', () => { + assert(getIn({a: [1, {b: {c: 2}}]}, 'a[1].b.c') === 2); + }); + }); + + describe('setIn', () => { + it('should initialize state with object when it is falsy and path is NaN', () => { + assert(setIn(undefined, 'a', 5).a === 5); + }); + + it('should initialize state with array when it is falsy and path is NaN', () => { + assert(setIn(undefined, '1', 5)[1] === 5); + }); + + it('should initialize state with whole path', () => { + assert(setIn(undefined, 'a.b.c', 5).a.b.c === 5); + }); + + it('should not modify state when setting new value', () => { + const state = {a: {b: { c: 1}}}; + setIn(state, 'a.b.c', 5); + assert(state.a.b.c === 1); + }); + + it('should duplicate state with new value', () => { + const state = {a: {b: { c: 1}}}; + const newState = setIn(state, 'a.b.c', 5); + assert(newState.a.b.c === 5); + }); + + it('should handle array dot notation', () => { + const state = {a: {b: [1, 2]}}; + const newState = setIn(state, 'a.b.1', 5); + assert(newState.a.b[1] === 5); + }); + + it('should handle array bracket notation', () => { + const state = {a: {b: [1, 2]}}; + const newState = setIn(state, 'a.b[1]', 5); + assert(newState.a.b[1] === 5); + }); + + it('should add to existing nested object', () => { + const state = {a: {b: 1 }}; + const newState = setIn(state, 'a.c.d', 5); + assert(newState.a.c.d === 5); + }); + + it('should add to empty object', () => { + const newState = setIn({}, 'a.b.c', 5); + assert(newState.a.b.c === 5); + }); + }); + + describe('deleteIn', () => { + it('should do nothing when name is not present', () => { + assert.deepEqual(deleteIn({a: { b: 1}}, 'x'), { a: { b: 1 }}); + }); + + it('should do nothing given empty object', () => { + assert.deepEqual(deleteIn({}, 'x'), {}); + }); + + it('should delete nested element, but leave object', () => { + assert.deepEqual(deleteIn({a : { b: 1}}, 'a.b'), {a: {}}); + }); + + it('should delete top level element, but leave object', () => { + assert.deepEqual(deleteIn({a : { b: 1}}, 'a'), {}); + }); + + it('should delete array element, but not change later indices', () => { + // eslint-disable-next-line no-sparse-arrays + assert.deepEqual(deleteIn({a : { b: [1, 2, 3]}}, 'a.b.0'), {a : { b: [, 2, 3]}}); + }); + }); });