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 ();
}
}
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]}});
+ });
+ });
});