420 lines
9.5 KiB
Go
420 lines
9.5 KiB
Go
package lang
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestParsePageDefinitions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want AST
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "basic page definition",
|
|
input: `page Home at "/" layout main`,
|
|
want: AST{
|
|
Definitions: []Definition{
|
|
{
|
|
Page: &Page{
|
|
Name: "Home",
|
|
Path: "/",
|
|
Layout: "main",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "page with optional fields",
|
|
input: `page Settings at "/settings" layout main title "User Settings" desc "Manage your account settings" auth`,
|
|
want: AST{
|
|
Definitions: []Definition{
|
|
{
|
|
Page: &Page{
|
|
Name: "Settings",
|
|
Path: "/settings",
|
|
Layout: "main",
|
|
Title: stringPtr("User Settings"),
|
|
Description: stringPtr("Manage your account settings"),
|
|
Auth: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "page with meta tags",
|
|
input: `page Settings at "/settings" layout main {
|
|
meta description "Settings page description"
|
|
meta keywords "settings, user, account"
|
|
meta author "My App"
|
|
}`,
|
|
want: AST{
|
|
Definitions: []Definition{
|
|
{
|
|
Page: &Page{
|
|
Name: "Settings",
|
|
Path: "/settings",
|
|
Layout: "main",
|
|
Meta: []MetaTag{
|
|
{Name: "description", Content: "Settings page description"},
|
|
{Name: "keywords", Content: "settings, user, account"},
|
|
{Name: "author", Content: "My App"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "page with sections",
|
|
input: `page Settings at "/settings" layout main {
|
|
section tabs type tab {
|
|
section profile label "Profile" active {
|
|
component form for User
|
|
}
|
|
section security label "Security" {
|
|
component form for Security
|
|
}
|
|
section notifications label "Notifications" {
|
|
component toggle for NotificationSettings
|
|
}
|
|
}
|
|
}`,
|
|
want: AST{
|
|
Definitions: []Definition{
|
|
{
|
|
Page: &Page{
|
|
Name: "Settings",
|
|
Path: "/settings",
|
|
Layout: "main",
|
|
Elements: []PageElement{
|
|
{
|
|
Section: &Section{
|
|
Name: "tabs",
|
|
Type: stringPtr("tab"),
|
|
Elements: []SectionElement{
|
|
{
|
|
Section: &Section{
|
|
Name: "profile",
|
|
Label: stringPtr("Profile"),
|
|
Active: true,
|
|
Elements: []SectionElement{
|
|
{
|
|
Component: &Component{
|
|
Type: "form",
|
|
Entity: stringPtr("User"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Section: &Section{
|
|
Name: "security",
|
|
Label: stringPtr("Security"),
|
|
Elements: []SectionElement{
|
|
{
|
|
Component: &Component{
|
|
Type: "form",
|
|
Entity: stringPtr("Security"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Section: &Section{
|
|
Name: "notifications",
|
|
Label: stringPtr("Notifications"),
|
|
Elements: []SectionElement{
|
|
{
|
|
Component: &Component{
|
|
Type: "toggle",
|
|
Entity: stringPtr("NotificationSettings"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "page with components",
|
|
input: `page Dashboard at "/dashboard" layout main {
|
|
component stats for Analytics {
|
|
field total_users type display
|
|
field revenue type display format "currency"
|
|
}
|
|
component chart for SalesData {
|
|
data from "analytics/sales"
|
|
}
|
|
}`,
|
|
want: AST{
|
|
Definitions: []Definition{
|
|
{
|
|
Page: &Page{
|
|
Name: "Dashboard",
|
|
Path: "/dashboard",
|
|
Layout: "main",
|
|
Elements: []PageElement{
|
|
{
|
|
Component: &Component{
|
|
Type: "stats",
|
|
Entity: stringPtr("Analytics"),
|
|
Elements: []ComponentElement{
|
|
{
|
|
Field: &ComponentField{
|
|
Name: "total_users",
|
|
Type: "display",
|
|
},
|
|
},
|
|
{
|
|
Field: &ComponentField{
|
|
Name: "revenue",
|
|
Type: "display",
|
|
Attributes: []ComponentFieldAttribute{
|
|
{Format: stringPtr("currency")},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Component: &Component{
|
|
Type: "chart",
|
|
Entity: stringPtr("SalesData"),
|
|
Elements: []ComponentElement{
|
|
{
|
|
Attribute: &ComponentAttr{
|
|
DataSource: stringPtr("analytics/sales"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "page with mixed sections and components",
|
|
input: `page Home at "/" layout main {
|
|
component hero for Banner {
|
|
field title type display
|
|
field subtitle type display
|
|
}
|
|
section content type container {
|
|
component posts for Post {
|
|
fields [title, excerpt, date]
|
|
}
|
|
}
|
|
component newsletter for Subscription {
|
|
field email type email required
|
|
button subscribe label "Subscribe"
|
|
}
|
|
}`,
|
|
want: AST{
|
|
Definitions: []Definition{
|
|
{
|
|
Page: &Page{
|
|
Name: "Home",
|
|
Path: "/",
|
|
Layout: "main",
|
|
Elements: []PageElement{
|
|
{
|
|
Component: &Component{
|
|
Type: "hero",
|
|
Entity: stringPtr("Banner"),
|
|
Elements: []ComponentElement{
|
|
{
|
|
Field: &ComponentField{
|
|
Name: "title",
|
|
Type: "display",
|
|
},
|
|
},
|
|
{
|
|
Field: &ComponentField{
|
|
Name: "subtitle",
|
|
Type: "display",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Section: &Section{
|
|
Name: "content",
|
|
Type: stringPtr("container"),
|
|
Elements: []SectionElement{
|
|
{
|
|
Component: &Component{
|
|
Type: "posts",
|
|
Entity: stringPtr("Post"),
|
|
Elements: []ComponentElement{
|
|
{
|
|
Attribute: &ComponentAttr{
|
|
Fields: []string{"title", "excerpt", "date"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Component: &Component{
|
|
Type: "newsletter",
|
|
Entity: stringPtr("Subscription"),
|
|
Elements: []ComponentElement{
|
|
{
|
|
Field: &ComponentField{
|
|
Name: "email",
|
|
Type: "email",
|
|
Attributes: []ComponentFieldAttribute{
|
|
{Required: true},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Button: &ComponentButton{
|
|
Name: "subscribe",
|
|
Label: "Subscribe",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePageLayouts(t *testing.T) {
|
|
layouts := []string{"main", "admin", "public", "auth", "minimal", "dashboard"}
|
|
|
|
for _, layout := range layouts {
|
|
t.Run("layout_"+layout, func(t *testing.T) {
|
|
input := `page Test at "/test" layout ` + layout
|
|
|
|
got, err := ParseInput(input)
|
|
if err != nil {
|
|
t.Errorf("ParseInput() failed for layout %s: %v", layout, err)
|
|
return
|
|
}
|
|
|
|
if len(got.Definitions) != 1 || got.Definitions[0].Page == nil {
|
|
t.Errorf("ParseInput() failed to parse page for layout %s", layout)
|
|
return
|
|
}
|
|
|
|
page := got.Definitions[0].Page
|
|
if page.Layout != layout {
|
|
t.Errorf("ParseInput() layout mismatch: got %s, want %s", page.Layout, layout)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePagePaths(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
}{
|
|
{"root", "/"},
|
|
{"simple", "/about"},
|
|
{"nested", "/admin/users"},
|
|
{"deep_nested", "/api/v1/users/profile"},
|
|
{"with_params", "/users/:id"},
|
|
{"with_multiple_params", "/users/:userId/posts/:postId"},
|
|
{"with_query", "/search?q=:query"},
|
|
{"with_extension", "/api/users.json"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
input := `page Test at "` + tt.path + `" layout main`
|
|
|
|
got, err := ParseInput(input)
|
|
if err != nil {
|
|
t.Errorf("ParseInput() failed for path %s: %v", tt.path, err)
|
|
return
|
|
}
|
|
|
|
page := got.Definitions[0].Page
|
|
if page.Path != tt.path {
|
|
t.Errorf("ParseInput() path mismatch: got %s, want %s", page.Path, tt.path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePageErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
}{
|
|
{
|
|
name: "missing page name",
|
|
input: `page at "/" layout main`,
|
|
},
|
|
{
|
|
name: "missing path",
|
|
input: `page Test layout main`,
|
|
},
|
|
{
|
|
name: "missing layout",
|
|
input: `page Test at "/"`,
|
|
},
|
|
{
|
|
name: "invalid path format",
|
|
input: `page Test at /invalid layout main`,
|
|
},
|
|
{
|
|
name: "unclosed page block",
|
|
input: `page Test at "/" layout main {
|
|
section test type container
|
|
`,
|
|
},
|
|
}
|
|
|
|
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")
|
|
}
|
|
})
|
|
}
|
|
}
|