From f8623f916e7b55e9caf6bd779e6ef53ffbb1a796 Mon Sep 17 00:00:00 2001 From: Marek Bodinger Date: Fri, 22 Mar 2024 19:09:01 +0100 Subject: [PATCH] Replace getFieldNames and getUsedFormData with schemaUtils.omitExtraData, remove moved tests, fix relevant tests --- packages/core/src/components/Form.tsx | 75 +----- packages/core/test/Form.test.jsx | 341 +------------------------- 2 files changed, 8 insertions(+), 408 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 31752c41e0..4586c716fb 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -6,21 +6,17 @@ import { ErrorSchema, ErrorTransformer, FormContextType, - GenericObjectType, getTemplate, getUiOptions, IdSchema, isObject, mergeObjects, - NAME_KEY, - PathSchema, StrictRJSFSchema, Registry, RegistryFieldsType, RegistryWidgetsType, RJSFSchema, RJSFValidationError, - RJSF_ADDITONAL_PROPERTIES_FLAG, SchemaUtilsType, shouldRender, SUBMIT_BTN_OPTIONS_KEY, @@ -34,9 +30,6 @@ import { ValidatorType, Experimental_DefaultFormStateBehavior, } from '@rjsf/utils'; -import _get from 'lodash/get'; -import _isEmpty from 'lodash/isEmpty'; -import _pick from 'lodash/pick'; import _toPath from 'lodash/toPath'; import getDefaultRegistry from '../getDefaultRegistry'; @@ -506,63 +499,6 @@ export default class Form< return null; } - /** Returns the `formData` with only the elements specified in the `fields` list - * - * @param formData - The data for the `Form` - * @param fields - The fields to keep while filtering - */ - getUsedFormData = (formData: T | undefined, fields: string[][]): T | undefined => { - // For the case of a single input form - if (fields.length === 0 && typeof formData !== 'object') { - return formData; - } - - // _pick has incorrect type definition, it works with string[][], because lodash/hasIn supports it - const data: GenericObjectType = _pick(formData, fields as unknown as string[]); - if (Array.isArray(formData)) { - return Object.keys(data).map((key: string) => data[key]) as unknown as T; - } - - return data as T; - }; - - /** Returns the list of field names from inspecting the `pathSchema` as well as using the `formData` - * - * @param pathSchema - The `PathSchema` object for the form - * @param [formData] - The form data to use while checking for empty objects/arrays - */ - getFieldNames = (pathSchema: PathSchema, formData?: T): string[][] => { - const getAllPaths = (_obj: GenericObjectType, acc: string[][] = [], paths: string[][] = [[]]) => { - Object.keys(_obj).forEach((key: string) => { - if (typeof _obj[key] === 'object') { - const newPaths = paths.map((path) => [...path, key]); - // If an object is marked with additionalProperties, all its keys are valid - if (_obj[key][RJSF_ADDITONAL_PROPERTIES_FLAG] && _obj[key][NAME_KEY] !== '') { - acc.push(_obj[key][NAME_KEY]); - } else { - getAllPaths(_obj[key], acc, newPaths); - } - } else if (key === NAME_KEY && _obj[key] !== '') { - paths.forEach((path) => { - const formValue = _get(formData, path); - // adds path to fieldNames if it points to a value - // or an empty object/array - if ( - typeof formValue !== 'object' || - _isEmpty(formValue) || - (Array.isArray(formValue) && formValue.every((val) => typeof val !== 'object')) - ) { - acc.push(path); - } - }); - } - }); - return acc; - }; - - return getAllPaths(pathSchema); - }; - /** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the * `formData` along with a new `ErrorSchema`. It will first update the `formData` with any missing default fields and * then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filterer to remove any extra data not @@ -590,11 +526,8 @@ export default class Form< let _retrievedSchema: S | undefined; if (omitExtraData === true && liveOmit === true) { _retrievedSchema = schemaUtils.retrieveSchema(schema, formData); - const pathSchema = schemaUtils.toPathSchema(_retrievedSchema, '', formData); - - const fieldNames = this.getFieldNames(pathSchema, formData); + newFormData = schemaUtils.omitExtraData(_retrievedSchema, formData); - newFormData = this.getUsedFormData(formData, fieldNames); state = { formData: newFormData, }; @@ -702,11 +635,7 @@ export default class Form< if (omitExtraData === true) { const retrievedSchema = schemaUtils.retrieveSchema(schema, newFormData); - const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', newFormData); - - const fieldNames = this.getFieldNames(pathSchema, newFormData); - - newFormData = this.getUsedFormData(newFormData, fieldNames); + newFormData = schemaUtils.omitExtraData(retrievedSchema, newFormData); } if (noValidate || this.validateForm()) { diff --git a/packages/core/test/Form.test.jsx b/packages/core/test/Form.test.jsx index efd264a191..512743fbd5 100644 --- a/packages/core/test/Form.test.jsx +++ b/packages/core/test/Form.test.jsx @@ -3229,7 +3229,7 @@ describe('Form omitExtraData and liveOmit', () => { sandbox.restore(); }); - it('should call getUsedFormData when the omitExtraData prop is true and liveOmit is true', () => { + it('should call omitExtraData when the omitExtraData prop is true and liveOmit is true', () => { const schema = { type: 'object', properties: { @@ -3252,7 +3252,7 @@ describe('Form omitExtraData and liveOmit', () => { liveOmit, }); - sandbox.stub(comp, 'getUsedFormData').returns({ + sandbox.stub(comp.state.schemaUtils, 'omitExtraData').returns({ foo: '', }); @@ -3260,10 +3260,10 @@ describe('Form omitExtraData and liveOmit', () => { target: { value: 'new' }, }); - sinon.assert.calledOnce(comp.getUsedFormData); + sinon.assert.calledOnce(comp.state.schemaUtils.omitExtraData); }); - it('should not call getUsedFormData when the omitExtraData prop is true and liveOmit is unspecified', () => { + it('should not call omitExtraData when the omitExtraData prop is true and liveOmit is unspecified', () => { const schema = { type: 'object', properties: { @@ -3284,7 +3284,7 @@ describe('Form omitExtraData and liveOmit', () => { omitExtraData, }); - sandbox.stub(comp, 'getUsedFormData').returns({ + sandbox.stub(comp.state.schemaUtils, 'omitExtraData').returns({ foo: '', }); @@ -3292,336 +3292,7 @@ describe('Form omitExtraData and liveOmit', () => { target: { value: 'new' }, }); - sinon.assert.notCalled(comp.getUsedFormData); - }); - - describe('getUsedFormData', () => { - it('should call getUsedFormData when the omitExtraData prop is true', () => { - const schema = { - type: 'object', - properties: { - foo: { - type: 'string', - }, - }, - }; - const formData = { - foo: '', - }; - const onSubmit = sandbox.spy(); - const onError = sandbox.spy(); - const omitExtraData = true; - const { comp, node } = createFormComponent({ - schema, - formData, - onSubmit, - onError, - omitExtraData, - }); - - sandbox.stub(comp, 'getUsedFormData').returns({ - foo: '', - }); - - Simulate.submit(node); - - sinon.assert.calledOnce(comp.getUsedFormData); - }); - it('should just return the single input form value', () => { - const schema = { - title: 'A single-field form', - type: 'string', - }; - const formData = 'foo'; - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const result = comp.getUsedFormData(formData, []); - expect(result).eql('foo'); - }); - - it('should return the root level array', () => { - const schema = { - type: 'array', - items: { - type: 'string', - }, - }; - const formData = []; - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const result = comp.getUsedFormData(formData, []); - expect(result).eql([]); - }); - - it('should call getUsedFormData with data from fields in event', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }; - const formData = { - foo: 'bar', - }; - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const result = comp.getUsedFormData(formData, ['foo']); - expect(result).eql({ foo: 'bar' }); - }); - - it('unused form values should be omitted', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - baz: { type: 'string' }, - list: { - type: 'array', - items: { - type: 'object', - properties: { - title: { type: 'string' }, - details: { type: 'string' }, - }, - }, - }, - }, - }; - - const formData = { - foo: 'bar', - baz: 'buzz', - list: [ - { title: 'title0', details: 'details0' }, - { title: 'title1', details: 'details1' }, - ], - }; - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const result = comp.getUsedFormData(formData, ['foo', 'list.0.title', 'list.1.details']); - expect(result).eql({ - foo: 'bar', - list: [{ title: 'title0' }, { details: 'details1' }], - }); - }); - }); - - describe('getFieldNames()', () => { - it('should return an empty array for a single input form', () => { - const schema = { - type: 'string', - }; - - const formData = 'foo'; - - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const pathSchema = { - $name: '', - }; - - const fieldNames = comp.getFieldNames(pathSchema, formData); - expect(fieldNames).eql([]); - }); - - it('should get field names from pathSchema', () => { - const schema = {}; - - const formData = { - extra: { - foo: 'bar', - }, - level1: { - level2: 'test', - anotherThing: { - anotherThingNested: 'abc', - extra: 'asdf', - anotherThingNested2: 0, - }, - stringArray: ['scobochka'], - }, - level1a: 1.23, - }; - - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const pathSchema = { - $name: '', - level1: { - $name: 'level1', - level2: { $name: 'level1.level2' }, - anotherThing: { - $name: 'level1.anotherThing', - anotherThingNested: { - $name: 'level1.anotherThing.anotherThingNested', - }, - anotherThingNested2: { - $name: 'level1.anotherThing.anotherThingNested2', - }, - }, - stringArray: { - $name: 'level1.stringArray', - }, - }, - level1a: { - $name: 'level1a', - }, - }; - - const fieldNames = comp.getFieldNames(pathSchema, formData); - expect(fieldNames.sort()).eql( - [ - ['level1', 'anotherThing', 'anotherThingNested'], - ['level1', 'anotherThing', 'anotherThingNested2'], - ['level1', 'level2'], - ['level1', 'stringArray'], - ['level1a'], - ].sort() - ); - }); - - it('should get field marked as additionalProperties', () => { - const schema = {}; - - const formData = { - extra: { - foo: 'bar', - }, - level1: { - level2: 'test', - extra: 'foo', - mixedMap: { - namedField: 'foo', - key1: 'val1', - }, - }, - level1a: 1.23, - }; - - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const pathSchema = { - $name: '', - level1: { - $name: 'level1', - level2: { $name: 'level1.level2' }, - mixedMap: { - $name: 'level1.mixedMap', - __rjsf_additionalProperties: true, - namedField: { $name: 'level1.mixedMap.namedField' }, // this name should not be returned, as the root object paths should be returned for objects marked with additionalProperties - }, - }, - level1a: { - $name: 'level1a', - }, - }; - - const fieldNames = comp.getFieldNames(pathSchema, formData); - expect(fieldNames.sort()).eql([['level1', 'level2'], 'level1.mixedMap', ['level1a']].sort()); - }); - - it('should get field names from pathSchema with array', () => { - const schema = {}; - - const formData = { - address_list: [ - { - street_address: '21, Jump Street', - city: 'Babel', - state: 'Neverland', - }, - { - street_address: '1234 Schema Rd.', - city: 'New York', - state: 'Arizona', - }, - ], - }; - - const onSubmit = sandbox.spy(); - const { comp } = createFormComponent({ - schema, - formData, - onSubmit, - }); - - const pathSchema = { - $name: '', - address_list: { - 0: { - $name: 'address_list.0', - city: { - $name: 'address_list.0.city', - }, - state: { - $name: 'address_list.0.state', - }, - street_address: { - $name: 'address_list.0.street_address', - }, - }, - 1: { - $name: 'address_list.1', - city: { - $name: 'address_list.1.city', - }, - state: { - $name: 'address_list.1.state', - }, - street_address: { - $name: 'address_list.1.street_address', - }, - }, - }, - }; - - const fieldNames = comp.getFieldNames(pathSchema, formData); - expect(fieldNames.sort()).eql( - [ - ['address_list', '0', 'city'], - ['address_list', '0', 'state'], - ['address_list', '0', 'street_address'], - ['address_list', '1', 'city'], - ['address_list', '1', 'state'], - ['address_list', '1', 'street_address'], - ].sort() - ); - }); + sinon.assert.notCalled(comp.state.schemaUtils.omitExtraData); }); it('should not omit data on change with omitExtraData=false and liveOmit=false', () => {