package cabana import ( "context" "encoding/json" "errors" "strings" "testing" ) // TestSettingsTabs checks UI-SPEC F4 without a database: a settings form // accepts `tab`, the compiled and localized schema carries each field's tab // (untabbed fields form the default tab and omit the key), and an untabbed // form's schema carries no tab key at all, byte for byte as before the // feature. A repeater and a dependsOn dropdown stay refused on settings. func TestSettingsTabs(t *testing.T) { item, fsys := secretSettingsItem(`fields: token: label: Token type: text api_key: label: API key type: password tab: Keys notes: label: Notes type: richeditor tab: Texts `) setting, err := compileSetting("acme.unit", item, fsys) if err != nil { t.Fatal(err) } view, err := setting.Form.Localize(context.Background(), nil, nil) if err != nil { t.Fatal(err) } tabs := map[string]string{} var order []string for _, field := range view.Fields { tabs[field.Name] = field.Tab order = append(order, field.Name) } if strings.Join(order, ",") != "token,api_key,notes" { t.Fatalf("field order = %v", order) } if tabs["token"] != "" || tabs["api_key"] != "Keys" || tabs["notes"] != "Texts" { t.Fatalf("tabs = %v", tabs) } raw, err := json.Marshal(view) if err != nil { t.Fatal(err) } if strings.Count(string(raw), `"tab":`) != 2 { t.Fatalf("tabbed schema JSON = %s", raw) } item, fsys = secretSettingsItem("fields:\n token:\n type: text\n notes:\n type: richeditor\n") plain, err := compileSetting("acme.unit", item, fsys) if err != nil { t.Fatal(err) } plainView, err := plain.Form.Localize(context.Background(), nil, nil) if err != nil { t.Fatal(err) } raw, err = json.Marshal(plainView) if err != nil { t.Fatal(err) } if strings.Contains(string(raw), `"tab"`) { t.Fatalf("untabbed schema carries a tab key: %s", raw) } for name, fields := range map[string]string{ "repeater": "fields:\n notes:\n type: repeater\n form:\n fields:\n a:\n type: text\n", "dependsOn": "fields:\n token:\n type: dropdown\n dependsOn: notes\n options: {a: A}\n notes:\n type: text\n", } { t.Run(name+" refused", func(t *testing.T) { item, fsys := secretSettingsItem(fields) if _, err := compileSetting("acme.unit", item, fsys); err == nil { t.Fatalf("%s compiled on a settings form", name) } }) } } // TestSettingsRichEditor checks UI-SPEC F3 without a database: a settings // richeditor value is sanitized through seaglass before Fill sees it and // what was removed comes back as notices on the field (one per kind); clean // HTML passes byte for byte with no notice; null is accepted and a value // that is neither a string nor null is a 422 on the field before anything // is written. func TestSettingsRichEditor(t *testing.T) { setting := secretTwoFields(t) dirty := `
Bank
` projected := projectSettingBody(setting, map[string]any{"notes": dirty, "token": "t"}) notices := sanitizeSettingRichEditors(setting, projected) if projected["notes"] != "Bank
" { t.Fatalf("sanitized notes = %#v", projected["notes"]) } if projected["token"] != "t" { t.Fatalf("a text field was touched: %#v", projected["token"]) } if len(notices) != 2 { t.Fatalf("notices = %+v, want one for the element and one for the attribute", notices) } messages := map[string]bool{} for _, n := range notices { if n.field != "notes" { t.Fatalf("notice on %q", n.field) } messages[n.notice.Message] = true } if !messages[msgRichRemovedElements] || !messages[msgRichRemovedAttributes] { t.Fatalf("notice keys = %v", messages) } meta := settingsMeta(context.Background(), nil, nil, notices) if meta == nil || len(meta.Notices) != 2 || meta.Notices[0].Field != "notes" || meta.Secrets != nil { t.Fatalf("meta = %+v", meta) } clean := "IBAN PL00 & \"swift\"
\nx
"} { if err := checkSettingRichEditors(setting, map[string]any{"notes": ok}); err != nil { t.Fatalf("%#v refused: %v", ok, err) } } for _, bad := range []any{[]any{"x
"}, 5, true, map[string]any{"html": "x"}} { err := checkSettingRichEditors(setting, map[string]any{"notes": bad}) var verr *ValidationError if !errors.As(err, &verr) { t.Fatalf("%#v: err = %v", bad, err) } if _, ok := verr.Details["notes"]; !ok { t.Fatalf("details = %#v", verr.Details) } } }