split tests into separate files
This commit is contained in:
344
lang/parser_component_test.go
Normal file
344
lang/parser_component_test.go
Normal file
@ -0,0 +1,344 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseComponentDefinitions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want AST
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
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 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,
|
||||
},
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user