allow for sections and components in any order on pages

This commit is contained in:
2025-09-09 22:30:00 -06:00
parent 88d757546a
commit b82e22c38d
11 changed files with 1179 additions and 848 deletions

View File

@ -23,10 +23,12 @@ func TestParseSectionDefinitions(t *testing.T) {
Name: "Test",
Path: "/test",
Layout: "main",
Sections: []Section{
Elements: []PageElement{
{
Name: "main",
Type: stringPtr("container"),
Section: &Section{
Name: "main",
Type: stringPtr("container"),
},
},
},
},
@ -46,15 +48,57 @@ func TestParseSectionDefinitions(t *testing.T) {
Name: "Test",
Path: "/test",
Layout: "main",
Sections: []Section{
Elements: []PageElement{
{
Name: "sidebar",
Type: stringPtr("panel"),
Position: stringPtr("left"),
Class: stringPtr("sidebar-nav"),
Label: stringPtr("Navigation"),
Trigger: stringPtr("toggle-sidebar"),
Entity: stringPtr("User"),
Section: &Section{
Name: "sidebar",
Type: stringPtr("panel"),
Class: stringPtr("sidebar-nav"),
Label: stringPtr("Navigation"),
Trigger: stringPtr("toggle-sidebar"),
Position: stringPtr("left"),
Entity: stringPtr("User"),
},
},
},
},
},
},
},
},
{
name: "sections with separate attributes",
input: `page Dashboard at "/dashboard" layout main {
section content type container {
data from "/api/data"
style "padding: 20px"
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Dashboard",
Path: "/dashboard",
Layout: "main",
Elements: []PageElement{
{
Section: &Section{
Name: "content",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Attribute: &SectionAttribute{
DataSource: stringPtr("/api/data"),
},
},
{
Attribute: &SectionAttribute{
Style: stringPtr("padding: 20px"),
},
},
},
},
},
},
},
@ -78,28 +122,30 @@ func TestParseSectionDefinitions(t *testing.T) {
Name: "Test",
Path: "/test",
Layout: "main",
Sections: []Section{
Elements: []PageElement{
{
Name: "tabs",
Type: stringPtr("tab"),
Elements: []SectionElement{
{
Section: &Section{
Name: "overview",
Label: stringPtr("Overview"),
Active: true,
Section: &Section{
Name: "tabs",
Type: stringPtr("tab"),
Elements: []SectionElement{
{
Section: &Section{
Name: "overview",
Label: stringPtr("Overview"),
Active: true,
},
},
},
{
Section: &Section{
Name: "details",
Label: stringPtr("Details"),
{
Section: &Section{
Name: "details",
Label: stringPtr("Details"),
},
},
},
{
Section: &Section{
Name: "settings",
Label: stringPtr("Settings"),
{
Section: &Section{
Name: "settings",
Label: stringPtr("Settings"),
},
},
},
},
@ -129,50 +175,52 @@ func TestParseSectionDefinitions(t *testing.T) {
Name: "Test",
Path: "/test",
Layout: "main",
Sections: []Section{
Elements: []PageElement{
{
Name: "userModal",
Type: stringPtr("modal"),
Trigger: stringPtr("edit-user"),
Elements: []SectionElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Required: true},
Section: &Section{
Name: "userModal",
Type: stringPtr("modal"),
Trigger: stringPtr("edit-user"),
Elements: []SectionElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Required: true},
},
},
},
},
{
Field: &ComponentField{
Name: "email",
Type: "email",
Attributes: []ComponentFieldAttribute{
{Required: true},
{
Field: &ComponentField{
Name: "email",
Type: "email",
Attributes: []ComponentFieldAttribute{
{Required: true},
},
},
},
},
{
Button: &ComponentButton{
Name: "save",
Label: "Save Changes",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "primary"}},
{
Button: &ComponentButton{
Name: "save",
Label: "Save Changes",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "primary"}},
},
},
},
},
{
Button: &ComponentButton{
Name: "cancel",
Label: "Cancel",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "secondary"}},
{
Button: &ComponentButton{
Name: "cancel",
Label: "Cancel",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "secondary"}},
},
},
},
},
@ -213,24 +261,26 @@ func TestParseSectionDefinitions(t *testing.T) {
Name: "Test",
Path: "/test",
Layout: "main",
Sections: []Section{
Elements: []PageElement{
{
Name: "masterDetail",
Type: stringPtr("master"),
Elements: []SectionElement{
{
Section: &Section{
Name: "userList",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Component: &Component{
Type: "table",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Attribute: &ComponentAttr{
Fields: []string{"name", "email"},
Section: &Section{
Name: "masterDetail",
Type: stringPtr("master"),
Elements: []SectionElement{
{
Section: &Section{
Name: "userList",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Component: &Component{
Type: "table",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Attribute: &ComponentAttr{
Fields: []string{"name", "email"},
},
},
},
},
@ -238,35 +288,35 @@ func TestParseSectionDefinitions(t *testing.T) {
},
},
},
},
{
Section: &Section{
Name: "userDetail",
Type: stringPtr("detail"),
Trigger: stringPtr("user-selected"),
Entity: stringPtr("User"),
Elements: []SectionElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
{
Section: &Section{
Name: "userDetail",
Type: stringPtr("detail"),
Trigger: stringPtr("user-selected"),
Entity: stringPtr("User"),
Elements: []SectionElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
},
},
},
{
Field: &ComponentField{
Name: "email",
Type: "email",
{
Field: &ComponentField{
Name: "email",
Type: "email",
},
},
},
{
Field: &ComponentField{
Name: "bio",
Type: "textarea",
{
Field: &ComponentField{
Name: "bio",
Type: "textarea",
},
},
},
},
@ -323,27 +373,29 @@ func TestParseSectionDefinitions(t *testing.T) {
Name: "Test",
Path: "/test",
Layout: "main",
Sections: []Section{
Elements: []PageElement{
{
Name: "mainLayout",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Section: &Section{
Name: "header",
Type: stringPtr("container"),
Class: stringPtr("header"),
Elements: []SectionElement{
{
Component: &Component{
Type: "navbar",
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "search",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Placeholder: stringPtr("Search...")},
Section: &Section{
Name: "mainLayout",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Section: &Section{
Name: "header",
Type: stringPtr("container"),
Class: stringPtr("header"),
Elements: []SectionElement{
{
Component: &Component{
Type: "navbar",
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "search",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Placeholder: stringPtr("Search...")},
},
},
},
},
@ -352,26 +404,26 @@ func TestParseSectionDefinitions(t *testing.T) {
},
},
},
},
{
Section: &Section{
Name: "content",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Section: &Section{
Name: "sidebar",
Type: stringPtr("panel"),
Position: stringPtr("left"),
Elements: []SectionElement{
{
Component: &Component{
Type: "menu",
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "navigation",
Type: "list",
{
Section: &Section{
Name: "content",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Section: &Section{
Name: "sidebar",
Type: stringPtr("panel"),
Position: stringPtr("left"),
Elements: []SectionElement{
{
Component: &Component{
Type: "menu",
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "navigation",
Type: "list",
},
},
},
},
@ -379,31 +431,31 @@ func TestParseSectionDefinitions(t *testing.T) {
},
},
},
},
{
Section: &Section{
Name: "main",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Section: &Section{
Name: "tabs",
Type: stringPtr("tab"),
Elements: []SectionElement{
{
Section: &Section{
Name: "overview",
Label: stringPtr("Overview"),
Active: true,
Elements: []SectionElement{
{
Component: &Component{
Type: "dashboard",
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "stats",
Type: "metric",
{
Section: &Section{
Name: "main",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Section: &Section{
Name: "tabs",
Type: stringPtr("tab"),
Elements: []SectionElement{
{
Section: &Section{
Name: "overview",
Label: stringPtr("Overview"),
Active: true,
Elements: []SectionElement{
{
Component: &Component{
Type: "dashboard",
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "stats",
Type: "metric",
},
},
},
},
@ -411,16 +463,16 @@ func TestParseSectionDefinitions(t *testing.T) {
},
},
},
},
{
Section: &Section{
Name: "reports",
Label: stringPtr("Reports"),
Elements: []SectionElement{
{
Component: &Component{
Type: "table",
Entity: stringPtr("Report"),
{
Section: &Section{
Name: "reports",
Label: stringPtr("Reports"),
Elements: []SectionElement{
{
Component: &Component{
Type: "table",
Entity: stringPtr("Report"),
},
},
},
},
@ -464,37 +516,39 @@ func TestParseSectionDefinitions(t *testing.T) {
Name: "Test",
Path: "/test",
Layout: "main",
Sections: []Section{
Elements: []PageElement{
{
Name: "adminPanel",
Type: stringPtr("container"),
Elements: []SectionElement{
{
When: &WhenCondition{
Field: "user_role",
Operator: "equals",
Value: "admin",
Sections: []Section{
{
Name: "userManagement",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Component: &Component{
Type: "table",
Entity: stringPtr("User"),
Section: &Section{
Name: "adminPanel",
Type: stringPtr("container"),
Elements: []SectionElement{
{
When: &WhenCondition{
Field: "user_role",
Operator: "equals",
Value: "admin",
Sections: []Section{
{
Name: "userManagement",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Component: &Component{
Type: "table",
Entity: stringPtr("User"),
},
},
},
},
},
{
Name: "systemSettings",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("Settings"),
{
Name: "systemSettings",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("Settings"),
},
},
},
},
@ -520,7 +574,7 @@ func TestParseSectionDefinitions(t *testing.T) {
return
}
if !astEqual(got, tt.want) {
t.Errorf("ParseInput() got = %v, want %v", got, tt.want)
t.Errorf("ParseInput() got = %+v, want %+v", got, tt.want)
}
})
}
@ -549,12 +603,12 @@ func TestParseSectionTypes(t *testing.T) {
}
page := got.Definitions[0].Page
if len(page.Sections) != 1 {
if len(page.Elements) != 1 || page.Elements[0].Section == nil {
t.Errorf("ParseInput() failed to parse section for type %s", sectionType)
return
}
section := page.Sections[0]
section := page.Elements[0].Section
if section.Type == nil || *section.Type != sectionType {
t.Errorf("ParseInput() section type mismatch: got %v, want %s", section.Type, sectionType)
}