Skip to content

Commit 4a66514

Browse files
Support nested fieldset conditionals
1 parent 8eafe97 commit 4a66514

File tree

3 files changed

+163
-16
lines changed

3 files changed

+163
-16
lines changed

src/helpers.js

+37-16
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ export function processNode({
330330
accRequired = new Set(),
331331
parentID = 'root',
332332
logic,
333+
processingConditional = false,
333334
}) {
334335
// Set initial required fields
335336
const requiredFields = new Set(accRequired);
@@ -338,6 +339,24 @@ export function processNode({
338339
Object.keys(node.properties ?? []).forEach((fieldName) => {
339340
const field = getField(fieldName, formFields);
340341
updateField(field, requiredFields, node, formValues, logic, { parentID });
342+
343+
// If we're processing a conditional field node and it's respective to a fieldset field,
344+
// update the nested fields going through the node recursively.
345+
// As an example, the node here can be:
346+
// 1. { properties: { perks: { properties: { retirement: { const: 'basic' } } } } } }
347+
// 2. { properties: { perks: { required: ['retirement'] } } } }
348+
// where 'perks' is a fieldset field.
349+
const nestedNode = node.properties[fieldName];
350+
const isFieldset = field?.inputType === supportedTypes.FIELDSET;
351+
if (isFieldset && processingConditional) {
352+
processNode({
353+
node: nestedNode,
354+
formValues: formValues[fieldName] || {},
355+
formFields: field.fields,
356+
parentID,
357+
logic,
358+
});
359+
}
341360
});
342361

343362
// Update required fields based on the `required` property and mutate node if needed
@@ -360,6 +379,7 @@ export function processNode({
360379
accRequired: requiredFields,
361380
parentID,
362381
logic,
382+
processingConditional: true,
363383
});
364384

365385
branchRequired.forEach((field) => requiredFields.add(field));
@@ -371,6 +391,7 @@ export function processNode({
371391
accRequired: requiredFields,
372392
parentID,
373393
logic,
394+
processingConditional: true,
374395
});
375396
branchRequired.forEach((field) => requiredFields.add(field));
376397
}
@@ -390,6 +411,22 @@ export function processNode({
390411
});
391412
}
392413

414+
if (node.properties) {
415+
Object.entries(node.properties).forEach(([name, nestedNode]) => {
416+
const inputType = getInputType(nestedNode);
417+
if (inputType === supportedTypes.FIELDSET) {
418+
// It's a fieldset, which might contain scoped conditions
419+
processNode({
420+
node: nestedNode,
421+
formValues: formValues[name] || {},
422+
formFields: getField(name, formFields).fields,
423+
parentID: name,
424+
logic,
425+
});
426+
}
427+
});
428+
}
429+
393430
if (node.allOf) {
394431
node.allOf
395432
.map((allOfNode) =>
@@ -407,22 +444,6 @@ export function processNode({
407444
});
408445
}
409446

410-
if (node.properties) {
411-
Object.entries(node.properties).forEach(([name, nestedNode]) => {
412-
const inputType = getInputType(nestedNode);
413-
if (inputType === supportedTypes.FIELDSET) {
414-
// It's a fieldset, which might contain scoped conditions
415-
processNode({
416-
node: nestedNode,
417-
formValues: formValues[name] || {},
418-
formFields: getField(name, formFields).fields,
419-
parentID: name,
420-
logic,
421-
});
422-
}
423-
});
424-
}
425-
426447
if (node['x-jsf-logic']) {
427448
const { required: requiredFromLogic } = processJSONLogicNode({
428449
node: node['x-jsf-logic'],

src/tests/createHeadlessForm.test.js

+42
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
schemaForErrorMessageSpecificity,
6464
jsfConfigForErrorMessageSpecificity,
6565
schemaInputTypeFile,
66+
schemaWithRootFieldsetsConditionals,
6667
} from './helpers';
6768
import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils';
6869
import { createHeadlessForm } from '@/createHeadlessForm';
@@ -2162,6 +2163,47 @@ describe('createHeadlessForm', () => {
21622163
).toBeUndefined();
21632164
});
21642165
});
2166+
2167+
describe('supports root fieldsets conditionals', () => {
2168+
it('Given a basic retirement, the perks.has_pension is hidden', async () => {
2169+
const { fields, handleValidation } = createHeadlessForm(
2170+
schemaWithRootFieldsetsConditionals,
2171+
{}
2172+
);
2173+
const validateForm = (vals) => friendlyError(handleValidation(vals));
2174+
2175+
expect(validateForm({})).toEqual({
2176+
perks: {
2177+
retirement: 'Required field',
2178+
},
2179+
});
2180+
2181+
// has_pension is not visible
2182+
expect(getField(fields, 'perks', 'has_pension').isVisible).toBe(false);
2183+
2184+
expect(
2185+
validateForm({
2186+
perks: { retirement: 'plus' },
2187+
})
2188+
).toEqual({
2189+
perks: {
2190+
has_pension: 'Required field',
2191+
},
2192+
});
2193+
2194+
// field becomes visible
2195+
expect(getField(fields, 'perks', 'has_pension').isVisible).toBe(true);
2196+
2197+
expect(
2198+
validateForm({
2199+
perks: { retirement: 'basic' },
2200+
})
2201+
).toBeUndefined();
2202+
2203+
// field becomes invisible
2204+
expect(getField(fields, 'perks', 'has_pension').isVisible).toBe(false);
2205+
});
2206+
});
21652207
});
21662208

21672209
it('support "email" field type', () => {

src/tests/helpers.js

+84
Original file line numberDiff line numberDiff line change
@@ -1973,6 +1973,90 @@ export const schemaWithConditionalToFieldset = {
19731973
required: ['perks', 'work_hours_per_week'],
19741974
};
19751975

1976+
export const schemaWithRootFieldsetsConditionals = {
1977+
additionalProperties: false,
1978+
type: 'object',
1979+
properties: {
1980+
perks: {
1981+
additionalProperties: false,
1982+
properties: {
1983+
retirement: {
1984+
oneOf: [
1985+
{
1986+
const: 'basic',
1987+
title: 'Basic',
1988+
},
1989+
{
1990+
const: 'plus',
1991+
title: 'Plus',
1992+
},
1993+
],
1994+
title: 'Retirement',
1995+
type: 'string',
1996+
'x-jsf-presentation': {
1997+
inputType: 'radio',
1998+
},
1999+
},
2000+
has_pension: {
2001+
oneOf: [
2002+
{
2003+
const: 'yes',
2004+
title: 'Yes',
2005+
},
2006+
{
2007+
const: 'no',
2008+
title: 'No',
2009+
},
2010+
],
2011+
title: 'Has pension',
2012+
type: 'string',
2013+
'x-jsf-presentation': {
2014+
inputType: 'radio',
2015+
},
2016+
},
2017+
},
2018+
required: ['retirement'],
2019+
title: 'Perks',
2020+
type: 'object',
2021+
'x-jsf-presentation': {
2022+
inputType: 'fieldset',
2023+
},
2024+
},
2025+
},
2026+
required: ['perks'],
2027+
allOf: [
2028+
{
2029+
if: {
2030+
properties: {
2031+
perks: {
2032+
properties: {
2033+
retirement: {
2034+
const: 'basic',
2035+
},
2036+
},
2037+
},
2038+
},
2039+
},
2040+
then: {
2041+
properties: {
2042+
perks: {
2043+
properties: {
2044+
has_pension: false,
2045+
},
2046+
},
2047+
},
2048+
},
2049+
else: {
2050+
properties: {
2051+
perks: {
2052+
required: ['has_pension'],
2053+
},
2054+
},
2055+
},
2056+
},
2057+
],
2058+
};
2059+
19762060
export const schemaWorkSchedule = {
19772061
type: 'object',
19782062
properties: {

0 commit comments

Comments
 (0)