Files
masonry/lang/parser_ui_component_test.go

567 lines
14 KiB
Go

package lang
import (
"testing"
)
func TestParseComponentDefinitions(t *testing.T) {
tests := []struct {
name string
input string
want AST
wantErr bool
}{
{
name: "basic component with entity",
input: `page Test at "/test" layout main {
component table for User
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Elements: []PageElement{
{
Component: &Component{
Type: "table",
Entity: stringPtr("User"),
},
},
},
},
},
},
},
},
{
name: "form component with fields",
input: `page Test at "/test" layout main {
component form for User {
field name type text label "Full Name" placeholder "Enter your name" required
field email type email label "Email Address" required
field bio type textarea rows 5 placeholder "Tell us about yourself"
field avatar type file accept "image/*"
field role type select options ["admin", "user", "guest"] default "user"
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Elements: []PageElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Full Name")},
{Placeholder: stringPtr("Enter your name")},
{Required: true},
},
},
},
{
Field: &ComponentField{
Name: "email",
Type: "email",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Email Address")},
{Required: true},
},
},
},
{
Field: &ComponentField{
Name: "bio",
Type: "textarea",
Attributes: []ComponentFieldAttribute{
{Rows: intPtr(5)},
{Placeholder: stringPtr("Tell us about yourself")},
},
},
},
{
Field: &ComponentField{
Name: "avatar",
Type: "file",
Attributes: []ComponentFieldAttribute{
{Accept: stringPtr("image/*")},
},
},
},
{
Field: &ComponentField{
Name: "role",
Type: "select",
Attributes: []ComponentFieldAttribute{
{Options: []string{"admin", "user", "guest"}},
{Default: stringPtr("user")},
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "component with field attributes and validation",
input: `page Test at "/test" layout main {
component form for Product {
field name type text required validate min_length "3"
field price type number format "currency" validate min "0"
field category type autocomplete relates to Category
field tags type multiselect source "tags/popular"
field description type richtext
field featured type checkbox default "false"
field thumbnail type image thumbnail
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Elements: []PageElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("Product"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Required: true},
{Validation: &ComponentValidation{Type: "min_length", Value: stringPtr("3")}},
},
},
},
{
Field: &ComponentField{
Name: "price",
Type: "number",
Attributes: []ComponentFieldAttribute{
{Format: stringPtr("currency")},
{Validation: &ComponentValidation{Type: "min", Value: stringPtr("0")}},
},
},
},
{
Field: &ComponentField{
Name: "category",
Type: "autocomplete",
Attributes: []ComponentFieldAttribute{
{Relates: &FieldRelation{Type: "Category"}},
},
},
},
{
Field: &ComponentField{
Name: "tags",
Type: "multiselect",
Attributes: []ComponentFieldAttribute{
{Source: stringPtr("tags/popular")},
},
},
},
{
Field: &ComponentField{
Name: "description",
Type: "richtext",
},
},
{
Field: &ComponentField{
Name: "featured",
Type: "checkbox",
Attributes: []ComponentFieldAttribute{
{Default: stringPtr("false")},
},
},
},
{
Field: &ComponentField{
Name: "thumbnail",
Type: "image",
Attributes: []ComponentFieldAttribute{
{Thumbnail: true},
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "component with buttons",
input: `page Test at "/test" layout main {
component form for User {
field name type text
button save label "Save User" style "primary" icon "save"
button cancel label "Cancel" style "secondary"
button delete label "Delete" style "danger" confirm "Are you sure?" disabled when is_protected
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Elements: []PageElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
},
},
{
Button: &ComponentButton{
Name: "save",
Label: "Save User",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "primary"}},
{Icon: &ComponentButtonIcon{Value: "save"}},
},
},
},
{
Button: &ComponentButton{
Name: "cancel",
Label: "Cancel",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "secondary"}},
},
},
},
{
Button: &ComponentButton{
Name: "delete",
Label: "Delete",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "danger"}},
{Confirm: &ComponentButtonConfirm{Value: "Are you sure?"}},
{Disabled: &ComponentButtonDisabled{Value: "is_protected"}},
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "component with conditional fields",
input: `page Test at "/test" layout main {
component form for User {
field account_type type select options ["personal", "business"]
when account_type equals "business" {
field company_name type text required
field tax_id type text
button verify_business label "Verify Business"
}
when account_type equals "personal" {
field date_of_birth type date
}
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Elements: []PageElement{
{
Component: &Component{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "account_type",
Type: "select",
Attributes: []ComponentFieldAttribute{
{Options: []string{"personal", "business"}},
},
},
},
{
When: &WhenCondition{
Field: "account_type",
Operator: "equals",
Value: "business",
Fields: []ComponentField{
{
Name: "company_name",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Required: true},
},
},
{
Name: "tax_id",
Type: "text",
},
},
Buttons: []ComponentButton{
{
Name: "verify_business",
Label: "Verify Business",
},
},
},
},
{
When: &WhenCondition{
Field: "account_type",
Operator: "equals",
Value: "personal",
Fields: []ComponentField{
{
Name: "date_of_birth",
Type: "date",
},
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "component with nested sections",
input: `page Test at "/test" layout main {
component dashboard {
section stats type container class "stats-grid" {
component metric {
field total_users type display value "1,234"
field revenue type display format "currency" value "45,678"
}
}
section charts type container {
component chart for Analytics {
data from "analytics/monthly"
}
}
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Elements: []PageElement{
{
Component: &Component{
Type: "dashboard",
Elements: []ComponentElement{
{
Section: &Section{
Name: "stats",
Type: stringPtr("container"),
Class: stringPtr("stats-grid"),
Elements: []SectionElement{
{
Component: &Component{
Type: "metric",
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "total_users",
Type: "display",
Attributes: []ComponentFieldAttribute{
{Value: stringPtr("1,234")},
},
},
},
{
Field: &ComponentField{
Name: "revenue",
Type: "display",
Attributes: []ComponentFieldAttribute{
{Format: stringPtr("currency")},
{Value: stringPtr("45,678")},
},
},
},
},
},
},
},
},
},
{
Section: &Section{
Name: "charts",
Type: stringPtr("container"),
Elements: []SectionElement{
{
Component: &Component{
Type: "chart",
Entity: stringPtr("Analytics"),
Elements: []ComponentElement{
{
Attribute: &ComponentAttr{
DataSource: stringPtr("analytics/monthly"),
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
}
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 TestParseComponentFieldTypes(t *testing.T) {
fieldTypes := []string{
"text", "email", "password", "number", "date", "datetime", "time",
"textarea", "richtext", "select", "multiselect", "checkbox", "radio",
"file", "image", "autocomplete", "range", "color", "url", "tel",
"hidden", "display", "json", "code",
}
for _, fieldType := range fieldTypes {
t.Run("field_type_"+fieldType, func(t *testing.T) {
input := `page Test at "/test" layout main {
component form {
field test_field type ` + fieldType + `
}
}`
got, err := ParseInput(input)
if err != nil {
t.Errorf("ParseInput() failed for field type %s: %v", fieldType, err)
return
}
if len(got.Definitions) != 1 || got.Definitions[0].Page == nil {
t.Errorf("ParseInput() failed to parse page for field type %s", fieldType)
return
}
page := got.Definitions[0].Page
if len(page.Elements) != 1 {
t.Errorf("ParseInput() failed to parse component for field type %s", fieldType)
return
}
element := page.Elements[0]
if element.Component == nil || len(element.Component.Elements) != 1 {
t.Errorf("ParseInput() failed to parse component for field type %s", fieldType)
return
}
fieldElement := element.Component.Elements[0]
if fieldElement.Field == nil || fieldElement.Field.Type != fieldType {
t.Errorf("ParseInput() field type mismatch: got %v, want %s", fieldElement.Field, fieldType)
}
})
}
}
func TestParseComponentErrors(t *testing.T) {
tests := []struct {
name string
input string
}{
{
name: "missing component type",
input: `page Test at "/test" layout main {
component
}`,
},
{
name: "invalid field syntax",
input: `page Test at "/test" layout main {
component form {
field name
}
}`,
},
{
name: "invalid button syntax",
input: `page Test at "/test" layout main {
component form {
button
}
}`,
},
}
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")
}
})
}
}