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") } }) } }