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 }], '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', () => { it('can swap from 0 to last index', () => {
const arrayField = formModel.createFieldArray('arr'); const arrayField = formModel.createFieldArray('arr');

View File

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

View File

@ -100,14 +100,14 @@ export class Path {
replaceParent(parent: Path, newParent: Path) { replaceParent(parent: Path, newParent: Path) {
if (parent.value.length > this.value.length) { if (parent.value.length > this.value.length) {
throw new Error( 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 = []; const rest = [];
for (let i = 0; i < this.value.length; i++) { for (let i = 0; i < this.value.length; i++) {
if (i < parent.value.length && parent.value[i] !== this.value[i]) { if (i < parent.value.length && parent.value[i] !== this.value[i]) {
throw new Error( 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) { if (i >= parent.value.length) {

View File

@ -80,18 +80,12 @@ export function shallowSetIn(obj: any, path: string, value: any): any {
return obj; return obj;
} }
if (value === undefined) { /**
delete resVal[pathArray[i]]; * In Formik, they delete the key if the value is undefined. but here we keep the key with the undefined value.
} else { * The reason that Formik tackle in this way is to fix the issue https://github.com/jaredpalmer/formik/issues/727
resVal[pathArray[i]] = value; * 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; return res;
} }