package forms import ( "encoding/json" "testing" ) // ── Parse Tests ────────────────────────────── func TestParseTypedFormTemplate_Empty(t *testing.T) { for _, input := range []string{"", "{}", "null"} { if tpl := ParseTypedFormTemplate(json.RawMessage(input)); tpl != nil { t.Errorf("expected nil for %q, got %+v", input, tpl) } } } func TestParseTypedFormTemplate_InvalidJSON(t *testing.T) { if tpl := ParseTypedFormTemplate(json.RawMessage(`{bad`)); tpl != nil { t.Fatal("expected nil for invalid JSON") } } func TestParseTypedFormTemplate_FlatFields(t *testing.T) { raw := json.RawMessage(`{ "fields": [ {"key": "name", "type": "text", "label": "Name"}, {"key": "email", "type": "email", "label": "Email"} ] }`) tpl := ParseTypedFormTemplate(raw) if tpl == nil { t.Fatal("expected non-nil template") } if len(tpl.Fields) != 2 { t.Fatalf("expected 2 fields, got %d", len(tpl.Fields)) } } func TestParseTypedFormTemplate_Fieldsets(t *testing.T) { raw := json.RawMessage(`{ "fieldsets": [ {"label": "Step 1", "fields": [{"key": "a", "type": "text", "label": "A"}]}, {"label": "Step 2", "fields": [{"key": "b", "type": "number", "label": "B"}]} ] }`) tpl := ParseTypedFormTemplate(raw) if tpl == nil { t.Fatal("expected non-nil template") } if len(tpl.Fields) != 2 { t.Fatalf("expected 2 flattened fields, got %d", len(tpl.Fields)) } if len(tpl.Fieldsets) != 2 { t.Fatalf("expected 2 fieldsets, got %d", len(tpl.Fieldsets)) } } func TestParseTypedFormTemplate_LegacyFormat(t *testing.T) { // Legacy format: fields present but missing key/type → returns nil raw := json.RawMessage(`{"fields": [{"label": "Name"}]}`) if tpl := ParseTypedFormTemplate(raw); tpl != nil { t.Fatal("expected nil for legacy format") } } // ── Validation Tests ───────────────────────── func tpl(fields ...FormField) *TypedFormTemplate { return &TypedFormTemplate{Fields: fields} } func intPtr(n int) *int { return &n } func floatPtr(n float64) *float64 { return &n } func TestValidateFormData_Required(t *testing.T) { template := tpl(FormField{Key: "name", Type: "text", Label: "Name", Required: true}) // Missing errs := ValidateFormData(template, map[string]interface{}{}) if len(errs) != 1 || errs[0].Key != "name" { t.Fatalf("expected 1 error for missing required field, got %+v", errs) } // Present errs = ValidateFormData(template, map[string]interface{}{"name": "Alice"}) if len(errs) != 0 { t.Fatalf("expected 0 errors, got %+v", errs) } // Empty string counts as missing errs = ValidateFormData(template, map[string]interface{}{"name": ""}) if len(errs) != 1 { t.Fatalf("expected 1 error for empty required field, got %+v", errs) } } func TestValidateFormData_Text(t *testing.T) { template := tpl(FormField{ Key: "bio", Type: "text", Label: "Bio", Validation: &FormValidation{MinLength: intPtr(3), MaxLength: intPtr(10), Pattern: `^[a-z]+$`}, }) // Too short errs := ValidateFormData(template, map[string]interface{}{"bio": "ab"}) if len(errs) != 1 { t.Fatalf("expected min_length error, got %+v", errs) } // Too long errs = ValidateFormData(template, map[string]interface{}{"bio": "abcdefghijk"}) if len(errs) != 1 { t.Fatalf("expected max_length error, got %+v", errs) } // Pattern mismatch errs = ValidateFormData(template, map[string]interface{}{"bio": "ABC"}) if len(errs) != 1 { t.Fatalf("expected pattern error, got %+v", errs) } // Valid errs = ValidateFormData(template, map[string]interface{}{"bio": "hello"}) if len(errs) != 0 { t.Fatalf("expected 0 errors, got %+v", errs) } } func TestValidateFormData_Email(t *testing.T) { template := tpl(FormField{Key: "email", Type: "email", Label: "Email"}) errs := ValidateFormData(template, map[string]interface{}{"email": "bad"}) if len(errs) != 1 { t.Fatalf("expected email error, got %+v", errs) } errs = ValidateFormData(template, map[string]interface{}{"email": "a@b.c"}) if len(errs) != 0 { t.Fatalf("expected 0 errors, got %+v", errs) } } func TestValidateFormData_Number(t *testing.T) { template := tpl(FormField{ Key: "age", Type: "number", Label: "Age", Validation: &FormValidation{Min: floatPtr(0), Max: floatPtr(150)}, }) // Below min errs := ValidateFormData(template, map[string]interface{}{"age": float64(-1)}) if len(errs) != 1 { t.Fatalf("expected min error, got %+v", errs) } // Above max errs = ValidateFormData(template, map[string]interface{}{"age": float64(200)}) if len(errs) != 1 { t.Fatalf("expected max error, got %+v", errs) } // Non-number errs = ValidateFormData(template, map[string]interface{}{"age": "thirty"}) if len(errs) != 1 { t.Fatalf("expected type error, got %+v", errs) } // Valid errs = ValidateFormData(template, map[string]interface{}{"age": float64(25)}) if len(errs) != 0 { t.Fatalf("expected 0 errors, got %+v", errs) } } func TestValidateFormData_Date(t *testing.T) { template := tpl(FormField{ Key: "start", Type: "date", Label: "Start", Validation: &FormValidation{MinDate: "2024-01-01", MaxDate: "2024-12-31"}, }) // Invalid format errs := ValidateFormData(template, map[string]interface{}{"start": "Jan 1"}) if len(errs) != 1 { t.Fatalf("expected format error, got %+v", errs) } // Before min errs = ValidateFormData(template, map[string]interface{}{"start": "2023-12-31"}) if len(errs) != 1 { t.Fatalf("expected min_date error, got %+v", errs) } // After max errs = ValidateFormData(template, map[string]interface{}{"start": "2025-01-01"}) if len(errs) != 1 { t.Fatalf("expected max_date error, got %+v", errs) } // Valid errs = ValidateFormData(template, map[string]interface{}{"start": "2024-06-15"}) if len(errs) != 0 { t.Fatalf("expected 0 errors, got %+v", errs) } } func TestValidateFormData_Select(t *testing.T) { template := tpl(FormField{ Key: "color", Type: "select", Label: "Color", Options: []FormOption{{Value: "red", Label: "Red"}, {Value: "blue", Label: "Blue"}}, }) errs := ValidateFormData(template, map[string]interface{}{"color": "green"}) if len(errs) != 1 { t.Fatalf("expected invalid option error, got %+v", errs) } // Case-insensitive match errs = ValidateFormData(template, map[string]interface{}{"color": "Red"}) if len(errs) != 0 { t.Fatalf("expected 0 errors (case-insensitive), got %+v", errs) } } func TestValidateFormData_Checkbox(t *testing.T) { template := tpl(FormField{Key: "agree", Type: "checkbox", Label: "Agree"}) errs := ValidateFormData(template, map[string]interface{}{"agree": "yes"}) if len(errs) != 1 { t.Fatalf("expected type error, got %+v", errs) } errs = ValidateFormData(template, map[string]interface{}{"agree": true}) if len(errs) != 0 { t.Fatalf("expected 0 errors, got %+v", errs) } } func TestValidateFormData_Condition(t *testing.T) { template := tpl( FormField{Key: "type", Type: "select", Label: "Type", Options: []FormOption{{Value: "normal", Label: "Normal"}, {Value: "other", Label: "Other"}}}, FormField{ Key: "details", Type: "text", Label: "Details", Required: true, Condition: &FieldCondition{When: "type", Op: "eq", Value: "other"}, }, ) // Condition not met → required field skipped errs := ValidateFormData(template, map[string]interface{}{"type": "normal"}) if len(errs) != 0 { t.Fatalf("expected 0 errors (condition not met), got %+v", errs) } // Condition met → required enforced errs = ValidateFormData(template, map[string]interface{}{"type": "other"}) if len(errs) != 1 || errs[0].Key != "details" { t.Fatalf("expected 1 error for conditional required field, got %+v", errs) } // Condition met + value provided errs = ValidateFormData(template, map[string]interface{}{"type": "other", "details": "More info"}) if len(errs) != 0 { t.Fatalf("expected 0 errors, got %+v", errs) } } func TestEvaluateFieldCondition_Operators(t *testing.T) { data := map[string]interface{}{"status": "active", "role": "admin"} tests := []struct { name string cond FieldCondition expect bool }{ {"eq match", FieldCondition{When: "status", Op: "eq", Value: "active"}, true}, {"eq mismatch", FieldCondition{When: "status", Op: "eq", Value: "inactive"}, false}, {"neq match", FieldCondition{When: "status", Op: "neq", Value: "inactive"}, true}, {"neq mismatch", FieldCondition{When: "status", Op: "neq", Value: "active"}, false}, {"exists present", FieldCondition{When: "status", Op: "exists"}, true}, {"exists absent", FieldCondition{When: "missing", Op: "exists"}, false}, {"in match", FieldCondition{When: "role", Op: "in", Value: []interface{}{"admin", "mod"}}, true}, {"in mismatch", FieldCondition{When: "role", Op: "in", Value: []interface{}{"user", "guest"}}, false}, {"default op is eq", FieldCondition{When: "status", Value: "active"}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := EvaluateFieldCondition(&tt.cond, data) if got != tt.expect { t.Errorf("expected %v, got %v", tt.expect, got) } }) } } func TestAllFields(t *testing.T) { raw := json.RawMessage(`{ "fieldsets": [ {"label": "A", "fields": [{"key": "x", "type": "text", "label": "X"}]}, {"label": "B", "fields": [{"key": "y", "type": "text", "label": "Y"}, {"key": "z", "type": "text", "label": "Z"}]} ] }`) tpl := ParseTypedFormTemplate(raw) if tpl == nil { t.Fatal("expected non-nil template") } all := tpl.AllFields() if len(all) != 3 { t.Fatalf("expected 3 fields, got %d", len(all)) } } func TestValidateFormData_OptionalSkipped(t *testing.T) { template := tpl(FormField{ Key: "note", Type: "text", Label: "Note", Validation: &FormValidation{MinLength: intPtr(5)}, }) // Not present and not required → no error errs := ValidateFormData(template, map[string]interface{}{}) if len(errs) != 0 { t.Fatalf("expected 0 errors for absent optional field, got %+v", errs) } }