From 36011f62d39cc905b294ec897c06a684efb4c0dd Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Fri, 4 Oct 2024 18:02:20 +0200 Subject: [PATCH 01/10] Make fields with const pre-fiiled and readonl --- .../src/components/fields/ObjectField.tsx | 6 +++- .../src/components/fields/SchemaField.tsx | 4 ++- .../utils/src/schema/getDefaultFormState.ts | 29 +++++++++++++++---- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/fields/ObjectField.tsx b/packages/core/src/components/fields/ObjectField.tsx index 8399429da5..fd884efc7d 100644 --- a/packages/core/src/components/fields/ObjectField.tsx +++ b/packages/core/src/components/fields/ObjectField.tsx @@ -202,15 +202,18 @@ class ObjectField(schema, uiOptions, idSchema, registry); const disabled = Boolean(uiOptions.disabled ?? props.disabled); - const readonly = Boolean(uiOptions.readonly ?? (props.readonly || props.schema.readOnly || schema.readOnly)); + const readonly = Boolean( + uiOptions.readonly ?? (props.readonly || props.schema.const || props.schema.readOnly || schema.readOnly) + ); const uiSchemaHideError = uiOptions.hideError; // Set hideError to the value provided in the uiSchema, otherwise stick with the prop to propagate to children const hideError = uiSchemaHideError === undefined ? props.hideError : Boolean(uiSchemaHideError); diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index b1f5a096c8..04f4909871 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -3,6 +3,7 @@ import isEmpty from 'lodash/isEmpty'; import { ANY_OF_KEY, + CONST_KEY, DEFAULT_KEY, DEPENDENCIES_KEY, PROPERTIES_KEY, @@ -29,6 +30,7 @@ import { } from '../types'; import isMultiSelect from './isMultiSelect'; import retrieveSchema, { resolveDependencies } from './retrieveSchema'; +import isConstant from '../isConstant'; /** Enum that indicates how `schema.additionalItems` should be handled by the `getInnerSchemaForArrayItem()` function. */ @@ -92,6 +94,7 @@ export function getInnerSchemaForArrayItem( obj: GenericObjectType, @@ -100,10 +103,13 @@ function maybeAddDefaultToObject( includeUndefinedValues: boolean | 'excludeObjectChildren', isParentRequired?: boolean, requiredFields: string[] = [], - experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior = {} + experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior = {}, + isConst = false ) { const { emptyObjectFields = 'populateAllDefaults' } = experimental_defaultFormStateBehavior; - if (includeUndefinedValues) { + if (includeUndefinedValues || isConst) { + // If includeUndefinedValues + // Or if the schema has a const property defined, then we should always return the computedDefault since it's coming from the const. obj[key] = computedDefault; } else if (emptyObjectFields !== 'skipDefaults') { if (isObject(computedDefault)) { @@ -190,7 +196,9 @@ export function computeDefaults { + const propertySchema = get(retrievedSchema, [PROPERTIES_KEY, key]); + const hasConst = isObject(propertySchema) && CONST_KEY in propertySchema; // Compute the defaults for this node, with the parent defaults we might // have from a previous run: defaults[key]. - const computedDefault = computeDefaults(validator, get(retrievedSchema, [PROPERTIES_KEY, key]), { + const computedDefault = computeDefaults(validator, propertySchema, { rootSchema, _recurseList, experimental_defaultFormStateBehavior, @@ -331,7 +341,8 @@ export function getObjectDefaults Date: Mon, 7 Oct 2024 21:41:11 +0200 Subject: [PATCH 02/10] fixed issue with default on root level. --- packages/utils/src/schema/getDefaultFormState.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 04f4909871..ed69a8f8a4 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -31,6 +31,7 @@ import { import isMultiSelect from './isMultiSelect'; import retrieveSchema, { resolveDependencies } from './retrieveSchema'; import isConstant from '../isConstant'; +import { JSONSchema7Object } from 'json-schema'; /** Enum that indicates how `schema.additionalItems` should be handled by the `getInnerSchemaForArrayItem()` function. */ @@ -319,10 +320,13 @@ export function getObjectDefaults(validator, schema, rootSchema, formData) : schema; + const parentConst = retrievedSchema[CONST_KEY]; const objectDefaults = Object.keys(retrievedSchema.properties || {}).reduce( (acc: GenericObjectType, key: string) => { const propertySchema = get(retrievedSchema, [PROPERTIES_KEY, key]); - const hasConst = isObject(propertySchema) && CONST_KEY in propertySchema; + // Check if the parent schema has a const property defined, then we should always return the computedDefault since it's coming from the const. + const hasParentConst = isObject(parentConst) && (parentConst as JSONSchema7Object)[key] !== undefined; + const hasConst = (isObject(propertySchema) && CONST_KEY in propertySchema) || hasParentConst; // Compute the defaults for this node, with the parent defaults we might // have from a previous run: defaults[key]. const computedDefault = computeDefaults(validator, propertySchema, { From 3f295ab00c47397cb60be5e273c0c590c72dd0c4 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Mon, 7 Oct 2024 23:19:24 +0200 Subject: [PATCH 03/10] fixed array const populate values --- .../utils/src/schema/getDefaultFormState.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index ed69a8f8a4..489cdedccb 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -459,13 +459,17 @@ export function getArrayDefaults Date: Mon, 7 Oct 2024 23:49:31 +0200 Subject: [PATCH 04/10] fixed array issue and written tests to cover the behavior --- .../utils/src/schema/getDefaultFormState.ts | 2 +- .../test/schema/getDefaultFormStateTest.ts | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 489cdedccb..c8926c1e89 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -461,7 +461,7 @@ export function getArrayDefaults { + const schema: RJSFSchema = { + type: 'object', + properties: { + test: { + type: 'string', + const: 'test', + }, + }, + }; + expect(computeDefaults(testValidator, schema, { rootSchema: schema })).toEqual({ + test: 'test', + }); + }); it('test an object with an optional property that has a nested required property', () => { const schema: RJSFSchema = { type: 'object', @@ -848,6 +862,52 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType requiredProperty: 'foo', }); }); + it('test an object const value populate as field defaults', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + localConst: { + type: 'string', + const: 'local', + }, + RootConst: { + type: 'object', + properties: { + attr1: { + type: 'number', + }, + attr2: { + type: 'boolean', + }, + }, + const: { + attr1: 1, + attr2: true, + }, + }, + RootAndLocalConst: { + type: 'string', + const: 'FromLocal', + }, + }, + const: { + RootAndLocalConst: 'FromRoot', + }, + }; + expect( + getObjectDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { emptyObjectFields: 'skipDefaults' }, + }) + ).toEqual({ + localConst: 'local', + RootConst: { + attr1: 1, + attr2: true, + }, + RootAndLocalConst: 'FromLocal', + }); + }); it('test an object with an additionalProperties', () => { const schema: RJSFSchema = { type: 'object', @@ -1065,6 +1125,29 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType ) ).toEqual(['Raphael', 'Michaelangelo', 'Unknown', 'Unknown']); }); + it('test an array const value populate as defaults', () => { + const schema: RJSFSchema = { + type: 'array', + minItems: 4, + const: ['ConstFromRoot', 'ConstFromRoot'], + items: { + type: 'string', + const: 'Constant', + }, + }; + + expect( + getArrayDefaults( + testValidator, + schema, + { + rootSchema: schema, + includeUndefinedValues: 'excludeObjectChildren', + }, + ['ConstFromRoot', 'ConstFromRoot'] + ) + ).toEqual(['ConstFromRoot', 'ConstFromRoot', 'Constant', 'Constant']); + }); it('test an array with no defaults', () => { const schema: RJSFSchema = { type: 'array', From 444e0b5781a51ad78a9f164f78cf02f5b190b0d3 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Mon, 7 Oct 2024 23:58:34 +0200 Subject: [PATCH 05/10] updated changeLog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc58fbb151..7a09dc8f48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ should change the heading of the (upcoming) version to include a major version b --> +# 5.22.0 + +## @rjsf/utils + +- Made fields with const pre-filled and readonly, fixing [#2600](https://github.com/rjsf-team/react-jsonschema-form/issues/2600) + # 5.21.2 ## @rjsf/core From 6e8755ce383fc4c7b9b5273378319c7971658a18 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Wed, 9 Oct 2024 00:56:32 +0200 Subject: [PATCH 06/10] fixed issue with core failing tests. --- packages/utils/src/schema/getDefaultFormState.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index c8926c1e89..65d2641c56 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -565,12 +565,7 @@ export default function getDefaultFormState< rawFormData: formData, }); - if ( - formData === undefined || - formData === null || - typeof formData === 'string' || - (typeof formData === 'number' && isNaN(formData)) - ) { + if (formData === undefined || formData === null || (typeof formData === 'number' && isNaN(formData))) { // No form data? Use schema defaults. return defaults; } From 7ffad5ba0b4581a870056de672923b10a96e6efb Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Wed, 9 Oct 2024 01:11:23 +0200 Subject: [PATCH 07/10] changed changeLog to rerun tests --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a09dc8f48..bb123fc3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/utils -- Made fields with const pre-filled and readonly, fixing [#2600](https://github.com/rjsf-team/react-jsonschema-form/issues/2600) +- Made fields with const property pre-filled and readonly, fixing [#2600](https://github.com/rjsf-team/react-jsonschema-form/issues/2600) # 5.21.2 From f59ccd370c60831fa276892311385a5da51bd983 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sat, 19 Oct 2024 17:29:32 +0200 Subject: [PATCH 08/10] improvement based on feedback --- .../utils/src/schema/getDefaultFormState.ts | 4 +- .../test/schema/getDefaultFormStateTest.ts | 64 ++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 856d0014ed..f51d41d04a 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -337,7 +337,7 @@ export function getObjectDefaults { const propertySchema = get(retrievedSchema, [PROPERTIES_KEY, key]); - // Check if the parent schema has a const property defined, then we should always return the computedDefault since it's coming from the const. + // Check if the parent schema has a const property defined, then we should always return the computedDefault since it's coming from the const. const hasParentConst = isObject(parentConst) && (parentConst as JSONSchema7Object)[key] !== undefined; const hasConst = (isObject(propertySchema) && CONST_KEY in propertySchema) || hasParentConst; // Compute the defaults for this node, with the parent defaults we might @@ -473,7 +473,7 @@ export function getArrayDefaults { expect(() => getDefaultFormState(testValidator, null as unknown as RJSFSchema)).toThrowError('Invalid schema:'); }); + it('test an object const value merge with formData', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + localConst: { + type: 'string', + const: 'local', + }, + RootConst: { + type: 'object', + properties: { + attr1: { + type: 'number', + }, + attr2: { + type: 'boolean', + }, + }, + const: { + attr1: 1, + attr2: true, + }, + }, + RootAndLocalConst: { + type: 'string', + const: 'FromLocal', + }, + fromFormData: { + type: 'string', + }, + }, + const: { + RootAndLocalConst: 'FromRoot', + }, + }; + expect( + getDefaultFormState( + testValidator, + schema, + { + fromFormData: 'fromFormData', + }, + schema, + false, + { emptyObjectFields: 'skipDefaults' } + ) + ).toEqual({ + localConst: 'local', + RootConst: { + attr1: 1, + attr2: true, + }, + RootAndLocalConst: 'FromLocal', + fromFormData: 'fromFormData', + }); + }); it('getInnerSchemaForArrayItem() item of type boolean returns empty schema', () => { expect(getInnerSchemaForArrayItem({ items: [true] }, AdditionalItemsHandling.Ignore, 0)).toEqual({}); }); @@ -48,7 +104,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType foo: 42, }); }); - it('test computeDefaults that is passed a schema with a cont property', () => { + it('test computeDefaults that is passed a schema with a const property', () => { const schema: RJSFSchema = { type: 'object', properties: { @@ -885,6 +941,9 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType attr2: true, }, }, + fromFormData: { + type: 'string', + }, RootAndLocalConst: { type: 'string', const: 'FromLocal', @@ -898,6 +957,9 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType getObjectDefaults(testValidator, schema, { rootSchema: schema, experimental_defaultFormStateBehavior: { emptyObjectFields: 'skipDefaults' }, + rawFormData: { + fromFormData: 'fromFormData', + }, }) ).toEqual({ localConst: 'local', From ab55c1964ee84a095cfa285af834a22eed733a44 Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Mon, 21 Oct 2024 09:00:52 -0700 Subject: [PATCH 09/10] Update packages/utils/test/schema/getDefaultFormStateTest.ts Adding default that should not be used --- packages/utils/test/schema/getDefaultFormStateTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/utils/test/schema/getDefaultFormStateTest.ts b/packages/utils/test/schema/getDefaultFormStateTest.ts index a799be2b36..faf94572e3 100644 --- a/packages/utils/test/schema/getDefaultFormStateTest.ts +++ b/packages/utils/test/schema/getDefaultFormStateTest.ts @@ -943,6 +943,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, fromFormData: { type: 'string', + default: 'notUsed' }, RootAndLocalConst: { type: 'string', From 9fb9af46806634795c938579129cb0706f84dc99 Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Mon, 21 Oct 2024 09:15:39 -0700 Subject: [PATCH 10/10] Update packages/utils/test/schema/getDefaultFormStateTest.ts Fix linter --- packages/utils/test/schema/getDefaultFormStateTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/test/schema/getDefaultFormStateTest.ts b/packages/utils/test/schema/getDefaultFormStateTest.ts index faf94572e3..f5cd467e5a 100644 --- a/packages/utils/test/schema/getDefaultFormStateTest.ts +++ b/packages/utils/test/schema/getDefaultFormStateTest.ts @@ -943,7 +943,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, fromFormData: { type: 'string', - default: 'notUsed' + default: 'notUsed', }, RootAndLocalConst: { type: 'string',