From 577d27f497f09bedc5089adc0304fa0b11dbf02e Mon Sep 17 00:00:00 2001 From: Aleksey Batuhtin Date: Wed, 2 Aug 2023 22:01:56 -0300 Subject: [PATCH 1/3] feat: arrayMinItems.populate = never --- CHANGELOG.md | 14 +- .../docs/docs/api-reference/form-props.md | 9 +- packages/playground/src/components/Header.tsx | 5 + .../utils/src/schema/getDefaultFormState.ts | 31 ++- packages/utils/src/types.ts | 3 +- .../test/schema/getDefaultFormStateTest.ts | 220 ++++++++++++++++++ 6 files changed, 265 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5233dddb83..a9a4f8adea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,18 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> +# 5.13.0 + +## @rjsf/utils + +- Experimental feature: + - Added `experimental_defaultFormStateBehavior = { arrayMinItems: { populate: 'never' } }` (feature [#3796](https://github.com/rjsf-team/react-jsonschema-form/issues/3796)) + +## Dev / docs / playground + +- Updated the `form-props` documentation `arrayMinItems`, added description for `never`. +- Updated the `playground` to add the option for the new `arrayMinItems.populate = 'never'`. + # 5.12.0 ## Dev / playground @@ -105,7 +117,7 @@ should change the heading of the (upcoming) version to include a major version b - Updated the `utility-functions` documentation to add the new `mergeExtraArrayDefaults` flag for the `mergeDefaultWithFormData()` function - Updated the `form-props` documentation to update the `arrayMinItems` documentation for the new object behavior -- Updated the `playground` to add a checkbox for the new `arrayMinItems.mergeExtraDefaults` flag +- Updated the `playground` to add a checkbox for the new `arrayMinItems.mergeExtraDefaults` flag # 5.8.2 diff --git a/packages/docs/docs/api-reference/form-props.md b/packages/docs/docs/api-reference/form-props.md index ca35faf0a0..147c1b6b29 100644 --- a/packages/docs/docs/api-reference/form-props.md +++ b/packages/docs/docs/api-reference/form-props.md @@ -77,10 +77,11 @@ When not specified, it defaults to `{ populate: 'all', mergeExtraDefaults: false Optional enumerated flag controlling how array minItems are populated, defaulting to `all`: -| Flag Value | Description | -| -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `all` | Legacy behavior - populate minItems entries with default values initially and include empty array when no values have been defined. | -| `requiredOnly` | Ignore `minItems` on a field when calculating defaults unless the field is required. | +| Flag Value | Description | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `all` | Legacy behavior - populate minItems entries with default values initially and include empty array when no values have been defined. | +| `requiredOnly` | Ignore `minItems` on a field when calculating defaults unless the field is required. | +| `never` | Ignore `minItems` on a field when calculating defaults for required and non-required. Value will set only if defined `default` and from `formData` | #### `arrayMinItems.mergeExtraDefaults` diff --git a/packages/playground/src/components/Header.tsx b/packages/playground/src/components/Header.tsx index fe46917b93..f6f28d7081 100644 --- a/packages/playground/src/components/Header.tsx +++ b/packages/playground/src/components/Header.tsx @@ -96,6 +96,11 @@ const liveSettingsSelectSchema: RJSFSchema = { title: 'Ignore minItems unless field is required', enum: ['requiredOnly'], }, + { + type: 'string', + title: 'Ignore populating arrays even if settled minItems', + enum: ['never'], + }, ], }, mergeExtraDefaults: { diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 3e41094770..de7402dd8e 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -321,6 +321,9 @@ export function computeDefaults { @@ -338,19 +341,25 @@ export function computeDefaults(schema); - defaults = rawFormData.map((item: T, idx: number) => { - return computeDefaults(validator, schemaItem, { - rootSchema, - _recurseList, - experimental_defaultFormStateBehavior, - rawFormData: item, - parentDefaults: get(defaults, [idx]), - required, - }); - }) as T[]; + if (neverPopulate) { + defaults = rawFormData; + } else { + defaults = rawFormData.map((item: T, idx: number) => { + return computeDefaults(validator, schemaItem, { + rootSchema, + _recurseList, + experimental_defaultFormStateBehavior, + rawFormData: item, + parentDefaults: get(defaults, [idx]), + required, + }); + }) as T[]; + } } - const ignoreMinItemsFlagSet = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'requiredOnly'; + if (neverPopulate) { + return defaults ?? []; + } if (ignoreMinItemsFlagSet && !required) { // If no form data exists or defaults are set leave the field empty/non-existent, otherwise // return form data/defaults diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index f40e98c34b..4e40c0d26b 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -38,8 +38,9 @@ export type Experimental_ArrayMinItems = { * - `all`: Legacy behavior, populate minItems entries with default values initially and include an empty array when * no values have been defined. * - `requiredOnly`: Ignore `minItems` on a field when calculating defaults unless the field is required. + * - `never`: Ignore `minItems` on a field even the field is required. */ - populate?: 'all' | 'requiredOnly'; + populate?: 'all' | 'requiredOnly' | 'never'; /** When `formData` is provided and does not contain `minItems` worth of data, this flag (`false` by default) controls * whether the extra data provided by the defaults is appended onto the existing `formData` items to ensure the * `minItems` condition is met. When false (legacy behavior), only the `formData` provided is merged into the default diff --git a/packages/utils/test/schema/getDefaultFormStateTest.ts b/packages/utils/test/schema/getDefaultFormStateTest.ts index 86ad2aa2c2..617b789ee2 100644 --- a/packages/utils/test/schema/getDefaultFormStateTest.ts +++ b/packages/utils/test/schema/getDefaultFormStateTest.ts @@ -412,6 +412,226 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType ).toEqual({ requiredArray: ['default0', 'default0'] }); }); }); + describe('default form state behavior: arrayMinItems.populate = "never"', () => { + it('should not be filled if minItems defined and required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + requiredArray: { + type: 'array', + items: { type: 'string' }, + minItems: 1, + }, + }, + required: ['requiredArray'], + }; + + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ requiredArray: [] }); + }); + it('should not be filled if minItems defined and non required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + items: { type: 'string' }, + minItems: 1, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: [] }); + }); + + it('should be filled with default values if required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + requiredArray: { + type: 'array', + default: ['raw0'], + items: { type: 'string' }, + minItems: 1, + }, + }, + required: ['requiredArray'], + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ requiredArray: ['raw0'] }); + }); + + it('should be filled with default values if non required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + default: ['raw0'], + items: { type: 'string' }, + minItems: 1, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: ['raw0'] }); + }); + + it('should be filled with default values partly and not fill others', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + default: ['raw0'], + items: { type: 'string' }, + minItems: 2, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + rawFormData: { nonRequiredArray: ['raw0'] }, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: ['raw0'] }); + }); + + it('should not add items to formData', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + items: { type: 'string' }, + minItems: 2, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + rawFormData: { nonRequiredArray: ['not add'] }, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: ['not add'] }); + }); + + it('should be empty array if minItems not defined and required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + requiredArray: { + type: 'array', + items: { type: 'string' }, + }, + }, + required: ['requiredArray'], + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ requiredArray: [] }); + }); + it('should be empty array if minItems not defined and non required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + items: { type: 'string' }, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: [] }); + }); + + it('injecting data should be stopped at the top level of tree', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + minItems: 2, + items: { + type: 'object', + properties: { + nestedValue: { type: 'string' }, + nestedArray: { type: 'array', items: { type: 'string' } }, + }, + }, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: [] }); + }); + it('no injecting for childs', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + minItems: 2, + items: { + type: 'object', + properties: { + nestedValue: { type: 'string' }, + nestedArray: { type: 'array', minItems: 3, items: { type: 'string' } }, + }, + }, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + rawFormData: { + nonRequiredArray: [ + { + nestedArray: ['raw0'], + }, + ], + }, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ + nonRequiredArray: [ + { + nestedArray: ['raw0'], + }, + ], + }); + }); + }); describe('default form state behavior: emptyObjectFields = "populateRequiredDefaults"', () => { it('test an object with an optional property that has a nested required property', () => { const schema: RJSFSchema = { From 8ae953714d4f023357c4295cf9e6d25995362852 Mon Sep 17 00:00:00 2001 From: Aleksey Batuhtin Date: Fri, 4 Aug 2023 13:46:39 -0300 Subject: [PATCH 2/3] fix: review comments --- packages/playground/src/components/Header.tsx | 4 ++-- packages/utils/test/schema/getDefaultFormStateTest.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playground/src/components/Header.tsx b/packages/playground/src/components/Header.tsx index f6f28d7081..782eacccb1 100644 --- a/packages/playground/src/components/Header.tsx +++ b/packages/playground/src/components/Header.tsx @@ -93,12 +93,12 @@ const liveSettingsSelectSchema: RJSFSchema = { }, { type: 'string', - title: 'Ignore minItems unless field is required', + title: 'Only populate minItems with default values when field is required', enum: ['requiredOnly'], }, { type: 'string', - title: 'Ignore populating arrays even if settled minItems', + title: 'Never populate minItems with default values', enum: ['never'], }, ], diff --git a/packages/utils/test/schema/getDefaultFormStateTest.ts b/packages/utils/test/schema/getDefaultFormStateTest.ts index 617b789ee2..b844bdc5f6 100644 --- a/packages/utils/test/schema/getDefaultFormStateTest.ts +++ b/packages/utils/test/schema/getDefaultFormStateTest.ts @@ -508,10 +508,10 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType expect( computeDefaults(testValidator, schema, { rootSchema: schema, - rawFormData: { nonRequiredArray: ['raw0'] }, + rawFormData: { nonRequiredArray: ['raw1'] }, experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, }) - ).toStrictEqual({ nonRequiredArray: ['raw0'] }); + ).toStrictEqual({ nonRequiredArray: ['raw1'] }); }); it('should not add items to formData', () => { From bc65d8ca818daa8fea0288bfad4620c803f8a6c0 Mon Sep 17 00:00:00 2001 From: Aleksey Batuhtin Date: Fri, 4 Aug 2023 15:04:08 -0300 Subject: [PATCH 3/3] move feature to 5.12.0 version --- CHANGELOG.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a4f8adea..c3c5d9ea45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,31 +15,26 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> -# 5.13.0 +# 5.12.0 ## @rjsf/utils - Experimental feature: - Added `experimental_defaultFormStateBehavior = { arrayMinItems: { populate: 'never' } }` (feature [#3796](https://github.com/rjsf-team/react-jsonschema-form/issues/3796)) -## Dev / docs / playground - -- Updated the `form-props` documentation `arrayMinItems`, added description for `never`. -- Updated the `playground` to add the option for the new `arrayMinItems.populate = 'never'`. +## @rjsf/validator-ajv8 -# 5.12.0 +- Exposing new function `compileSchemaValidatorsCode` to allow creating precompiled validator without a file. This is useful in case when precompiled validator is to be created dynamically. [#3793](https://github.com/rjsf-team/react-jsonschema-form/pull/3793) -## Dev / playground +## Dev / docs / playground - update playground vite config to use sources directly, allowing to reload changes in it without additional build step - moving from `dts-cli` to use individual dev tools directly, updating package publish config - tsc for generating type definitions and esm modules - esbuild for CJS bundle - rollup for UMD bundle - -## @rjsf/validator-ajv8 - -- Exposing new function `compileSchemaValidatorsCode` to allow creating precompiled validator without a file. This is useful in case when precompiled validator is to be created dynamically. [#3793](https://github.com/rjsf-team/react-jsonschema-form/pull/3793) +- Updated the `form-props` documentation `arrayMinItems`, added description for `never`. +- Updated the `playground` to add the option for the new `arrayMinItems.populate = 'never'`. # 5.11.2