From 4ffc0bfcee59033bd9c109b960426e45e0445585 Mon Sep 17 00:00:00 2001 From: Dan O'Neill Date: Tue, 15 Sep 2026 13:36:40 -0700 Subject: [PATCH 1/3] Remvoe conditionsMatch param, group multiple conditions by feild --- src/v2/conditions.ts | 45 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/v2/conditions.ts b/src/v2/conditions.ts index 15d08f6..37c559e 100644 --- a/src/v2/conditions.ts +++ b/src/v2/conditions.ts @@ -340,6 +340,34 @@ export const buildConditionSchema = ( }; }; +/** + * Groups conditions by field, preserving the order fields first appear in. + */ +const groupConditionsByField = (conditions: V2Condition[]): V2Condition[][] => { + const order: string[] = []; + const groups: Record = {}; + + conditions.forEach((condition) => { + if (!groups[condition.field]) { + groups[condition.field] = []; + order.push(condition.field); + } + groups[condition.field].push(condition); + }); + + return order.map((field) => groups[field]); +}; + +const buildFieldGroupSchema = (group: V2Condition[]): Record => { + if (group.length === 1) { + return buildConditionSchema(group[0]); + } + + return { + anyOf: group.map((condition) => buildConditionSchema(condition)), + }; +}; + /** * Builds a JSONForms schema-based condition from one or more V2 conditions. * @@ -347,7 +375,10 @@ export const buildConditionSchema = ( * - IS_EMPTY → root scope "#" (must check for field absence at object level) * - All others → field scope "#/properties/fieldName" * - * Multiple conditions: root scope "#" with allOf combining all condition schemas. + * Multiple conditions: grouped by field, then combined at root scope "#" — + * conditions on the same field are OR'd together (anyOf) as alternative + * values, and the resulting per-field groups are AND'd together (allOf) as + * independent requirements. */ export const buildSchemaBasedCondition = ( conditions: V2Condition[], @@ -372,13 +403,19 @@ export const buildSchemaBasedCondition = ( }; } - // Multiple conditions: combine with allOf at root scope - const allOfSchemas = conditions.map((condition) => buildConditionSchema(condition)); + const groups = groupConditionsByField(conditions); + + if (groups.length === 1) { + return { + scope: '#', + schema: buildFieldGroupSchema(groups[0]), + }; + } return { scope: '#', schema: { - allOf: allOfSchemas, + allOf: groups.map((group) => buildFieldGroupSchema(group)), }, }; }; From 1b9c4223e7e9f68f52be1b7d29c279d2a1e35578 Mon Sep 17 00:00:00 2001 From: Dan O'Neill Date: Tue, 15 Sep 2026 13:36:48 -0700 Subject: [PATCH 2/3] update tests --- test/v2.conditions.test.ts | 74 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/v2.conditions.test.ts b/test/v2.conditions.test.ts index b1e25db..2fb4e71 100644 --- a/test/v2.conditions.test.ts +++ b/test/v2.conditions.test.ts @@ -580,6 +580,64 @@ describe('buildSchemaBasedCondition', () => { }); }); }); + + describe('field grouping', () => { + it('same-field conditions are OR\'d (anyOf) with no schema flag needed', () => { + const result = buildSchemaBasedCondition([ + makeCondition('yes_or_no', 'IS_EXACTLY', 'yes'), + makeCondition('yes_or_no', 'IS_EXACTLY', 'no'), + ]) as any; + expect(result.schema.anyOf).toHaveLength(2); + expect(result.schema.allOf).toBeUndefined(); + }); + + it('each same-field condition is still field-wrapped under anyOf', () => { + const result = buildSchemaBasedCondition([ + makeCondition('yes_or_no', 'IS_EXACTLY', 'yes'), + makeCondition('yes_or_no', 'IS_EXACTLY', 'no'), + ]) as any; + expect(result.schema.anyOf[0].properties.yes_or_no).toBeDefined(); + expect(result.schema.anyOf[0].required).toContain('yes_or_no'); + }); + + it('three same-field conditions all OR together', () => { + const result = buildSchemaBasedCondition([ + makeCondition('status', 'IS_EXACTLY', 'active'), + makeCondition('status', 'IS_EXACTLY', 'pending'), + makeCondition('status', 'IS_EXACTLY', 'closed'), + ]) as any; + expect(result.schema.anyOf).toHaveLength(3); + }); + + it('different-field conditions are AND\'d (allOf), unchanged from before', () => { + const result = buildSchemaBasedCondition([ + makeCondition('injured_animal', 'CONTAINS', 'elephant'), + makeCondition('requires_attention', 'IS_NOT_EMPTY'), + ]) as any; + expect(result.schema.allOf).toHaveLength(2); + expect(result.schema.anyOf).toBeUndefined(); + }); + + it('mixes grouping: same-field group OR\'d, then AND\'d with other fields', () => { + const result = buildSchemaBasedCondition([ + makeCondition('yes_or_no', 'IS_EXACTLY', 'yes'), + makeCondition('yes_or_no', 'IS_EXACTLY', 'no'), + makeCondition('requires_attention', 'IS_NOT_EMPTY'), + ]) as any; + expect(result.schema.allOf).toHaveLength(2); + // First group: the two yes_or_no conditions, OR'd + expect(result.schema.allOf[0].anyOf).toHaveLength(2); + // Second group: the single requires_attention condition, not wrapped in anyOf + expect(result.schema.allOf[1].properties.requires_attention).toBeDefined(); + }); + + it('field grouping does not apply to a single condition', () => { + const result = buildSchemaBasedCondition([ + makeCondition('injured_animal', 'CONTAINS', 'elephant'), + ]); + expect(result.scope).toBe('#/properties/injured_animal'); + }); + }); }); // --------------------------------------------------------------------------- @@ -607,6 +665,22 @@ describe('createSectionRule', () => { ]); expect(rule.condition.scope).toBe('#'); }); + + it('uses anyOf for multiple conditions on the same field, with no flag needed', () => { + const rule = createSectionRule([ + { field: 'yes_or_no', id: 'c1', operator: 'IS_EXACTLY', value: 'yes' }, + { field: 'yes_or_no', id: 'c2', operator: 'IS_EXACTLY', value: 'no' }, + ]) as any; + expect(rule.condition.schema.anyOf).toHaveLength(2); + }); + + it('uses allOf for multiple conditions on different fields', () => { + const rule = createSectionRule([ + { field: 'injured_animal', id: 'c1', operator: 'CONTAINS', value: 'elephant' }, + { field: 'requires_attention', id: 'c2', operator: 'IS_NOT_EMPTY' }, + ]) as any; + expect(rule.condition.schema.allOf).toHaveLength(2); + }); }); // --------------------------------------------------------------------------- From 58c04f3970bdcaed13b69d1a54c918457a4e52dd Mon Sep 17 00:00:00 2001 From: Dan O'Neill Date: Tue, 15 Sep 2026 13:36:57 -0700 Subject: [PATCH 3/3] update dependency version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac1e1b9..9503b93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@earthranger/react-native-jsonforms-formatter", - "version": "2.0.0-beta.33", + "version": "2.0.0-beta.35", "description": "Converts JTD into JSON Schema ", "main": "./dist/bundle.js", "types": "./dist/index.d.ts",