feat(cabana): accept preset on mltext fields
- compilePresetKey admits a text or mltext target - checkPresets admits a text or mltext source - TestPresetML pins every text/mltext pairing and the refusals
This commit is contained in:
@@ -638,17 +638,18 @@ func compileWidgetKeys(typ string, values map[string]ast.Node, field *FormField)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compilePresetKey decodes `preset` (D-27): the name of the field this text
|
// compilePresetKey decodes `preset` (D-27): the name of the field this text
|
||||||
// field follows while the administrator has not edited it, on create only. It
|
// or mltext field follows while the administrator has not edited it, on
|
||||||
// is a string (the source field; type slug) or a mapping with the keys field
|
// create only. It is a string (the source field; type slug) or a mapping with
|
||||||
// and type, where type is slug or exact. The source is checked against the
|
// the keys field and type, where type is slug or exact. An mltext pair
|
||||||
// form in checkPresets.
|
// follows per locale in the admin SPA. The source is checked against the form
|
||||||
|
// in checkPresets.
|
||||||
func compilePresetKey(typ string, values map[string]ast.Node, field *FormField) error {
|
func compilePresetKey(typ string, values map[string]ast.Node, field *FormField) error {
|
||||||
node, ok := values["preset"]
|
node, ok := values["preset"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if typ != "text" {
|
if typ != "text" && typ != "mltext" {
|
||||||
return fmt.Errorf("preset is only valid on type: text")
|
return fmt.Errorf("preset is only valid on type: text or mltext")
|
||||||
}
|
}
|
||||||
preset := FieldPreset{Type: "slug"}
|
preset := FieldPreset{Type: "slug"}
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
@@ -687,7 +688,7 @@ func compilePresetKey(typ string, values map[string]ast.Node, field *FormField)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkPresets checks every preset of a form against its fields: the source
|
// checkPresets checks every preset of a form against its fields: the source
|
||||||
// must be another text field of the same form.
|
// must be another text or mltext field of the same form.
|
||||||
func checkPresets(fields []FormField) error {
|
func checkPresets(fields []FormField) error {
|
||||||
types := make(map[string]string, len(fields))
|
types := make(map[string]string, len(fields))
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
@@ -705,8 +706,8 @@ func checkPresets(fields []FormField) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("field %s: preset field %s is not a field of this form", field.Name, source)
|
return fmt.Errorf("field %s: preset field %s is not a field of this form", field.Name, source)
|
||||||
}
|
}
|
||||||
if typ != "text" {
|
if typ != "text" && typ != "mltext" {
|
||||||
return fmt.Errorf("field %s: preset field %s must be a text field", field.Name, source)
|
return fmt.Errorf("field %s: preset field %s must be a text or mltext field", field.Name, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -372,9 +372,9 @@ func TestPresetSchema(t *testing.T) {
|
|||||||
{"mapping with exact", " preset: name\n", " preset:\n field: name\n type: exact\n", ""},
|
{"mapping with exact", " preset: name\n", " preset:\n field: name\n type: exact\n", ""},
|
||||||
{"unsupported type", " preset: name\n", " preset:\n field: name\n type: camel\n", "preset type camel is not supported (want slug or exact)"},
|
{"unsupported type", " preset: name\n", " preset:\n field: name\n type: camel\n", "preset type camel is not supported (want slug or exact)"},
|
||||||
{"unknown key", " preset: name\n", " preset:\n field: name\n prefix: x\n", "preset: unknown field prefix"},
|
{"unknown key", " preset: name\n", " preset:\n field: name\n prefix: x\n", "preset: unknown field prefix"},
|
||||||
{"not a text target", " type: checkbox\n default: true\n", " type: checkbox\n default: true\n preset: name\n", "preset is only valid on type: text"},
|
{"not a text target", " type: checkbox\n default: true\n", " type: checkbox\n default: true\n preset: name\n", "preset is only valid on type: text or mltext"},
|
||||||
{"unknown source", " preset: name\n", " preset: title\n", "field slug: preset field title is not a field of this form"},
|
{"unknown source", " preset: name\n", " preset: title\n", "field slug: preset field title is not a field of this form"},
|
||||||
{"source is not text", " preset: name\n", " preset: notify\n", "field slug: preset field notify must be a text field"},
|
{"source is not text", " preset: name\n", " preset: notify\n", "field slug: preset field notify must be a text or mltext field"},
|
||||||
{"itself", " preset: name\n", " preset: slug\n", "field slug: preset names the field itself"},
|
{"itself", " preset: name\n", " preset: slug\n", "field slug: preset names the field itself"},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -154,11 +154,11 @@ func TestPhase121BootErrors(t *testing.T) {
|
|||||||
{name: "an unknown preset key", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset:\n field: name\n prefix: x\n")},
|
{name: "an unknown preset key", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset:\n field: name\n prefix: x\n")},
|
||||||
want: "preset: unknown field prefix", file: rosterFieldsFile},
|
want: "preset: unknown field prefix", file: rosterFieldsFile},
|
||||||
{name: "preset on a field that is not text", files: map[string]string{rosterFieldsFile: rosterFields(t, " type: checkbox\n default: true\n", " type: checkbox\n default: true\n preset: name\n")},
|
{name: "preset on a field that is not text", files: map[string]string{rosterFieldsFile: rosterFields(t, " type: checkbox\n default: true\n", " type: checkbox\n default: true\n preset: name\n")},
|
||||||
want: "preset is only valid on type: text", file: rosterFieldsFile},
|
want: "preset is only valid on type: text or mltext", file: rosterFieldsFile},
|
||||||
{name: "preset from an unknown field", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset: title\n")},
|
{name: "preset from an unknown field", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset: title\n")},
|
||||||
want: "field slug: preset field title is not a field of this form", file: rosterFieldsFile},
|
want: "field slug: preset field title is not a field of this form", file: rosterFieldsFile},
|
||||||
{name: "preset from a field that is not text", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset: notify\n")},
|
{name: "preset from a field that is not text", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset: notify\n")},
|
||||||
want: "field slug: preset field notify must be a text field", file: rosterFieldsFile},
|
want: "field slug: preset field notify must be a text or mltext field", file: rosterFieldsFile},
|
||||||
{name: "preset from the field itself", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset: slug\n")},
|
{name: "preset from the field itself", files: map[string]string{rosterFieldsFile: rosterFields(t, " preset: name\n", " preset: slug\n")},
|
||||||
want: "field slug: preset names the field itself", file: rosterFieldsFile},
|
want: "field slug: preset names the field itself", file: rosterFieldsFile},
|
||||||
// permissioneditor (D-16).
|
// permissioneditor (D-16).
|
||||||
|
|||||||
179
modules/cabana/preset_ml_test.go
Normal file
179
modules/cabana/preset_ml_test.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package cabana
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestPresetML pins preset on multilingual fields: a text or mltext target
|
||||||
|
// may follow a text or mltext source; any other target or source type is
|
||||||
|
// refused, by CompileForm for the target and by checkPresets for the source.
|
||||||
|
func TestPresetML(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
yaml string
|
||||||
|
compileErr string
|
||||||
|
checkErr string
|
||||||
|
preset *FieldPreset
|
||||||
|
json []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "mltext title to mltext slug",
|
||||||
|
yaml: `fields:
|
||||||
|
title:
|
||||||
|
type: mltext
|
||||||
|
label: Title
|
||||||
|
slug:
|
||||||
|
type: mltext
|
||||||
|
label: Slug
|
||||||
|
preset:
|
||||||
|
field: title
|
||||||
|
type: slug
|
||||||
|
`,
|
||||||
|
preset: &FieldPreset{Field: "title", Type: "slug"},
|
||||||
|
json: []string{`"type":"mltext"`, `"preset":{"field":"title","type":"slug"}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "short form on an mltext slug",
|
||||||
|
yaml: `fields:
|
||||||
|
title:
|
||||||
|
type: mltext
|
||||||
|
label: Title
|
||||||
|
slug:
|
||||||
|
type: mltext
|
||||||
|
label: Slug
|
||||||
|
preset: title
|
||||||
|
`,
|
||||||
|
preset: &FieldPreset{Field: "title", Type: "slug"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact copy between mltext fields",
|
||||||
|
yaml: `fields:
|
||||||
|
title:
|
||||||
|
type: mltext
|
||||||
|
label: Title
|
||||||
|
slug:
|
||||||
|
type: mltext
|
||||||
|
label: Slug
|
||||||
|
preset:
|
||||||
|
field: title
|
||||||
|
type: exact
|
||||||
|
`,
|
||||||
|
preset: &FieldPreset{Field: "title", Type: "exact"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text source to mltext target",
|
||||||
|
yaml: `fields:
|
||||||
|
title:
|
||||||
|
type: text
|
||||||
|
label: Title
|
||||||
|
slug:
|
||||||
|
type: mltext
|
||||||
|
label: Slug
|
||||||
|
preset: title
|
||||||
|
`,
|
||||||
|
preset: &FieldPreset{Field: "title", Type: "slug"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mltext source to text target",
|
||||||
|
yaml: `fields:
|
||||||
|
title:
|
||||||
|
type: mltext
|
||||||
|
label: Title
|
||||||
|
slug:
|
||||||
|
type: text
|
||||||
|
label: Slug
|
||||||
|
preset: title
|
||||||
|
`,
|
||||||
|
preset: &FieldPreset{Field: "title", Type: "slug"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mlmarkdown target is refused",
|
||||||
|
yaml: `fields:
|
||||||
|
title:
|
||||||
|
type: mltext
|
||||||
|
label: Title
|
||||||
|
slug:
|
||||||
|
type: mlmarkdown
|
||||||
|
label: Slug
|
||||||
|
preset: title
|
||||||
|
`,
|
||||||
|
compileErr: "preset is only valid on type: text or mltext",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "markdown target is refused",
|
||||||
|
yaml: `fields:
|
||||||
|
title:
|
||||||
|
type: text
|
||||||
|
label: Title
|
||||||
|
slug:
|
||||||
|
type: markdown
|
||||||
|
label: Slug
|
||||||
|
preset: title
|
||||||
|
`,
|
||||||
|
compileErr: "preset is only valid on type: text or mltext",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mlmarkdown source is refused",
|
||||||
|
yaml: `fields:
|
||||||
|
body:
|
||||||
|
type: mlmarkdown
|
||||||
|
label: Body
|
||||||
|
slug:
|
||||||
|
type: mltext
|
||||||
|
label: Slug
|
||||||
|
preset: body
|
||||||
|
`,
|
||||||
|
checkErr: "field slug: preset field body must be a text or mltext field",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
schema, err := CompileForm("acme.demo", schemaController{model: "Widget"}, formFS(formConfig, tc.yaml))
|
||||||
|
if tc.compileErr != "" {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tc.compileErr) {
|
||||||
|
t.Fatalf("compile err = %v, want %q", err, tc.compileErr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("compile: %v", err)
|
||||||
|
}
|
||||||
|
err = checkPresets(schema.Fields)
|
||||||
|
if tc.checkErr != "" {
|
||||||
|
if err == nil || err.Error() != tc.checkErr {
|
||||||
|
t.Fatalf("checkPresets err = %v, want %q", err, tc.checkErr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("checkPresets: %v", err)
|
||||||
|
}
|
||||||
|
var slug *FormField
|
||||||
|
for i := range schema.Fields {
|
||||||
|
if schema.Fields[i].Name == "slug" {
|
||||||
|
slug = &schema.Fields[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if slug == nil {
|
||||||
|
t.Fatal("slug field missing")
|
||||||
|
}
|
||||||
|
if slug.Preset == nil || *slug.Preset != *tc.preset {
|
||||||
|
t.Fatalf("preset = %+v, want %+v", slug.Preset, tc.preset)
|
||||||
|
}
|
||||||
|
if len(tc.json) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raw, err := json.Marshal(slug)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range tc.json {
|
||||||
|
if !strings.Contains(string(raw), want) {
|
||||||
|
t.Fatalf("json %s lacks %s", raw, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user