From c899f0a4563e46bfa08506b901ff1b31c13f8e2e Mon Sep 17 00:00:00 2001 From: choonkeat Date: Wed, 12 Nov 2025 16:59:05 +0800 Subject: [PATCH 1/2] test: add failing test for EqualsField with multi-value fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test cases to verify that EqualsField comparison should check if ANY value from one field matches ANY value from another field, aligning with Elm's behavior. Current Go implementation only compares first values, causing the test to fail. Test case shows: - skills: ["Go", "Elm"] - preferred_skills: ["Python", "Elm", "JavaScript"] - Expected: "Elm" overlap means condition is met (field hidden) - Current: "Go" != "Python" so condition fails (field visible) Equivalent Elm integration tests for EqualsField with multi-value fields passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- go/validate_test.go | 116 ++++++++++++++++++++++++++++ tests/MainTest.elm | 180 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+) diff --git a/go/validate_test.go b/go/validate_test.go index a37c73f..37e1674 100644 --- a/go/validate_test.go +++ b/go/validate_test.go @@ -1531,6 +1531,122 @@ func TestVisibilityRules(t *testing.T) { }, expectedError: ErrRequiredFieldMissing, }, + { + name: "EqualsField with multi-value fields - any value matches (Elm behavior)", + formFields: ` + [ + { + "name": "skills", + "type": { + "type": "ChooseMultiple", + "choices": ["Go", "Elm", "JavaScript", "Python"] + }, + "label": "Your Skills", + "presence": "Required" + }, + { + "name": "preferred_skills", + "type": { + "type": "ChooseMultiple", + "choices": ["Go", "Elm", "JavaScript", "Python"] + }, + "label": "Preferred Skills", + "presence": "Required" + }, + { + "type": { + "type": "ShortText", + "inputType": "text", + "attributes": { + "type": "text", + "class": "size-0-invisible", + "value": "form-invalid", + "pattern": "form-ok" + } + }, + "label": "Skills Match Indicator", + "presence": "Required", + "visibilityRule": [ + { + "type": "HideWhen", + "conditions": [ + { + "type": "Field", + "fieldName": "skills", + "comparison": { + "type": "EqualsField", + "value": "preferred_skills" + } + } + ] + } + ] + } + ]`, + values: url.Values{ + "skills": []string{"Go", "Elm"}, + "preferred_skills": []string{"Python", "Elm", "JavaScript"}, + }, + expectedError: nil, // Should pass - "Elm" is in both lists, so condition is met, field is hidden + }, + { + name: "EqualsField with multi-value fields - no overlap", + formFields: ` + [ + { + "name": "skills", + "type": { + "type": "ChooseMultiple", + "choices": ["Go", "Elm", "JavaScript", "Python"] + }, + "label": "Your Skills", + "presence": "Required" + }, + { + "name": "preferred_skills", + "type": { + "type": "ChooseMultiple", + "choices": ["Go", "Elm", "JavaScript", "Python"] + }, + "label": "Preferred Skills", + "presence": "Required" + }, + { + "type": { + "type": "ShortText", + "inputType": "text", + "attributes": { + "type": "text", + "class": "size-0-invisible", + "value": "form-invalid", + "pattern": "form-ok" + } + }, + "label": "Skills Match Indicator", + "presence": "Required", + "visibilityRule": [ + { + "type": "HideWhen", + "conditions": [ + { + "type": "Field", + "fieldName": "skills", + "comparison": { + "type": "EqualsField", + "value": "preferred_skills" + } + } + ] + } + ] + } + ]`, + values: url.Values{ + "skills": []string{"Go", "Elm"}, + "preferred_skills": []string{"Python", "JavaScript"}, + }, + expectedError: ErrRequiredFieldMissing, // Should fail - no overlap, so field is visible but missing value + }, } for _, tt := range scenarios { diff --git a/tests/MainTest.elm b/tests/MainTest.elm index 3064830..a3dc273 100644 --- a/tests/MainTest.elm +++ b/tests/MainTest.elm @@ -612,6 +612,186 @@ suite = -- Comparison is as-is: whitespace and duplicates are not normalized Main.evaluateCondition values (Main.Field "a" (Main.EqualsField "b")) |> Expect.equal True + , test "EqualsField with multi-value fields - any value matches (integration test)" <| + \_ -> + let + -- Mirror the Go test: ChooseMultiple fields with EqualsField visibility rule + formFields = + Array.fromList + [ { label = "Your Skills" + , name = Just "skills" + , presence = Main.Required + , description = Main.AttributeNotNeeded + , type_ = + Main.ChooseMultiple + { choices = + [ { label = "Go", value = "Go" } + , { label = "Elm", value = "Elm" } + , { label = "JavaScript", value = "JavaScript" } + , { label = "Python", value = "Python" } + ] + , filter = Nothing + , maxAllowed = Nothing + , minRequired = Nothing + } + , visibilityRule = [] + } + , { label = "Preferred Skills" + , name = Just "preferred_skills" + , presence = Main.Required + , description = Main.AttributeNotNeeded + , type_ = + Main.ChooseMultiple + { choices = + [ { label = "Go", value = "Go" } + , { label = "Elm", value = "Elm" } + , { label = "JavaScript", value = "JavaScript" } + , { label = "Python", value = "Python" } + ] + , filter = Nothing + , maxAllowed = Nothing + , minRequired = Nothing + } + , visibilityRule = [] + } + , { label = "Skills Match Indicator" + , name = Nothing + , presence = Main.Required + , description = Main.AttributeNotNeeded + , type_ = + Main.ShortText + (Main.fromRawCustomElement + { inputType = "text" + , inputTag = "input" + , attributes = + Dict.fromList + [ ( "type", "text" ) + , ( "class", "size-0-invisible" ) + , ( "value", "form-invalid" ) + , ( "pattern", "form-ok" ) + ] + } + ) + , visibilityRule = + [ Main.HideWhen + [ Main.Field "skills" (Main.EqualsField "preferred_skills") ] + ] + } + ] + + -- Values with overlap: "Elm" is in both lists + values = + Dict.fromList + [ ( "skills", [ "Go", "Elm" ] ) + , ( "preferred_skills", [ "Python", "Elm", "JavaScript" ] ) + ] + in + Expect.all + [ \_ -> + -- The EqualsField condition should be met (overlap exists) + Main.evaluateCondition values (Main.Field "skills" (Main.EqualsField "preferred_skills")) + |> Expect.equal True + , \_ -> + -- The third field should be hidden (HideWhen condition met) + case Array.get 2 formFields of + Just field -> + Main.isVisibilityRuleSatisfied field.visibilityRule values + |> Expect.equal False + + Nothing -> + Expect.fail "Field 2 not found" + ] + () + , test "EqualsField with multi-value fields - no overlap (integration test)" <| + \_ -> + let + -- Same form structure as above + formFields = + Array.fromList + [ { label = "Your Skills" + , name = Just "skills" + , presence = Main.Required + , description = Main.AttributeNotNeeded + , type_ = + Main.ChooseMultiple + { choices = + [ { label = "Go", value = "Go" } + , { label = "Elm", value = "Elm" } + , { label = "JavaScript", value = "JavaScript" } + , { label = "Python", value = "Python" } + ] + , filter = Nothing + , maxAllowed = Nothing + , minRequired = Nothing + } + , visibilityRule = [] + } + , { label = "Preferred Skills" + , name = Just "preferred_skills" + , presence = Main.Required + , description = Main.AttributeNotNeeded + , type_ = + Main.ChooseMultiple + { choices = + [ { label = "Go", value = "Go" } + , { label = "Elm", value = "Elm" } + , { label = "JavaScript", value = "JavaScript" } + , { label = "Python", value = "Python" } + ] + , filter = Nothing + , maxAllowed = Nothing + , minRequired = Nothing + } + , visibilityRule = [] + } + , { label = "Skills Match Indicator" + , name = Nothing + , presence = Main.Required + , description = Main.AttributeNotNeeded + , type_ = + Main.ShortText + (Main.fromRawCustomElement + { inputType = "text" + , inputTag = "input" + , attributes = + Dict.fromList + [ ( "type", "text" ) + , ( "class", "size-0-invisible" ) + , ( "value", "form-invalid" ) + , ( "pattern", "form-ok" ) + ] + } + ) + , visibilityRule = + [ Main.HideWhen + [ Main.Field "skills" (Main.EqualsField "preferred_skills") ] + ] + } + ] + + -- Values with NO overlap + values = + Dict.fromList + [ ( "skills", [ "Go", "Elm" ] ) + , ( "preferred_skills", [ "Python", "JavaScript" ] ) + ] + in + Expect.all + [ \_ -> + -- The EqualsField condition should NOT be met (no overlap) + Main.evaluateCondition values (Main.Field "skills" (Main.EqualsField "preferred_skills")) + |> Expect.equal False + , \_ -> + -- The third field should be visible (HideWhen condition not met) + case Array.get 2 formFields of + Just field -> + Main.isVisibilityRuleSatisfied field.visibilityRule values + |> Expect.equal True + + Nothing -> + Expect.fail "Field 2 not found" + ] + () ] , describe "list attribute handling" [ test "fromRawCustomElement removes list attribute" <| From d33f94957bf887893746805de888811fba4ddf93 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Wed, 12 Nov 2025 17:04:33 +0800 Subject: [PATCH 2/2] fix: align EqualsField to check ANY value overlap (Elm behavior) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update Go's EqualsField comparison to check if ANY value from one field matches ANY value from another field, matching Elm's behavior. Previous implementation: - Used values.Get() which only compared first values - fieldA[0] == fieldB[0] New implementation: - Iterates through all values from both fields - Returns true if any value overlaps - Handles both single-value and multi-value fields correctly This ensures consistent behavior between Go backend validation and Elm frontend logic for multi-select fields, checkboxes, etc. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- go/validate.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/go/validate.go b/go/validate.go index fc406eb..d951416 100644 --- a/go/validate.go +++ b/go/validate.go @@ -602,8 +602,21 @@ func isVisibilityRuleSatisfied(rule VisibilityRule, values url.Values) bool { conditionMet = strings.HasSuffix(fieldValue, condition.Comparison.Value) case "EqualsField": // the value stored in Comparison.Value is the name of another field - comparisonValue := values.Get(condition.Comparison.Value) - conditionMet = fieldValue == comparisonValue + // Check if ANY value from fieldName matches ANY value from the other field + fieldValues := values[condition.FieldName] + otherFieldValues := values[condition.Comparison.Value] + conditionMet = false + for _, fv := range fieldValues { + for _, ofv := range otherFieldValues { + if fv == ofv { + conditionMet = true + break + } + } + if conditionMet { + break + } + } case "GreaterThan": // Try to parse comparison value as float64 comparisonValue, comparisonErr := strconv.ParseFloat(condition.Comparison.Value, 64)