add bracket syntax replace tests
This commit is contained in:
599
lang/parser_ui_section_test.go
Normal file
599
lang/parser_ui_section_test.go
Normal file
@ -0,0 +1,599 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSectionDefinitions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want AST
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic container section",
|
||||
input: `page Test at "/test" layout main {
|
||||
section main type container
|
||||
}`,
|
||||
want: AST{
|
||||
Definitions: []Definition{
|
||||
{
|
||||
Page: &Page{
|
||||
Name: "Test",
|
||||
Path: "/test",
|
||||
Layout: "main",
|
||||
Sections: []Section{
|
||||
{
|
||||
Name: "main",
|
||||
Type: stringPtr("container"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "section with all attributes",
|
||||
input: `page Test at "/test" layout main {
|
||||
section sidebar type panel class "sidebar-nav" label "Navigation" trigger "toggle-sidebar" position "left" for User
|
||||
}`,
|
||||
want: AST{
|
||||
Definitions: []Definition{
|
||||
{
|
||||
Page: &Page{
|
||||
Name: "Test",
|
||||
Path: "/test",
|
||||
Layout: "main",
|
||||
Sections: []Section{
|
||||
{
|
||||
Name: "sidebar",
|
||||
Type: stringPtr("panel"),
|
||||
Position: stringPtr("left"),
|
||||
Class: stringPtr("sidebar-nav"),
|
||||
Label: stringPtr("Navigation"),
|
||||
Trigger: stringPtr("toggle-sidebar"),
|
||||
Entity: stringPtr("User"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tab sections with active state",
|
||||
input: `page Test at "/test" layout main {
|
||||
section tabs type tab {
|
||||
section overview label "Overview" active
|
||||
section details label "Details"
|
||||
section settings label "Settings"
|
||||
}
|
||||
}`,
|
||||
want: AST{
|
||||
Definitions: []Definition{
|
||||
{
|
||||
Page: &Page{
|
||||
Name: "Test",
|
||||
Path: "/test",
|
||||
Layout: "main",
|
||||
Sections: []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: "settings",
|
||||
Label: stringPtr("Settings"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "modal section with content",
|
||||
input: `page Test at "/test" layout main {
|
||||
section userModal type modal trigger "edit-user" {
|
||||
component form for User {
|
||||
field name type text required
|
||||
field email type email required
|
||||
button save label "Save Changes" style "primary"
|
||||
button cancel label "Cancel" style "secondary"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
want: AST{
|
||||
Definitions: []Definition{
|
||||
{
|
||||
Page: &Page{
|
||||
Name: "Test",
|
||||
Path: "/test",
|
||||
Layout: "main",
|
||||
Sections: []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},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "master-detail sections",
|
||||
input: `page Test at "/test" layout main {
|
||||
section masterDetail type master {
|
||||
section userList type container {
|
||||
component table for User {
|
||||
fields [name, email]
|
||||
}
|
||||
}
|
||||
|
||||
section userDetail type detail trigger "user-selected" for User {
|
||||
component form for User {
|
||||
field name type text
|
||||
field email type email
|
||||
field bio type textarea
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
want: AST{
|
||||
Definitions: []Definition{
|
||||
{
|
||||
Page: &Page{
|
||||
Name: "Test",
|
||||
Path: "/test",
|
||||
Layout: "main",
|
||||
Sections: []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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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: "bio",
|
||||
Type: "textarea",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deeply nested sections",
|
||||
input: `page Test at "/test" layout main {
|
||||
section mainLayout type container {
|
||||
section header type container class "header" {
|
||||
component navbar {
|
||||
field search type text placeholder "Search..."
|
||||
}
|
||||
}
|
||||
|
||||
section content type container {
|
||||
section sidebar type panel position "left" {
|
||||
component menu {
|
||||
field navigation type list
|
||||
}
|
||||
}
|
||||
|
||||
section main type container {
|
||||
section tabs type tab {
|
||||
section overview label "Overview" active {
|
||||
component dashboard {
|
||||
field stats type metric
|
||||
}
|
||||
}
|
||||
|
||||
section reports label "Reports" {
|
||||
component table for Report
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
want: AST{
|
||||
Definitions: []Definition{
|
||||
{
|
||||
Page: &Page{
|
||||
Name: "Test",
|
||||
Path: "/test",
|
||||
Layout: "main",
|
||||
Sections: []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...")},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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: "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: "reports",
|
||||
Label: stringPtr("Reports"),
|
||||
Elements: []SectionElement{
|
||||
{
|
||||
Component: &Component{
|
||||
Type: "table",
|
||||
Entity: stringPtr("Report"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "section with conditional content",
|
||||
input: `page Test at "/test" layout main {
|
||||
section adminPanel type container {
|
||||
when user_role equals "admin" {
|
||||
section userManagement type container {
|
||||
component table for User
|
||||
}
|
||||
section systemSettings type container {
|
||||
component form for Settings
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
want: AST{
|
||||
Definitions: []Definition{
|
||||
{
|
||||
Page: &Page{
|
||||
Name: "Test",
|
||||
Path: "/test",
|
||||
Layout: "main",
|
||||
Sections: []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"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseInput(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseInput() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !astEqual(got, tt.want) {
|
||||
t.Errorf("ParseInput() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSectionTypes(t *testing.T) {
|
||||
sectionTypes := []string{
|
||||
"container", "tab", "panel", "modal", "master", "detail",
|
||||
}
|
||||
|
||||
for _, sectionType := range sectionTypes {
|
||||
t.Run("section_type_"+sectionType, func(t *testing.T) {
|
||||
input := `page Test at "/test" layout main {
|
||||
section test_section type ` + sectionType + `
|
||||
}`
|
||||
|
||||
got, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Errorf("ParseInput() failed for section type %s: %v", sectionType, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(got.Definitions) != 1 || got.Definitions[0].Page == nil {
|
||||
t.Errorf("ParseInput() failed to parse page for section type %s", sectionType)
|
||||
return
|
||||
}
|
||||
|
||||
page := got.Definitions[0].Page
|
||||
if len(page.Sections) != 1 {
|
||||
t.Errorf("ParseInput() failed to parse section for type %s", sectionType)
|
||||
return
|
||||
}
|
||||
|
||||
section := page.Sections[0]
|
||||
if section.Type == nil || *section.Type != sectionType {
|
||||
t.Errorf("ParseInput() section type mismatch: got %v, want %s", section.Type, sectionType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSectionErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "missing section name",
|
||||
input: `page Test at "/test" layout main {
|
||||
section type container
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "invalid section type",
|
||||
input: `page Test at "/test" layout main {
|
||||
section test type invalid_type
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "unclosed section block",
|
||||
input: `page Test at "/test" layout main {
|
||||
section test type container {
|
||||
component form
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParseInput(tt.input)
|
||||
if err == nil {
|
||||
t.Errorf("ParseInput() expected error for invalid syntax, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user