package lang import ( "testing" ) func TestParseInput(t *testing.T) { tests := []struct { name string input string want AST wantErr bool }{ { name: "simple server definition", input: `server MyApp host "localhost" port 8080`, want: AST{ Definitions: []Definition{ { Server: &Server{ Name: "MyApp", Settings: []ServerSetting{ {Host: stringPtr("localhost")}, {Port: intPtr(8080)}, }, }, }, }, }, wantErr: false, }, { name: "entity with enhanced fields and relationships", input: `entity User desc "User management" id: uuid required unique email: string required validate email validate min_length "5" name: string default "Anonymous" profile_id: uuid relates to Profile as one via "user_id"`, want: AST{ Definitions: []Definition{ { Entity: &Entity{ Name: "User", Description: stringPtr("User management"), Fields: []Field{ { Name: "id", Type: "uuid", Required: true, Unique: true, }, { Name: "email", Type: "string", Required: true, Validations: []Validation{ {Type: "email"}, {Type: "min_length", Value: stringPtr("5")}, }, }, { Name: "name", Type: "string", Default: stringPtr("Anonymous"), }, { Name: "profile_id", Type: "uuid", Relationship: &Relationship{ Type: "Profile", Cardinality: "one", ForeignKey: stringPtr("user_id"), }, }, }, }, }, }, }, wantErr: false, }, { name: "page with container and sections", input: `page UserManagement at "/admin/users" layout AdminLayout title "User Management" auth meta description "Manage users" container main class "grid grid-cols-2" section sidebar class "col-span-1" component UserStats for User data from "/users/stats"`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "UserManagement", Path: "/admin/users", Layout: "AdminLayout", Title: stringPtr("User Management"), Auth: true, Meta: []MetaTag{ {Name: "description", Content: "Manage users"}, }, Containers: []Container{ { Type: "main", Class: stringPtr("grid grid-cols-2"), Sections: []Section{ { Name: "sidebar", Class: stringPtr("col-span-1"), Components: []Component{ { Type: "UserStats", Entity: stringPtr("User"), Elements: []ComponentElement{ { Config: &ComponentAttr{ DataSource: &ComponentDataSource{ Endpoint: "/users/stats", }, }, }, }, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "component with enhanced field configurations", input: `page UserForm at "/users/new" layout MainLayout component Form for User field email type text label "Email Address" placeholder "Enter email" required validate email field role type select options ["admin", "user"] default "user" field avatar type file accept "image/*" field bio type textarea rows 4`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "UserForm", Path: "/users/new", Layout: "MainLayout", Components: []Component{ { Type: "Form", Entity: stringPtr("User"), Elements: []ComponentElement{ { Field: &ComponentField{ Name: "email", Type: "text", Attributes: []ComponentFieldAttribute{ {Label: stringPtr("Email Address")}, {Placeholder: stringPtr("Enter email")}, {Required: true}, {Validation: &ComponentValidation{Type: "email"}}, }, }, }, { Field: &ComponentField{ Name: "role", Type: "select", Attributes: []ComponentFieldAttribute{ {Options: []string{"admin", "user"}}, {Default: stringPtr("user")}, }, }, }, { Field: &ComponentField{ Name: "avatar", Type: "file", Attributes: []ComponentFieldAttribute{ {Accept: stringPtr("image/*")}, }, }, }, { Field: &ComponentField{ Name: "bio", Type: "textarea", Attributes: []ComponentFieldAttribute{ {Rows: intPtr(4)}, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "component with conditional rendering", input: `page ConditionalForm at "/conditional" layout MainLayout component UserForm for User field role type select options ["admin", "user"] when role equals "admin" field permissions type multiselect label "Admin Permissions" options ["users.manage", "posts.manage"]`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "ConditionalForm", Path: "/conditional", Layout: "MainLayout", Components: []Component{ { Type: "UserForm", Entity: stringPtr("User"), Elements: []ComponentElement{ { Field: &ComponentField{ Name: "role", Type: "select", Attributes: []ComponentFieldAttribute{ {Options: []string{"admin", "user"}}, }, }, }, { Condition: &WhenCondition{ Field: "role", Operator: "equals", Value: "admin", Fields: []ComponentField{ { Name: "permissions", Type: "multiselect", Attributes: []ComponentFieldAttribute{ {Label: stringPtr("Admin Permissions")}, {Options: []string{"users.manage", "posts.manage"}}, }, }, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "page with tabs container", input: `page Dashboard at "/dashboard" layout MainLayout container tabs tab overview label "Overview" active component StatsCards tab users label "Users" component UserTable for User`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "Dashboard", Path: "/dashboard", Layout: "MainLayout", Containers: []Container{ { Type: "tabs", Tabs: []Tab{ { Name: "overview", Label: "Overview", Active: true, Components: []Component{ { Type: "StatsCards", Elements: []ComponentElement{}, }, }, }, { Name: "users", Label: "Users", Components: []Component{ { Type: "UserTable", Entity: stringPtr("User"), Elements: []ComponentElement{}, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "page with modal", input: `page UserList at "/users" layout MainLayout modal CreateUserModal trigger "create-user" component UserForm for User field email type text required button save label "Create" via "/users"`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "UserList", Path: "/users", Layout: "MainLayout", Modals: []Modal{ { Name: "CreateUserModal", Trigger: "create-user", Components: []Component{ { Type: "UserForm", Entity: stringPtr("User"), Elements: []ComponentElement{ { Field: &ComponentField{ Name: "email", Type: "text", Attributes: []ComponentFieldAttribute{ {Required: true}, }, }, }, { Action: &ComponentButtonAttr{ Name: "save", Label: "Create", Via: stringPtr("/users"), }, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "page with master-detail layout", input: `page PostManagement at "/admin/posts" layout AdminLayout layout "master-detail" master PostList component Table for Post field title type text sortable detail PostEditor trigger "edit" component Form for Post field title type text required`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "PostManagement", Path: "/admin/posts", Layout: "AdminLayout", LayoutType: stringPtr("master-detail"), MasterDetail: &MasterDetail{ Master: &MasterSection{ Name: "PostList", Components: []Component{ { Type: "Table", Entity: stringPtr("Post"), Elements: []ComponentElement{ { Field: &ComponentField{ Name: "title", Type: "text", Attributes: []ComponentFieldAttribute{ {Sortable: true}, }, }, }, }, }, }, }, Detail: &DetailSection{ Name: "PostEditor", Trigger: stringPtr("edit"), Components: []Component{ { Type: "Form", Entity: stringPtr("Post"), Elements: []ComponentElement{ { Field: &ComponentField{ Name: "title", Type: "text", Attributes: []ComponentFieldAttribute{ {Required: true}, }, }, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "component with sections and buttons", input: `page FormWithSections at "/form" layout MainLayout component UserForm for User section basic class "mb-4" field email type text required field name type text required section actions button save label "Save" style "primary" loading "Saving..." via "/users" button cancel label "Cancel" style "secondary"`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "FormWithSections", Path: "/form", Layout: "MainLayout", Components: []Component{ { Type: "UserForm", Entity: stringPtr("User"), Elements: []ComponentElement{ { Section: &ComponentSection{ Name: "basic", Class: stringPtr("mb-4"), Fields: []ComponentField{ { Name: "email", Type: "text", Attributes: []ComponentFieldAttribute{ {Required: true}, }, }, { Name: "name", Type: "text", Attributes: []ComponentFieldAttribute{ {Required: true}, }, }, }, }, }, { Section: &ComponentSection{ Name: "actions", Buttons: []ComponentButtonAttr{ { Name: "save", Label: "Save", Style: stringPtr("primary"), Loading: stringPtr("Saving..."), Via: stringPtr("/users"), }, { Name: "cancel", Label: "Cancel", Style: stringPtr("secondary"), }, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "detailed field configurations", input: `page DetailedTable at "/detailed" layout MainLayout component Table for User field email type text label "Email" sortable searchable field avatar type image thumbnail size "32x32" field created_at type datetime format "MMM dd, yyyy" field author_id type autocomplete source "/users" display "name" value "id"`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "DetailedTable", Path: "/detailed", Layout: "MainLayout", Components: []Component{ { Type: "Table", Entity: stringPtr("User"), Elements: []ComponentElement{ { Field: &ComponentField{ Name: "email", Type: "text", Attributes: []ComponentFieldAttribute{ {Label: stringPtr("Email")}, {Sortable: true}, {Searchable: true}, }, }, }, { Field: &ComponentField{ Name: "avatar", Type: "image", Attributes: []ComponentFieldAttribute{ {Thumbnail: true}, {Size: stringPtr("32x32")}, }, }, }, { Field: &ComponentField{ Name: "created_at", Type: "datetime", Attributes: []ComponentFieldAttribute{ {Format: stringPtr("MMM dd, yyyy")}, }, }, }, { Field: &ComponentField{ Name: "author_id", Type: "autocomplete", Attributes: []ComponentFieldAttribute{ {Source: stringPtr("/users")}, {Display: stringPtr("name")}, {Value: stringPtr("id")}, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "panel with trigger and position", input: `page PageWithPanel at "/panel" layout MainLayout container main section content panel UserEditPanel for User trigger "edit" position "slide-right" component UserForm for User field name type text required`, want: AST{ Definitions: []Definition{ { Page: &Page{ Name: "PageWithPanel", Path: "/panel", Layout: "MainLayout", Containers: []Container{ { Type: "main", Sections: []Section{ { Name: "content", Panels: []Panel{ { Name: "UserEditPanel", Entity: stringPtr("User"), Trigger: "edit", Position: stringPtr("slide-right"), Components: []Component{ { Type: "UserForm", Entity: stringPtr("User"), Elements: []ComponentElement{ { Field: &ComponentField{ Name: "name", Type: "text", Attributes: []ComponentFieldAttribute{ {Required: true}, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, wantErr: false, }, { name: "invalid syntax should error", input: `invalid syntax here`, want: AST{}, wantErr: true, }, } 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 !tt.wantErr && !astEqual(got, tt.want) { t.Errorf("ParseInput() mismatch.\nGot: %+v\nWant: %+v", got, tt.want) } }) } } // Test specifically for enhanced field features - updated for new structure func TestEnhancedFieldParsing(t *testing.T) { input := `page TestPage at "/test" layout MainLayout component Form for User field email type text label "Email" placeholder "Enter email" required validate email field role type select label "Role" options ["admin", "user"] default "user" field avatar type file accept "image/*" field bio type textarea rows 5 placeholder "Tell us about yourself"` ast, err := ParseInput(input) if err != nil { t.Fatalf("Failed to parse: %v", err) } page := ast.Definitions[0].Page component := page.Components[0] // Extract fields from elements var fields []ComponentField for _, element := range component.Elements { if element.Field != nil { fields = append(fields, *element.Field) } } if len(fields) != 4 { t.Errorf("Expected 4 fields, got %d", len(fields)) } // Test basic field structure emailField := fields[0] if emailField.Name != "email" || emailField.Type != "text" { t.Errorf("Email field incorrect: name=%s, type=%s", emailField.Name, emailField.Type) } // Test that attributes are populated if len(emailField.Attributes) == 0 { t.Error("Email field should have attributes") } // Test role field roleField := fields[1] if roleField.Name != "role" || roleField.Type != "select" { t.Errorf("Role field incorrect: name=%s, type=%s", roleField.Name, roleField.Type) } // Test file field fileField := fields[2] if fileField.Name != "avatar" || fileField.Type != "file" { t.Errorf("File field incorrect: name=%s, type=%s", fileField.Name, fileField.Type) } // Test textarea field textareaField := fields[3] if textareaField.Name != "bio" || textareaField.Type != "textarea" { t.Errorf("Textarea field incorrect: name=%s, type=%s", textareaField.Name, textareaField.Type) } } // Test for conditional rendering func TestConditionalRendering(t *testing.T) { input := `page ConditionalTest at "/test" layout MainLayout component Form for User field role type select options ["admin", "user"] when role equals "admin" field permissions type multiselect options ["manage_users", "manage_posts"] section admin_tools field audit_log type toggle` ast, err := ParseInput(input) if err != nil { t.Fatalf("Failed to parse: %v", err) } component := ast.Definitions[0].Page.Components[0] // Extract conditions from elements var conditions []WhenCondition for _, element := range component.Elements { if element.Condition != nil { conditions = append(conditions, *element.Condition) } } if len(conditions) != 1 { t.Errorf("Expected 1 condition, got %d", len(conditions)) } condition := conditions[0] if condition.Field != "role" || condition.Operator != "equals" || condition.Value != "admin" { t.Error("Condition parameters incorrect") } if len(condition.Fields) != 1 { t.Errorf("Expected 1 conditional field, got %d", len(condition.Fields)) } if len(condition.Sections) != 1 { t.Errorf("Expected 1 conditional section, got %d", len(condition.Sections)) } } // Test for config attributes after fields (reproduces parsing issue) func TestConfigAfterFields(t *testing.T) { input := `page TestPage at "/test" layout MainLayout component DetailedTable for User field email type text label "Email Address" field name type text label "Full Name" data from "/users" pagination size 20` ast, err := ParseInput(input) if err != nil { t.Fatalf("Failed to parse: %v", err) } // Verify the component was parsed correctly page := ast.Definitions[0].Page component := page.Components[0] if component.Type != "DetailedTable" { t.Errorf("Expected component type DetailedTable, got %s", component.Type) } // Count fields and config elements fieldCount := 0 configCount := 0 for _, element := range component.Elements { if element.Field != nil { fieldCount++ } if element.Config != nil { configCount++ } } if fieldCount != 2 { t.Errorf("Expected 2 fields, got %d", fieldCount) } if configCount != 2 { t.Errorf("Expected 2 config items, got %d", configCount) } } // Custom comparison functions (simplified for the new structure) func astEqual(got, want AST) bool { if len(got.Definitions) != len(want.Definitions) { return false } for i := range got.Definitions { if !definitionEqual(got.Definitions[i], want.Definitions[i]) { return false } } return true } func definitionEqual(got, want Definition) bool { // Server comparison if (got.Server == nil) != (want.Server == nil) { return false } if got.Server != nil && want.Server != nil { if got.Server.Name != want.Server.Name { return false } if len(got.Server.Settings) != len(want.Server.Settings) { return false } // Simplified server settings comparison } // Entity comparison if (got.Entity == nil) != (want.Entity == nil) { return false } if got.Entity != nil && want.Entity != nil { if got.Entity.Name != want.Entity.Name { return false } // Simplified entity comparison } // Endpoint comparison if (got.Endpoint == nil) != (want.Endpoint == nil) { return false } if got.Endpoint != nil && want.Endpoint != nil { if got.Endpoint.Method != want.Endpoint.Method || got.Endpoint.Path != want.Endpoint.Path { return false } } // Page comparison (enhanced) if (got.Page == nil) != (want.Page == nil) { return false } if got.Page != nil && want.Page != nil { return pageEqual(*got.Page, *want.Page) } return true } func pageEqual(got, want Page) bool { if got.Name != want.Name || got.Path != want.Path || got.Layout != want.Layout { return false } if !stringPtrEqual(got.Title, want.Title) { return false } if got.Auth != want.Auth { return false } if !stringPtrEqual(got.LayoutType, want.LayoutType) { return false } // Compare meta tags if len(got.Meta) != len(want.Meta) { return false } // Compare containers if len(got.Containers) != len(want.Containers) { return false } // Compare components if len(got.Components) != len(want.Components) { return false } // Compare modals if len(got.Modals) != len(want.Modals) { return false } // Compare master-detail if (got.MasterDetail == nil) != (want.MasterDetail == nil) { return false } return true } func stringPtrEqual(got, want *string) bool { if (got == nil) != (want == nil) { return false } if got != nil && want != nil { return *got == *want } return true } func intPtrEqual(got, want *int) bool { if (got == nil) != (want == nil) { return false } if got != nil && want != nil { return *got == *want } return true } // Helper functions for creating pointers func stringPtr(s string) *string { return &s } func intPtr(i int) *int { return &i }