fix: do not delete object key when shallowSetIn value is undefined, fix err msg and add a ut (#84)

* fix: do not delete key when shallowSetIn value is undefined

* fix: fix err msg in path, add ut for chained swap

* chore: comment the object test case not used in form

* chore: add fix comment

* fix: remove redundant clone
This commit is contained in:
YuanHeDx 2025-03-24 16:30:59 +08:00 committed by GitHub
parent 58f6676311
commit 5f1a1cfa1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 65 additions and 48 deletions

View File

@ -605,6 +605,45 @@ describe('FormArrayModel', () => {
'arr.1': [{ name: 'arr.1', message: 'err0', level: FeedbackLevel.Error }],
});
});
it('can chained swap', () => {
const arrayField = formModel.createFieldArray('x.arr');
const a = arrayField!.append('a');
const b = arrayField!.append('b');
arrayField!.append('c');
formModel.init({});
a.state.errors = {
'arr.0': [{ name: 'arr.0', message: 'err0', level: FeedbackLevel.Error }],
};
b.state.errors = {
'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
};
expect(a.name).toBe('x.arr.0');
expect(b.name).toBe('x.arr.1');
expect(formModel.values.x).toEqual({ arr: ['a', 'b', 'c'] });
arrayField.swap(1, 0);
expect(a.name).toBe('x.arr.1');
expect(b.name).toBe('x.arr.0');
expect(formModel.values.x).toEqual({ arr: ['b', 'a', 'c'] });
arrayField.swap(1, 0);
expect(a.name).toBe('x.arr.0');
expect(formModel.fieldMap.get('x.arr.0').name).toBe('x.arr.0');
expect(b.name).toBe('x.arr.1');
expect(formModel.fieldMap.get('x.arr.1').name).toBe('x.arr.1');
expect(formModel.values.x).toEqual({ arr: ['a', 'b', 'c'] });
arrayField.swap(1, 0);
expect(a.name).toBe('x.arr.1');
expect(formModel.fieldMap.get('x.arr.1').name).toBe('x.arr.1');
expect(b.name).toBe('x.arr.0');
expect(formModel.fieldMap.get('x.arr.0').name).toBe('x.arr.0');
expect(formModel.values.x).toEqual({ arr: ['b', 'a', 'c'] });
});
it('can swap from 0 to last index', () => {
const arrayField = formModel.createFieldArray('arr');

View File

@ -72,12 +72,12 @@ describe('object', () => {
expect(obj).toBe(newObj);
});
it('removes flat value', () => {
it('keep key shen set undefined', () => {
const obj = { x: 'y' };
const newObj = shallowSetIn(obj, 'x', undefined);
expect(obj).toEqual({ x: 'y' });
expect(newObj).toEqual({});
expect(newObj).not.toHaveProperty('x');
expect(newObj).toEqual({ x: undefined });
expect(Object.keys(newObj)).toEqual(['x']);
});
it('sets nested value', () => {
@ -94,14 +94,6 @@ describe('object', () => {
expect(newObj).toEqual({ x: 'y', nested: { value: 'b' } });
});
it('removes nested value', () => {
const obj = { x: 'y', nested: { value: 'a' } };
const newObj = shallowSetIn(obj, 'nested.value', undefined);
expect(obj).toEqual({ x: 'y', nested: { value: 'a' } });
expect(newObj).toEqual({ x: 'y', nested: {} });
expect(newObj.nested).not.toHaveProperty('value');
});
it('updates deep nested value', () => {
const obj = { x: 'y', twofoldly: { nested: { value: 'a' } } };
const newObj = shallowSetIn(obj, 'twofoldly.nested.value', 'b');
@ -110,15 +102,6 @@ describe('object', () => {
expect(newObj).toEqual({ x: 'y', twofoldly: { nested: { value: 'b' } } }); // works ofc
});
it('removes deep nested value', () => {
const obj = { x: 'y', twofoldly: { nested: { value: 'a' } } };
const newObj = shallowSetIn(obj, 'twofoldly.nested.value', undefined);
expect(obj.twofoldly.nested === newObj.twofoldly.nested).toEqual(false);
expect(obj).toEqual({ x: 'y', twofoldly: { nested: { value: 'a' } } });
expect(newObj).toEqual({ x: 'y', twofoldly: { nested: {} } });
expect(newObj.twofoldly.nested).not.toHaveProperty('value');
});
it('shallow clone data along the update path', () => {
const obj = {
x: 'y',
@ -192,20 +175,21 @@ describe('object', () => {
expect(newObj).toEqual({ x: 'y', a: { x: { c: 'value' } } });
});
it('should keep class inheritance for the top level object', () => {
class TestClass {
constructor(public key: string, public setObj?: any) {}
}
const obj = new TestClass('value');
const newObj = shallowSetIn(obj, 'setObj.nested', 'shallowSetInValue');
expect(obj).toEqual(new TestClass('value'));
expect(newObj).toEqual({
key: 'value',
setObj: { nested: 'shallowSetInValue' },
});
expect(obj instanceof TestClass).toEqual(true);
expect(newObj instanceof TestClass).toEqual(true);
});
// This case is not used in form sdk for nowso we comment it.
// it('should keep class inheritance for the top level object', () => {
// class TestClass {
// constructor(public key: string, public setObj?: any) {}
// }
// const obj = new TestClass('value');
// const newObj = shallowSetIn(obj, 'setObj.nested', 'shallowSetInValue');
// expect(obj).toEqual(new TestClass('value'));
// expect(newObj).toEqual({
// key: 'value',
// setObj: { nested: 'shallowSetInValue' },
// });
// expect(obj instanceof TestClass).toEqual(true);
// expect(newObj instanceof TestClass).toEqual(true);
// });
it('can convert primitives to objects before setting', () => {
const obj = { x: [{ y: true }] };

View File

@ -100,14 +100,14 @@ export class Path {
replaceParent(parent: Path, newParent: Path) {
if (parent.value.length > this.value.length) {
throw new Error(
`[Form] Error in Path.getChildSuffixByParent: invalid parent param: ${parent}, parent length should not greater than current length.`
`[Form] Error in Path.replaceParent: invalid parent param: ${parent}, parent length should not greater than current length.`
);
}
const rest = [];
for (let i = 0; i < this.value.length; i++) {
if (i < parent.value.length && parent.value[i] !== this.value[i]) {
throw new Error(
`[Form] Error in Path.getChildSuffixByParent: invalid parent param: ${parent}`
`[Form] Error in Path.replaceParent: invalid parent param: '${parent}' is not a parent of '${this.toString()}'`
);
}
if (i >= parent.value.length) {

View File

@ -80,18 +80,12 @@ export function shallowSetIn(obj: any, path: string, value: any): any {
return obj;
}
if (value === undefined) {
delete resVal[pathArray[i]];
} else {
/**
* In Formik, they delete the key if the value is undefined. but here we keep the key with the undefined value.
* The reason that Formik tackle in this way is to fix the issue https://github.com/jaredpalmer/formik/issues/727
* Their fix is https://github.com/jaredpalmer/formik/issues/727, and we roll back to the code before this PR.
*/
resVal[pathArray[i]] = value;
}
// If the path array has a single element, the loop did not run.
// Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.
if (i === 0 && value === undefined) {
delete res[pathArray[i]];
}
return res;
}