From e112b7fa37632ab8d444388a1efe8632e3683b4a Mon Sep 17 00:00:00 2001 From: Simone Erba Date: Fri, 14 Feb 2025 15:25:39 +0100 Subject: [PATCH 1/4] first version --- src/createHeadlessForm.js | 31 ++-- src/helpers.js | 33 +++- src/internals/checkIfConditionMatches.js | 6 +- src/tests/conditions.test.js | 194 +++++++++++++++++++++++ src/tests/createHeadlessForm.test.js | 3 - src/yupSchema.js | 11 +- 6 files changed, 254 insertions(+), 24 deletions(-) diff --git a/src/createHeadlessForm.js b/src/createHeadlessForm.js index e8a767321..9b4c60a7d 100644 --- a/src/createHeadlessForm.js +++ b/src/createHeadlessForm.js @@ -118,6 +118,22 @@ function buildFieldParameters(name, fieldProperties, required = [], config = {}, ); } + if (inputType === supportedTypes.GROUP_ARRAY) { + // eslint-disable-next-line no-use-before-define + fields = () => + getFieldsFromJSONSchema( + fieldProperties.items, + { + customProperties: get(config, `customProperties.${name}.customProperties`, {}), + parentID: name, + }, + logic + ).reduce((acc, obj) => { + acc[obj.name] = obj; + return acc; + }, {}); + } + const result = { name, inputType, @@ -242,7 +258,6 @@ function buildField(fieldParams, config, scopedJsonSchema, logic) { const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties({ fieldParams, customProperties, logic, config }); - const calculateCustomValidationPropertiesClosure = calculateCustomValidationProperties( fieldParams, customProperties @@ -300,14 +315,12 @@ function getFieldsFromJSONSchema(scopedJsonSchema, config, logic) { fieldParamsList.forEach((fieldParams) => { if (fieldParams.inputType === 'group-array') { const groupArrayItems = convertJSONSchemaPropertiesToFieldParameters(fieldParams.items); - const groupArrayFields = groupArrayItems.map((groupArrayItem) => { - groupArrayItem.nameKey = groupArrayItem.name; - const customProperties = null; // getCustomPropertiesForField(fieldParams, config); // TODO later support in group-array - const composeFn = getComposeFunctionForField(groupArrayItem, !!customProperties); - return composeFn(groupArrayItem); - }); - - fieldParams.nameKey = fieldParams.name; + const groupArrayFields = groupArrayItems.reduce((acc, groupArrayItem) => { + return { + ...acc, + [groupArrayItem.name]: buildField(groupArrayItem, config, scopedJsonSchema, logic), + }; + }, {}); fieldParams.nthFieldGroup = { name: fieldParams.name, diff --git a/src/helpers.js b/src/helpers.js index f8d9b088a..c6debcc52 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -72,7 +72,11 @@ function hasType(type, typeName) { * @returns */ export function getField(fieldName, fields) { - return fields.find(({ name }) => name === fieldName); + if (Array.isArray(fields)) { + return fields.find(({ name }) => name === fieldName); + } else { + return fields[fieldName]; + } } /** @@ -99,6 +103,10 @@ export function compareFormValueWithSchemaValue(formValue, schemaValue) { // fallback to undefined since JSON-schemas empty values come represented as null const currentPropertyValue = typeof schemaValue === 'number' ? schemaValue : schemaValue || undefined; + if (Array.isArray(formValue)) { + return formValue.some((x) => String(x) === String(currentPropertyValue)); + } + // We're using the stringified version of both values since numeric values from forms come represented as Strings. // By doing this, we're sure that we're comparing the same type. return String(formValue) === String(currentPropertyValue); @@ -190,7 +198,7 @@ export function getPrefillValues(fields, initialValues = {}) { switch (field.type) { case supportedTypes.GROUP_ARRAY: { initialValues[fieldName] = initialValues[fieldName]?.map((subFieldValues) => - getPrefillValues(field.fields(), subFieldValues) + field.fields().map((subField) => getPrefillValues(subField, subFieldValues)) ); break; } @@ -333,7 +341,6 @@ export function processNode({ }) { // Set initial required fields const requiredFields = new Set(accRequired); - // Go through the node properties definition and update each field accordingly Object.keys(node.properties ?? []).forEach((fieldName) => { const field = getField(fieldName, formFields); @@ -420,6 +427,26 @@ export function processNode({ logic, }); } + if (inputType === supportedTypes.GROUP_ARRAY) { + // It's a group array, which might contain scoped conditions + const values = formValues[name]; + if (Array.isArray(values)) { + const newFields = []; + const field = getField(name, formFields); + values.forEach((value) => { + const fields = field.fields(); + processNode({ + node: nestedNode.items, + formValues: value, + formFields: fields, + parentID: name, + logic, + }); + newFields.push(fields); + }); + field.fields = () => newFields; + } + } }); } diff --git a/src/internals/checkIfConditionMatches.js b/src/internals/checkIfConditionMatches.js index 4d1704e71..f9987d575 100644 --- a/src/internals/checkIfConditionMatches.js +++ b/src/internals/checkIfConditionMatches.js @@ -14,7 +14,10 @@ export function checkIfConditionMatchesProperties(node, formValues, formFields, return Object.keys(node.if.properties ?? {}).every((name) => { const currentProperty = node.if.properties[name]; - const value = formValues[name]; + // const value = formValues[name]; + const value = Array.isArray(formValues) + ? formValues.filter((item) => item?.[name] !== undefined).map((x) => x?.[name]) + : formValues[name]; const hasEmptyValue = typeof value === 'undefined' || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null". @@ -30,7 +33,6 @@ export function checkIfConditionMatchesProperties(node, formValues, formFields, // https://json-schema.org/understanding-json-schema/reference/conditionals.html#if-then-else return true; } - if (hasProperty(currentProperty, 'const')) { return compareFormValueWithSchemaValue(value, currentProperty.const); } diff --git a/src/tests/conditions.test.js b/src/tests/conditions.test.js index 9ec72de90..0e13c4031 100644 --- a/src/tests/conditions.test.js +++ b/src/tests/conditions.test.js @@ -708,4 +708,198 @@ describe('Conditionals - bugs and code-smells', () => { // field_c: 'Must be greater or equal to 5', // }); }); + + it('Group array nested condition', () => { + const { fields, handleValidation } = createHeadlessForm({ + type: 'object', + additionalProperties: false, + properties: { + companies: { + title: 'Comapnies', + type: 'array', + default: [], + 'x-jsf-presentation': { + inputType: 'group-array', + }, + items: { + type: 'object', + properties: { + company: { + title: 'Select Company', + type: 'string', + description: 'Choose a company', + oneOf: [ + { + title: 'A', + const: 'A', + }, + { + title: 'B', + const: 'B', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + role: { + title: 'Role', + oneOf: [], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + allOf: [ + { + if: { + properties: { + company: { + const: 'A', + }, + }, + required: ['company'], + }, + then: { + properties: { + role: { + oneOf: [ + { + title: 'admin', + const: 'admin', + }, + { + title: 'user', + const: 'user', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + required: ['role'], + }, + }, + { + if: { + properties: { + company: { + const: 'B', + }, + }, + required: ['company'], + }, + then: { + properties: { + role: { + oneOf: [ + { + title: 'adminB', + const: 'adminB', + }, + { + title: 'userB', + const: 'userB', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + required: ['role'], + }, + }, + ], + required: ['company', 'role'], + }, + }, + }, + required: ['companies'], + }); + + handleValidation({ companies: [{ company: 'A' }, { company: 'B' }] }); + expect(fields[0].fields()[0].role.options).toEqual([ + { label: 'admin', value: 'admin' }, + { label: 'user', value: 'user' }, + ]); + expect(fields[0].fields()[1].role.options).toEqual([ + { label: 'adminB', value: 'adminB' }, + { label: 'userB', value: 'userB' }, + ]); + }); + + it('select multiple conditions', () => { + const { fields, handleValidation } = createHeadlessForm({ + type: 'object', + additionalProperties: false, + properties: { + company: { + title: 'Select Company', + type: 'string', + description: 'Choose a company', + oneOf: [ + { + title: 'A', + const: 'A', + }, + { + title: 'B', + const: 'B', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + role: { + title: 'Role', + oneOf: [], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + allOf: [ + { + if: { + properties: { + company: { + const: 'A', + }, + }, + required: ['company'], + }, + then: { + properties: { + role: { + oneOf: [ + { + title: 'admin', + const: 'admin', + }, + { + title: 'user', + const: 'user', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + required: ['role'], + }, + }, + ], + required: ['company', 'role'], + }); + + handleValidation({ company: 'A' }); + expect(fields[1].options).toEqual([ + { label: 'admin', value: 'admin' }, + { label: 'user', value: 'user' }, + ]); + }); }); diff --git a/src/tests/createHeadlessForm.test.js b/src/tests/createHeadlessForm.test.js index 5d5e12ba4..67a894ac7 100644 --- a/src/tests/createHeadlessForm.test.js +++ b/src/tests/createHeadlessForm.test.js @@ -1604,7 +1604,6 @@ describe('createHeadlessForm', () => { type: 'text', description: 'Enter your child’s full name', maxLength: 255, - nameKey: 'full_name', label: 'Child Full Name', name: 'full_name', required: true, @@ -1616,7 +1615,6 @@ describe('createHeadlessForm', () => { required: true, description: 'Enter your child’s date of birth', maxLength: 255, - nameKey: 'birthdate', }, { type: 'radio', @@ -1635,7 +1633,6 @@ describe('createHeadlessForm', () => { required: true, description: 'We know sex is non-binary but for insurance and payroll purposes, we need to collect this information.', - nameKey: 'sex', }, ]); }); diff --git a/src/yupSchema.js b/src/yupSchema.js index 873ec73c5..d7d9f0a86 100644 --- a/src/yupSchema.js +++ b/src/yupSchema.js @@ -485,13 +485,10 @@ export function buildYupSchema(field, config, logic) { function buildGroupArraySchema() { return object().shape( - propertyFields.nthFieldGroup.fields().reduce( - (schema, groupArrayField) => ({ - ...schema, - [groupArrayField.name]: buildYupSchema(groupArrayField, config)(), - }), - {} - ) + Object.keys(propertyFields.nthFieldGroup.fields()).reduce((acc, key) => { + acc[key] = buildYupSchema(propertyFields.nthFieldGroup.fields()[key], config); + return acc; + }, {}) ); } From 716e28ace121f08d2a92c5e4c72fd53fc447095f Mon Sep 17 00:00:00 2001 From: Simone Erba Date: Fri, 14 Feb 2025 16:39:40 +0100 Subject: [PATCH 2/4] retro-compatible --- src/createHeadlessForm.js | 19 +- src/helpers.js | 7 +- src/internals/checkIfConditionMatches.js | 5 +- src/tests/conditions.test.js | 421 ++++++++++++----------- src/tests/createHeadlessForm.test.js | 3 + src/yupSchema.js | 11 +- 6 files changed, 249 insertions(+), 217 deletions(-) diff --git a/src/createHeadlessForm.js b/src/createHeadlessForm.js index 9b4c60a7d..38ca44e88 100644 --- a/src/createHeadlessForm.js +++ b/src/createHeadlessForm.js @@ -128,10 +128,7 @@ function buildFieldParameters(name, fieldProperties, required = [], config = {}, parentID: name, }, logic - ).reduce((acc, obj) => { - acc[obj.name] = obj; - return acc; - }, {}); + ); } const result = { @@ -315,12 +312,14 @@ function getFieldsFromJSONSchema(scopedJsonSchema, config, logic) { fieldParamsList.forEach((fieldParams) => { if (fieldParams.inputType === 'group-array') { const groupArrayItems = convertJSONSchemaPropertiesToFieldParameters(fieldParams.items); - const groupArrayFields = groupArrayItems.reduce((acc, groupArrayItem) => { - return { - ...acc, - [groupArrayItem.name]: buildField(groupArrayItem, config, scopedJsonSchema, logic), - }; - }, {}); + const groupArrayFields = groupArrayItems.map((groupArrayItem) => { + groupArrayItem.nameKey = groupArrayItem.name; + const customProperties = null; // getCustomPropertiesForField(fieldParams, config); // TODO later support in group-array + const composeFn = getComposeFunctionForField(groupArrayItem, !!customProperties); + return composeFn(groupArrayItem); + }); + + fieldParams.nameKey = fieldParams.name; fieldParams.nthFieldGroup = { name: fieldParams.name, diff --git a/src/helpers.js b/src/helpers.js index c6debcc52..b5591d883 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -103,9 +103,6 @@ export function compareFormValueWithSchemaValue(formValue, schemaValue) { // fallback to undefined since JSON-schemas empty values come represented as null const currentPropertyValue = typeof schemaValue === 'number' ? schemaValue : schemaValue || undefined; - if (Array.isArray(formValue)) { - return formValue.some((x) => String(x) === String(currentPropertyValue)); - } // We're using the stringified version of both values since numeric values from forms come represented as Strings. // By doing this, we're sure that we're comparing the same type. @@ -198,7 +195,7 @@ export function getPrefillValues(fields, initialValues = {}) { switch (field.type) { case supportedTypes.GROUP_ARRAY: { initialValues[fieldName] = initialValues[fieldName]?.map((subFieldValues) => - field.fields().map((subField) => getPrefillValues(subField, subFieldValues)) + getPrefillValues(field.fields(), subFieldValues) ); break; } @@ -444,7 +441,7 @@ export function processNode({ }); newFields.push(fields); }); - field.fields = () => newFields; + field.dynamicFields = newFields; } } }); diff --git a/src/internals/checkIfConditionMatches.js b/src/internals/checkIfConditionMatches.js index f9987d575..9d7e39be0 100644 --- a/src/internals/checkIfConditionMatches.js +++ b/src/internals/checkIfConditionMatches.js @@ -14,10 +14,7 @@ export function checkIfConditionMatchesProperties(node, formValues, formFields, return Object.keys(node.if.properties ?? {}).every((name) => { const currentProperty = node.if.properties[name]; - // const value = formValues[name]; - const value = Array.isArray(formValues) - ? formValues.filter((item) => item?.[name] !== undefined).map((x) => x?.[name]) - : formValues[name]; + const value = formValues[name]; const hasEmptyValue = typeof value === 'undefined' || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null". diff --git a/src/tests/conditions.test.js b/src/tests/conditions.test.js index 0e13c4031..f83444ad6 100644 --- a/src/tests/conditions.test.js +++ b/src/tests/conditions.test.js @@ -422,6 +422,233 @@ describe('Conditional attributes updated', () => { handleValidation({ is_full_time: 'no' }); expect(fields[1].visibilityCondition).toEqual(expect.any(Function)); }); + + it('Group array nested condition', () => { + const { fields, handleValidation } = createHeadlessForm({ + type: 'object', + additionalProperties: false, + properties: { + companies: { + type: 'array', + 'x-jsf-presentation': { + inputType: 'group-array', + }, + items: { + type: 'object', + properties: { + company: { + type: 'string', + oneOf: [ + { + title: 'A', + const: 'A', + }, + { + title: 'B', + const: 'B', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + role: { + title: 'Role', + oneOf: [], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + allOf: [ + { + if: { + properties: { + company: { + const: 'A', + }, + }, + required: ['company'], + }, + then: { + properties: { + role: { + oneOf: [ + { + title: 'adminA', + const: 'adminA', + }, + { + title: 'userA', + const: 'userA', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + required: ['role'], + }, + }, + { + if: { + properties: { + company: { + const: 'B', + }, + }, + required: ['company'], + }, + then: { + properties: { + role: { + oneOf: [ + { + title: 'adminB', + const: 'adminB', + }, + { + title: 'userB', + const: 'userB', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + required: ['role'], + }, + }, + ], + required: ['company', 'role'], + }, + }, + }, + required: ['companies'], + }); + + handleValidation({ companies: [{ company: 'A' }, { company: 'B' }] }); + expect(fields[0].dynamicFields[0][1].options).toEqual([ + { label: 'adminA', value: 'adminA' }, + { label: 'userA', value: 'userA' }, + ]); + expect(fields[0].dynamicFields[1][1].options).toEqual([ + { label: 'adminB', value: 'adminB' }, + { label: 'userB', value: 'userB' }, + ]); + handleValidation({ companies: [] }); + expect(fields[0].dynamicFields).toEqual([]); + }); + + it('select multiple conditions', () => { + const { fields, handleValidation } = createHeadlessForm({ + type: 'object', + additionalProperties: false, + properties: { + company: { + title: 'Select Company', + type: 'string', + description: 'Choose a company', + oneOf: [ + { + title: 'A', + const: 'A', + }, + { + title: 'B', + const: 'B', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + role: { + title: 'Role', + oneOf: [], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + allOf: [ + { + if: { + properties: { + company: { + const: 'A', + }, + }, + required: ['company'], + }, + then: { + properties: { + role: { + oneOf: [ + { + title: 'adminA', + const: 'adminA', + }, + { + title: 'userA', + const: 'userA', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + required: ['role'], + }, + }, + { + if: { + properties: { + company: { + const: 'B', + }, + }, + required: ['company'], + }, + then: { + properties: { + role: { + oneOf: [ + { + title: 'adminB', + const: 'adminB', + }, + { + title: 'userB', + const: 'userB', + }, + ], + 'x-jsf-presentation': { + inputType: 'select', + }, + }, + }, + required: ['role'], + }, + }, + ], + required: ['company', 'role'], + }); + + handleValidation({ company: 'A' }); + expect(fields[1].options).toEqual([ + { label: 'adminA', value: 'adminA' }, + { label: 'userA', value: 'userA' }, + ]); + handleValidation({ company: 'B' }); + expect(fields[1].options).toEqual([ + { label: 'adminB', value: 'adminB' }, + { label: 'userB', value: 'userB' }, + ]); + }); }); describe('Conditional with a minimum value check', () => { @@ -708,198 +935,4 @@ describe('Conditionals - bugs and code-smells', () => { // field_c: 'Must be greater or equal to 5', // }); }); - - it('Group array nested condition', () => { - const { fields, handleValidation } = createHeadlessForm({ - type: 'object', - additionalProperties: false, - properties: { - companies: { - title: 'Comapnies', - type: 'array', - default: [], - 'x-jsf-presentation': { - inputType: 'group-array', - }, - items: { - type: 'object', - properties: { - company: { - title: 'Select Company', - type: 'string', - description: 'Choose a company', - oneOf: [ - { - title: 'A', - const: 'A', - }, - { - title: 'B', - const: 'B', - }, - ], - 'x-jsf-presentation': { - inputType: 'select', - }, - }, - role: { - title: 'Role', - oneOf: [], - 'x-jsf-presentation': { - inputType: 'select', - }, - }, - }, - allOf: [ - { - if: { - properties: { - company: { - const: 'A', - }, - }, - required: ['company'], - }, - then: { - properties: { - role: { - oneOf: [ - { - title: 'admin', - const: 'admin', - }, - { - title: 'user', - const: 'user', - }, - ], - 'x-jsf-presentation': { - inputType: 'select', - }, - }, - }, - required: ['role'], - }, - }, - { - if: { - properties: { - company: { - const: 'B', - }, - }, - required: ['company'], - }, - then: { - properties: { - role: { - oneOf: [ - { - title: 'adminB', - const: 'adminB', - }, - { - title: 'userB', - const: 'userB', - }, - ], - 'x-jsf-presentation': { - inputType: 'select', - }, - }, - }, - required: ['role'], - }, - }, - ], - required: ['company', 'role'], - }, - }, - }, - required: ['companies'], - }); - - handleValidation({ companies: [{ company: 'A' }, { company: 'B' }] }); - expect(fields[0].fields()[0].role.options).toEqual([ - { label: 'admin', value: 'admin' }, - { label: 'user', value: 'user' }, - ]); - expect(fields[0].fields()[1].role.options).toEqual([ - { label: 'adminB', value: 'adminB' }, - { label: 'userB', value: 'userB' }, - ]); - }); - - it('select multiple conditions', () => { - const { fields, handleValidation } = createHeadlessForm({ - type: 'object', - additionalProperties: false, - properties: { - company: { - title: 'Select Company', - type: 'string', - description: 'Choose a company', - oneOf: [ - { - title: 'A', - const: 'A', - }, - { - title: 'B', - const: 'B', - }, - ], - 'x-jsf-presentation': { - inputType: 'select', - }, - }, - role: { - title: 'Role', - oneOf: [], - 'x-jsf-presentation': { - inputType: 'select', - }, - }, - }, - allOf: [ - { - if: { - properties: { - company: { - const: 'A', - }, - }, - required: ['company'], - }, - then: { - properties: { - role: { - oneOf: [ - { - title: 'admin', - const: 'admin', - }, - { - title: 'user', - const: 'user', - }, - ], - 'x-jsf-presentation': { - inputType: 'select', - }, - }, - }, - required: ['role'], - }, - }, - ], - required: ['company', 'role'], - }); - - handleValidation({ company: 'A' }); - expect(fields[1].options).toEqual([ - { label: 'admin', value: 'admin' }, - { label: 'user', value: 'user' }, - ]); - }); }); diff --git a/src/tests/createHeadlessForm.test.js b/src/tests/createHeadlessForm.test.js index 67a894ac7..803b569b8 100644 --- a/src/tests/createHeadlessForm.test.js +++ b/src/tests/createHeadlessForm.test.js @@ -1606,11 +1606,13 @@ describe('createHeadlessForm', () => { maxLength: 255, label: 'Child Full Name', name: 'full_name', + nameKey: 'full_name', required: true, }, { type: 'date', name: 'birthdate', + nameKey: 'birthday', label: 'Child Birthdate', required: true, description: 'Enter your child’s date of birth', @@ -1619,6 +1621,7 @@ describe('createHeadlessForm', () => { { type: 'radio', name: 'sex', + nameKey: 'sex', label: 'Child Sex', options: [ { diff --git a/src/yupSchema.js b/src/yupSchema.js index d7d9f0a86..873ec73c5 100644 --- a/src/yupSchema.js +++ b/src/yupSchema.js @@ -485,10 +485,13 @@ export function buildYupSchema(field, config, logic) { function buildGroupArraySchema() { return object().shape( - Object.keys(propertyFields.nthFieldGroup.fields()).reduce((acc, key) => { - acc[key] = buildYupSchema(propertyFields.nthFieldGroup.fields()[key], config); - return acc; - }, {}) + propertyFields.nthFieldGroup.fields().reduce( + (schema, groupArrayField) => ({ + ...schema, + [groupArrayField.name]: buildYupSchema(groupArrayField, config)(), + }), + {} + ) ); } From 2e8a7e77e32eb5947e525bc4bceb8c488367a941 Mon Sep 17 00:00:00 2001 From: Simone Erba Date: Fri, 14 Feb 2025 16:55:04 +0100 Subject: [PATCH 3/4] fix nested group array --- src/createHeadlessForm.js | 3 -- src/tests/createHeadlessForm.test.js | 61 ++++++++++++++++++++++++++-- src/tests/helpers.js | 56 +++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/src/createHeadlessForm.js b/src/createHeadlessForm.js index 38ca44e88..4dbebe654 100644 --- a/src/createHeadlessForm.js +++ b/src/createHeadlessForm.js @@ -313,14 +313,11 @@ function getFieldsFromJSONSchema(scopedJsonSchema, config, logic) { if (fieldParams.inputType === 'group-array') { const groupArrayItems = convertJSONSchemaPropertiesToFieldParameters(fieldParams.items); const groupArrayFields = groupArrayItems.map((groupArrayItem) => { - groupArrayItem.nameKey = groupArrayItem.name; const customProperties = null; // getCustomPropertiesForField(fieldParams, config); // TODO later support in group-array const composeFn = getComposeFunctionForField(groupArrayItem, !!customProperties); return composeFn(groupArrayItem); }); - fieldParams.nameKey = fieldParams.name; - fieldParams.nthFieldGroup = { name: fieldParams.name, label: fieldParams.label, diff --git a/src/tests/createHeadlessForm.test.js b/src/tests/createHeadlessForm.test.js index 803b569b8..875198868 100644 --- a/src/tests/createHeadlessForm.test.js +++ b/src/tests/createHeadlessForm.test.js @@ -63,6 +63,7 @@ import { schemaForErrorMessageSpecificity, jsfConfigForErrorMessageSpecificity, schemaInputTypeFile, + nestedGroupArrayForm, } from './helpers'; import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils'; import { createHeadlessForm } from '@/createHeadlessForm'; @@ -1606,13 +1607,11 @@ describe('createHeadlessForm', () => { maxLength: 255, label: 'Child Full Name', name: 'full_name', - nameKey: 'full_name', required: true, }, { type: 'date', name: 'birthdate', - nameKey: 'birthday', label: 'Child Birthdate', required: true, description: 'Enter your child’s date of birth', @@ -1621,7 +1620,6 @@ describe('createHeadlessForm', () => { { type: 'radio', name: 'sex', - nameKey: 'sex', label: 'Child Sex', options: [ { @@ -1737,6 +1735,63 @@ describe('createHeadlessForm', () => { }); }); + it('nested "group-array" fields', () => { + const result = createHeadlessForm({ + properties: { + nestedGroupArray: nestedGroupArrayForm, + }, + }); + + expect(result.fields[0]).toMatchObject({ + label: 'Parent object', + name: 'nestedGroupArray', + required: false, + type: 'group-array', + inputType: 'group-array', + jsonType: 'array', + fields: expect.any(Function), + }); + + expect(result.fields[0].fields()).toMatchObject([ + { + type: 'text', + description: 'Simple text field', + maxLength: 255, + label: 'Outer Field', + name: 'notNested', + required: true, + }, + { + label: 'Nested group-array', + name: 'nested', + required: true, + type: 'group-array', + inputType: 'group-array', + jsonType: 'array', + fields: expect.any(Function), + }, + ]); + + expect(result.fields[0].fields()[1].fields()).toMatchObject([ + { + type: 'text', + description: 'First nested text field', + maxLength: 255, + label: 'Inner Field 1', + name: 'nestedField1', + required: true, + }, + { + type: 'text', + description: 'Second nested text field', + maxLength: 255, + label: 'Inner Field 2', + name: 'nestedField2', + required: true, + }, + ]); + }); + it('can pass custom field attributes', () => { const result = createHeadlessForm( { diff --git a/src/tests/helpers.js b/src/tests/helpers.js index e07d3ee7c..a1784a7c7 100644 --- a/src/tests/helpers.js +++ b/src/tests/helpers.js @@ -2327,3 +2327,59 @@ export const schemaWithCustomValidationsAndConditionals = { }, ], }; + +export const nestedGroupArrayForm = { + items: { + properties: { + notNested: { + description: 'Simple text field', + 'x-jsf-presentation': { + inputType: 'text', + }, + title: 'Outer Field', + type: 'string', + maxLength: 255, + }, + nested: { + items: { + properties: { + nestedField1: { + description: 'First nested text field', + 'x-jsf-presentation': { + inputType: 'text', + }, + title: 'Inner Field 1', + type: 'string', + maxLength: 255, + }, + nestedField2: { + description: 'Second nested text field', + 'x-jsf-presentation': { + inputType: 'text', + }, + title: 'Inner Field 2', + type: 'string', + maxLength: 255, + }, + }, + 'x-jsf-order': ['nestedField1', 'nestedField2'], + required: ['nestedField1', 'nestedField2'], + type: 'object', + }, + 'x-jsf-presentation': { + inputType: 'group-array', + }, + title: 'Nested group-array', + type: 'array', + }, + }, + 'x-jsf-order': ['notNested', 'nested'], + required: ['notNested', 'nested'], + type: 'object', + }, + 'x-jsf-presentation': { + inputType: 'group-array', + }, + title: 'Parent object', + type: 'array', +}; From bce4a3dcdb5faaaac3c3fc0037e7ccc23592c459 Mon Sep 17 00:00:00 2001 From: Sandrina Pereira Date: Thu, 20 Feb 2025 17:48:57 +0000 Subject: [PATCH 4/4] Release 0.11.11-dev.20250220174843 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4137bc28f..0c07f856b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@remoteoss/json-schema-form", - "version": "0.11.10-beta.0", + "version": "0.11.11-dev.20250220174843", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@remoteoss/json-schema-form", - "version": "0.11.10-beta.0", + "version": "0.11.11-dev.20250220174843", "license": "MIT", "dependencies": { "json-logic-js": "^2.0.2", diff --git a/package.json b/package.json index 1b98b1035..6617f2179 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@remoteoss/json-schema-form", - "version": "0.11.10-beta.0", + "version": "0.11.11-dev.20250220174843", "description": "Headless UI form powered by JSON Schemas", "author": "Remote.com (https://remote.com/)", "license": "MIT",