fix(09): WR-07 keep relation column order from the YAML itself, not a fixed indentation
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||||
@@ -212,7 +211,7 @@ func compileRelations(pluginID string, ctl pact.AdminController, fsys fs.FS, for
|
|||||||
return nil, bootErr(pluginID, ctl.ID(), file, err)
|
return nil, bootErr(pluginID, ctl.ID(), file, err)
|
||||||
}
|
}
|
||||||
var root yaml.MapSlice
|
var root yaml.MapSlice
|
||||||
if err := decodeStrict(raw, &root); err != nil {
|
if err := decodeStrictOrdered(raw, &root); err != nil {
|
||||||
return nil, bootErr(pluginID, ctl.ID(), file, err)
|
return nil, bootErr(pluginID, ctl.ID(), file, err)
|
||||||
}
|
}
|
||||||
out := map[string]*CompiledRelation{}
|
out := map[string]*CompiledRelation{}
|
||||||
@@ -247,8 +246,6 @@ func compileRelations(pluginID string, ctl pact.AdminController, fsys fs.FS, for
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, bootErr(pluginID, ctl.ID(), file, fmt.Errorf("relation %s manage: %w", name, err))
|
return nil, bootErr(pluginID, ctl.ID(), file, fmt.Errorf("relation %s manage: %w", name, err))
|
||||||
}
|
}
|
||||||
orderRelationColumns(raw, &view)
|
|
||||||
orderRelationColumns(raw, &manage)
|
|
||||||
schema := &RelationSchema{Name: name, Label: doc.Label, View: view, Manage: manage}
|
schema := &RelationSchema{Name: name, Label: doc.Label, View: view, Manage: manage}
|
||||||
if doc.Messages != nil {
|
if doc.Messages != nil {
|
||||||
schema.messageKeys = *doc.Messages
|
schema.messageKeys = *doc.Messages
|
||||||
@@ -274,24 +271,6 @@ func compileRelations(pluginID string, ctl pact.AdminController, fsys fs.FS, for
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func orderRelationColumns(raw []byte, panel *RelationPanel) {
|
|
||||||
if panel == nil || len(panel.List.Columns) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
source := string(raw)
|
|
||||||
sort.SliceStable(panel.List.Columns, func(i, j int) bool {
|
|
||||||
left := strings.Index(source, "\n "+panel.List.Columns[i].Key+":")
|
|
||||||
right := strings.Index(source, "\n "+panel.List.Columns[j].Key+":")
|
|
||||||
if left < 0 {
|
|
||||||
left = len(source)
|
|
||||||
}
|
|
||||||
if right < 0 {
|
|
||||||
right = len(source)
|
|
||||||
}
|
|
||||||
return left < right
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func compileRelationPanel(doc relationPanelDocument, contract RelationContract, view bool) (RelationPanel, error) {
|
func compileRelationPanel(doc relationPanelDocument, contract RelationContract, view bool) (RelationPanel, error) {
|
||||||
cols := make([]RelationColumn, 0, len(doc.List.Columns))
|
cols := make([]RelationColumn, 0, len(doc.List.Columns))
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
|
|||||||
@@ -115,6 +115,77 @@ func TestRelationDefaultSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRelationColumnOrderIsIndependentOfIndentation pins WR-07: column order
|
||||||
|
// is the file's order in each panel, whatever the indentation, and the view and
|
||||||
|
// manage panels do not share one order.
|
||||||
|
func TestRelationColumnOrderIsIndependentOfIndentation(t *testing.T) {
|
||||||
|
const twoSpace = `editors:
|
||||||
|
label: demo.editors
|
||||||
|
view:
|
||||||
|
list:
|
||||||
|
columns:
|
||||||
|
username:
|
||||||
|
label: demo.username
|
||||||
|
email:
|
||||||
|
label: demo.email
|
||||||
|
toolbarButtons: link|unlink
|
||||||
|
manage:
|
||||||
|
list:
|
||||||
|
columns:
|
||||||
|
username:
|
||||||
|
label: demo.username
|
||||||
|
email:
|
||||||
|
label: demo.email
|
||||||
|
`
|
||||||
|
for name, yamlText := range map[string]string{"two-space": twoSpace, "four-space": validRelationYAML} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
ctl := relationTestController{}
|
||||||
|
fsys := relationTestFS(yamlText)
|
||||||
|
form, err := CompileForm("acme.demo", ctl, fsys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
relations, err := compileRelations("acme.demo", ctl, fsys, form)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
schema := relations["editors"].Schema
|
||||||
|
for panel, cols := range map[string][]RelationColumn{"view": schema.View.List.Columns, "manage": schema.Manage.List.Columns} {
|
||||||
|
if len(cols) != 2 || cols[0].Key != "username" || cols[1].Key != "email" {
|
||||||
|
t.Fatalf("%s columns = %+v, want username then email (file order, not alphabetical)", panel, cols)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const reversed = `editors:
|
||||||
|
view:
|
||||||
|
list:
|
||||||
|
columns:
|
||||||
|
username: {label: demo.username}
|
||||||
|
email: {label: demo.email}
|
||||||
|
manage:
|
||||||
|
list:
|
||||||
|
columns:
|
||||||
|
email: {label: demo.email}
|
||||||
|
username: {label: demo.username}
|
||||||
|
`
|
||||||
|
ctl := relationTestController{}
|
||||||
|
fsys := relationTestFS(reversed)
|
||||||
|
form, err := CompileForm("acme.demo", ctl, fsys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
relations, err := compileRelations("acme.demo", ctl, fsys, form)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
schema := relations["editors"].Schema
|
||||||
|
if v, m := schema.View.List.Columns, schema.Manage.List.Columns; v[0].Key != "username" || m[0].Key != "email" {
|
||||||
|
t.Fatalf("view=%+v manage=%+v, want each panel in its own file order", v, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRelationCandidateExclusions(t *testing.T) {
|
func TestRelationCandidateExclusions(t *testing.T) {
|
||||||
contract := relationTestController{}.AdminRelationContracts()[0]
|
contract := relationTestController{}.AdminRelationContracts()[0]
|
||||||
ids, err := contract.ExcludedRelatedIDs(&relationTestOwner{OwnerID: 41})
|
ids, err := contract.ExcludedRelatedIDs(&relationTestOwner{OwnerID: 41})
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ func decodeStrict(raw []byte, dest any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeStrictOrdered is decodeStrict with every nested mapping kept as an
|
||||||
|
// ordered yaml.MapSlice, so a file that is decoded, re-marshalled per entry and
|
||||||
|
// decoded again keeps its key order at any depth and any indentation.
|
||||||
|
func decodeStrictOrdered(raw []byte, dest any) error {
|
||||||
|
dec := yaml.NewDecoder(bytes.NewReader(raw), yaml.DisallowUnknownField(), yaml.UseOrderedMap())
|
||||||
|
return dec.Decode(dest)
|
||||||
|
}
|
||||||
|
|
||||||
func readAsset(fsys fs.FS, name string) ([]byte, error) {
|
func readAsset(fsys fs.FS, name string) ([]byte, error) {
|
||||||
name = path.Clean(name)
|
name = path.Clean(name)
|
||||||
if name == "." || strings.HasPrefix(name, "..") || strings.Contains(name, "..") {
|
if name == "." || strings.HasPrefix(name, "..") || strings.Contains(name, "..") {
|
||||||
|
|||||||
Reference in New Issue
Block a user