Files
masonry/lang/parser_ui_advanced_test.go

576 lines
16 KiB
Go

package lang
import (
"testing"
)
func TestParseAdvancedUIFeatures(t *testing.T) {
tests := []struct {
name string
input string
want AST
wantErr bool
}{
{
name: "complex conditional rendering with multiple operators",
input: `page Test at "/test" layout main {
component form for User {
field status type select options ["active", "inactive", "pending"]
when status equals "active" {
field last_login type datetime
field permissions type multiselect
button deactivate label "Deactivate User" style "warning"
}
when status not_equals "active" {
field reason type textarea placeholder "Reason for status"
button activate label "Activate User" style "success"
}
when status contains "pending" {
field approval_date type date
button approve label "Approve" style "primary"
button reject label "Reject" style "danger"
}
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Components: []Component{
{
Type: "form",
Entity: stringPtr("User"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "status",
Type: "select",
Attributes: []ComponentFieldAttribute{
{Options: []string{"active", "inactive", "pending"}},
},
},
},
{
When: &WhenCondition{
Field: "status",
Operator: "equals",
Value: "active",
Fields: []ComponentField{
{Name: "last_login", Type: "datetime"},
{Name: "permissions", Type: "multiselect"},
},
Buttons: []ComponentButton{
{
Name: "deactivate",
Label: "Deactivate User",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "warning"}},
},
},
},
},
},
{
When: &WhenCondition{
Field: "status",
Operator: "not_equals",
Value: "active",
Fields: []ComponentField{
{
Name: "reason",
Type: "textarea",
Attributes: []ComponentFieldAttribute{
{Placeholder: stringPtr("Reason for status")},
},
},
},
Buttons: []ComponentButton{
{
Name: "activate",
Label: "Activate User",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "success"}},
},
},
},
},
},
{
When: &WhenCondition{
Field: "status",
Operator: "contains",
Value: "pending",
Fields: []ComponentField{
{Name: "approval_date", Type: "date"},
},
Buttons: []ComponentButton{
{
Name: "approve",
Label: "Approve",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "primary"}},
},
},
{
Name: "reject",
Label: "Reject",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "danger"}},
},
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "field attributes with all possible options",
input: `page Test at "/test" layout main {
component form for Product {
field name type text {
label "Product Name"
placeholder "Enter product name"
required
default "New Product"
validate min_length "3"
size "large"
display "block"
}
field price type number {
label "Price ($)"
format "currency"
validate min "0"
validate max "10000"
}
field category type autocomplete {
label "Category"
placeholder "Start typing..."
relates to Category
searchable
source "categories/search"
}
field tags type multiselect {
label "Tags"
options ["electronics", "clothing", "books", "home"]
source "tags/popular"
}
field description type richtext {
label "Description"
rows 10
placeholder "Describe your product..."
}
field thumbnail type image {
label "Product Image"
accept "image/jpeg,image/png"
thumbnail
}
field featured type checkbox {
label "Featured Product"
default "false"
value "true"
}
field availability type select {
label "Availability"
options ["in_stock", "out_of_stock", "pre_order"]
default "in_stock"
sortable
}
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Components: []Component{
{
Type: "form",
Entity: stringPtr("Product"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "name",
Type: "text",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Product Name")},
{Placeholder: stringPtr("Enter product name")},
{Required: true},
{Default: stringPtr("New Product")},
{Validation: &ComponentValidation{Type: "min_length", Value: stringPtr("3")}},
{Size: stringPtr("large")},
{Display: stringPtr("block")},
},
},
},
{
Field: &ComponentField{
Name: "price",
Type: "number",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Price ($)")},
{Format: stringPtr("currency")},
{Validation: &ComponentValidation{Type: "min", Value: stringPtr("0")}},
{Validation: &ComponentValidation{Type: "max", Value: stringPtr("10000")}},
},
},
},
{
Field: &ComponentField{
Name: "category",
Type: "autocomplete",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Category")},
{Placeholder: stringPtr("Start typing...")},
{Relates: &FieldRelation{Type: "Category"}},
{Searchable: true},
{Source: stringPtr("categories/search")},
},
},
},
{
Field: &ComponentField{
Name: "tags",
Type: "multiselect",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Tags")},
{Options: []string{"electronics", "clothing", "books", "home"}},
{Source: stringPtr("tags/popular")},
},
},
},
{
Field: &ComponentField{
Name: "description",
Type: "richtext",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Description")},
{Rows: intPtr(10)},
{Placeholder: stringPtr("Describe your product...")},
},
},
},
{
Field: &ComponentField{
Name: "thumbnail",
Type: "image",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Product Image")},
{Accept: stringPtr("image/jpeg,image/png")},
{Thumbnail: true},
},
},
},
{
Field: &ComponentField{
Name: "featured",
Type: "checkbox",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Featured Product")},
{Default: stringPtr("false")},
{Value: stringPtr("true")},
},
},
},
{
Field: &ComponentField{
Name: "availability",
Type: "select",
Attributes: []ComponentFieldAttribute{
{Label: stringPtr("Availability")},
{Options: []string{"in_stock", "out_of_stock", "pre_order"}},
{Default: stringPtr("in_stock")},
{Sortable: true},
},
},
},
},
},
},
},
},
},
},
},
{
name: "complex button configurations",
input: `page Test at "/test" layout main {
component form for Order {
field status type select options ["draft", "submitted", "approved"]
button save label "Save Draft" style "secondary" icon "save" position "left"
button submit label "Submit Order" style "primary" icon "send" loading "Submitting..." confirm "Submit this order?"
button approve label "Approve" style "success" loading "Approving..." disabled when status confirm "Approve this order?" target approval_modal via "api/orders/approve"
button reject label "Reject" style "danger" icon "x" confirm "Are you sure you want to reject this order?"
button print label "Print" style "outline" icon "printer" position "right"
}
}`,
want: AST{
Definitions: []Definition{
{
Page: &Page{
Name: "Test",
Path: "/test",
Layout: "main",
Components: []Component{
{
Type: "form",
Entity: stringPtr("Order"),
Elements: []ComponentElement{
{
Field: &ComponentField{
Name: "status",
Type: "select",
Attributes: []ComponentFieldAttribute{
{Options: []string{"draft", "submitted", "approved"}},
},
},
},
{
Button: &ComponentButton{
Name: "save",
Label: "Save Draft",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "secondary"}},
{Icon: &ComponentButtonIcon{Value: "save"}},
{Position: &ComponentButtonPosition{Value: "left"}},
},
},
},
{
Button: &ComponentButton{
Name: "submit",
Label: "Submit Order",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "primary"}},
{Icon: &ComponentButtonIcon{Value: "send"}},
{Loading: &ComponentButtonLoading{Value: "Submitting..."}},
{Confirm: &ComponentButtonConfirm{Value: "Submit this order?"}},
},
},
},
{
Button: &ComponentButton{
Name: "approve",
Label: "Approve",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "success"}},
{Loading: &ComponentButtonLoading{Value: "Approving..."}},
{Disabled: &ComponentButtonDisabled{Value: "status"}},
{Confirm: &ComponentButtonConfirm{Value: "Approve this order?"}},
{Target: &ComponentButtonTarget{Value: "approval_modal"}},
{Via: &ComponentButtonVia{Value: "api/orders/approve"}},
},
},
},
{
Button: &ComponentButton{
Name: "reject",
Label: "Reject",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "danger"}},
{Icon: &ComponentButtonIcon{Value: "x"}},
{Confirm: &ComponentButtonConfirm{Value: "Are you sure you want to reject this order?"}},
},
},
},
{
Button: &ComponentButton{
Name: "print",
Label: "Print",
Attributes: []ComponentButtonAttr{
{Style: &ComponentButtonStyle{Value: "outline"}},
{Icon: &ComponentButtonIcon{Value: "printer"}},
{Position: &ComponentButtonPosition{Value: "right"}},
},
},
},
},
},
},
},
},
},
},
},
}
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 TestParseFieldValidationTypes(t *testing.T) {
validationTypes := []struct {
validation string
hasValue bool
}{
{"email", false},
{"required", false},
{"min_length", true},
{"max_length", true},
{"min", true},
{"max", true},
{"pattern", true},
{"numeric", false},
{"alpha", false},
{"alphanumeric", false},
{"url", false},
{"date", false},
{"datetime", false},
{"time", false},
{"phone", false},
{"postal_code", false},
{"credit_card", false},
}
for _, vt := range validationTypes {
t.Run("validation_"+vt.validation, func(t *testing.T) {
var input string
if vt.hasValue {
input = `page Test at "/test" layout main {
component form {
field test_field type text validate ` + vt.validation + ` "test_value"
}
}`
} else {
input = `page Test at "/test" layout main {
component form {
field test_field type text validate ` + vt.validation + `
}
}`
}
got, err := ParseInput(input)
if err != nil {
t.Errorf("ParseInput() failed for validation %s: %v", vt.validation, err)
return
}
if len(got.Definitions) != 1 || got.Definitions[0].Page == nil {
t.Errorf("ParseInput() failed to parse page for validation %s", vt.validation)
return
}
page := got.Definitions[0].Page
if len(page.Components) != 1 || len(page.Components[0].Elements) != 1 {
t.Errorf("ParseInput() failed to parse component for validation %s", vt.validation)
return
}
element := page.Components[0].Elements[0]
if element.Field == nil || len(element.Field.Attributes) != 1 {
t.Errorf("ParseInput() failed to parse field attributes for validation %s", vt.validation)
return
}
attr := element.Field.Attributes[0]
if attr.Validation == nil || attr.Validation.Type != vt.validation {
t.Errorf("ParseInput() validation type mismatch: got %v, want %s", attr.Validation, vt.validation)
}
if vt.hasValue && (attr.Validation.Value == nil || *attr.Validation.Value != "test_value") {
t.Errorf("ParseInput() validation value mismatch for %s", vt.validation)
}
})
}
}
func TestParseConditionalOperators(t *testing.T) {
operators := []string{"equals", "not_equals", "contains"}
for _, op := range operators {
t.Run("operator_"+op, func(t *testing.T) {
input := `page Test at "/test" layout main {
component form {
field test_field type text
when test_field ` + op + ` "test_value" {
field conditional_field type text
}
}
}`
got, err := ParseInput(input)
if err != nil {
t.Errorf("ParseInput() failed for operator %s: %v", op, err)
return
}
// Verify the when condition was parsed correctly
page := got.Definitions[0].Page
component := page.Components[0]
whenElement := component.Elements[1].When
if whenElement == nil || whenElement.Operator != op {
t.Errorf("ParseInput() operator mismatch: got %v, want %s", whenElement, op)
}
})
}
}
func TestParseAdvancedUIErrors(t *testing.T) {
tests := []struct {
name string
input string
}{
{
name: "invalid conditional operator",
input: `page Test at "/test" layout main {
component form {
when field invalid_operator "value" {
field test type text
}
}
}`,
},
{
name: "missing field attribute block closure",
input: `page Test at "/test" layout main {
component form {
field test type text {
label "Test"
required
}
}`,
},
}
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")
}
})
}
}