package interpreter import ( "os" "path/filepath" "strings" "testing" ) func TestCustomJavaScriptFunctions(t *testing.T) { // Create a temporary directory for test files tempDir := t.TempDir() // Create a test Masonry file masonryContent := `entity User { name: string required email: string required unique } entity Post { title: string required slug: string required authorId: uuid required }` masonryFile := filepath.Join(tempDir, "test.masonry") err := os.WriteFile(masonryFile, []byte(masonryContent), 0644) if err != nil { t.Fatalf("Failed to write test Masonry file: %v", err) } // Create templates directory templatesDir := filepath.Join(tempDir, "templates") err = os.MkdirAll(templatesDir, 0755) if err != nil { t.Fatalf("Failed to create templates directory: %v", err) } // Create a test template that uses custom functions templateContent := `package models // {{formatComment "Generated model"}} type {{.Entity.Name | title}} struct { Name string } // Table name: {{pluralize (.Entity.Name | lower)}} func ({{.Entity.Name | title}}) TableName() string { return "{{pluralize (.Entity.Name | lower)}}" } // Custom function result: {{customUpper (.Entity.Name)}}` templateFile := filepath.Join(templatesDir, "model.tmpl") err = os.WriteFile(templateFile, []byte(templateContent), 0644) if err != nil { t.Fatalf("Failed to write test template file: %v", err) } // Create manifest with custom JavaScript functions manifestContent := `name: "Test Generator" description: "Test custom JavaScript functions" functions: # Format a comment block formatComment: | function main() { var text = args[0] || ""; return "// " + text; } # Convert singular words to plural (simple rules) pluralize: | function main() { var word = args[0] || ""; if (word.slice(-1) === 'y') { return word.slice(0, -1) + 'ies'; } else if (word.slice(-1) === 's') { return word + 'es'; } else { return word + 's'; } } # Convert to uppercase customUpper: | function main() { var text = args[0] || ""; return text.toUpperCase(); } outputs: - path: "models/{{.Entity.Name | lower}}.go" template: "model" iterator: "entities" item_context: "Entity"` manifestFile := filepath.Join(templatesDir, "manifest.yaml") err = os.WriteFile(manifestFile, []byte(manifestContent), 0644) if err != nil { t.Fatalf("Failed to write test manifest file: %v", err) } // Create template interpreter and test interpreter := NewTemplateInterpreter() // Test InterpretToFiles with custom functions outputs, err := interpreter.InterpretToFiles(masonryFile, templatesDir, "manifest.yaml") if err != nil { t.Fatalf("InterpretToFiles failed: %v", err) } // Should generate 2 files (User and Post) expectedFiles := 2 if len(outputs) != expectedFiles { t.Errorf("Expected %d output files, got %d", expectedFiles, len(outputs)) } // Check User model output userFile := "models/user.go" userContent, exists := outputs[userFile] if !exists { t.Errorf("Expected output file %s not found", userFile) } else { // Test formatComment function if !strings.Contains(userContent, "// Generated model") { t.Errorf("formatComment function not working: expected '// Generated model' in output") } // Test pluralize function if !strings.Contains(userContent, "Table name: users") { t.Errorf("pluralize function not working: expected 'Table name: users' in output") } if !strings.Contains(userContent, `return "users"`) { t.Errorf("pluralize function not working in template: expected 'return \"users\"' in output") } // Test customUpper function if !strings.Contains(userContent, "Custom function result: USER") { t.Errorf("customUpper function not working: expected 'Custom function result: USER' in output") } } // Check Post model output postFile := "models/post.go" postContent, exists := outputs[postFile] if !exists { t.Errorf("Expected output file %s not found", postFile) } else { // Test pluralize function with 's' ending if !strings.Contains(postContent, "Table name: posts") { t.Errorf("pluralize function not working for 's' ending: expected 'Table name: posts' in output") } if !strings.Contains(postContent, `return "posts"`) { t.Errorf("pluralize function not working in template for 's' ending: expected 'return \"posts\"' in output") } // Test customUpper function if !strings.Contains(postContent, "Custom function result: POST") { t.Errorf("customUpper function not working: expected 'Custom function result: POST' in output") } } } func TestCustomFunctionErrorHandling(t *testing.T) { interpreter := NewTemplateInterpreter() tests := []struct { name string functionName string jsCode string expectError bool errorContains string }{ { name: "Invalid function name", functionName: "123invalid", jsCode: "function main() { return 'test'; }", expectError: true, errorContains: "invalid function name", }, { name: "Reserved function name", functionName: "and", jsCode: "function main() { return 'test'; }", expectError: true, errorContains: "invalid function name", }, { name: "Missing main function", functionName: "testFunc", jsCode: "function notMain() { return 'test'; }", expectError: false, // Error will occur during execution, not loading }, { name: "Valid function", functionName: "validFunc", jsCode: "function main() { return 'test'; }", expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { functions := map[string]string{ tt.functionName: tt.jsCode, } err := interpreter.loadCustomFunctions(functions) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) { t.Errorf("Expected error to contain '%s', got: %v", tt.errorContains, err) } } else { if err != nil { t.Errorf("Unexpected error: %v", err) } } }) } } func TestJavaScriptFunctionExecution(t *testing.T) { interpreter := NewTemplateInterpreter() // Load a test function functions := map[string]string{ "testConcat": ` function main() { var result = ""; for (var i = 0; i < args.length; i++) { result += args[i]; if (i < args.length - 1) result += "-"; } return result; } `, "testMath": ` function main() { var a = args[0] || 0; var b = args[1] || 0; return a + b; } `, "testError": ` function main() { throw new Error("Test error"); } `, "testNoMain": ` function notMain() { return "should not work"; } `, } err := interpreter.loadCustomFunctions(functions) if err != nil { t.Fatalf("Failed to load custom functions: %v", err) } // Test function execution through template testCases := []struct { name string template string data interface{} expectedOutput string expectError bool errorContains string }{ { name: "Concat function with multiple args", template: `{{testConcat "hello" "world" "test"}}`, data: struct{}{}, expectedOutput: "hello-world-test", expectError: false, }, { name: "Math function", template: `{{testMath 5 3}}`, data: struct{}{}, expectedOutput: "8", expectError: false, }, { name: "Function with error", template: `{{testError}}`, data: struct{}{}, expectError: true, errorContains: "Test error", }, { name: "Function without main", template: `{{testNoMain}}`, data: struct{}{}, expectError: true, errorContains: "must define a main() function", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result, err := interpreter.Interpret("", tc.template) if tc.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tc.errorContains != "" && !strings.Contains(err.Error(), tc.errorContains) { t.Errorf("Expected error to contain '%s', got: %v", tc.errorContains, err) } } else { if err != nil { t.Errorf("Unexpected error: %v", err) } else if result != tc.expectedOutput { t.Errorf("Expected output '%s', got '%s'", tc.expectedOutput, result) } } }) } } func TestCustomFunctionArgumentHandling(t *testing.T) { interpreter := NewTemplateInterpreter() // Load test functions that handle different argument types functions := map[string]string{ "argCount": ` function main() { return args.length; } `, "argTypes": ` function main() { var types = []; for (var i = 0; i < args.length; i++) { types.push(typeof args[i]); } return types.join(","); } `, "argAccess": ` function main() { // Test both args array and individual arg variables var fromArray = args[0] || "empty"; var fromVar = arg0 || "empty"; return fromArray + ":" + fromVar; } `, } err := interpreter.loadCustomFunctions(functions) if err != nil { t.Fatalf("Failed to load custom functions: %v", err) } testCases := []struct { name string template string expectedOutput string }{ { name: "No arguments", template: `{{argCount}}`, expectedOutput: "0", }, { name: "Multiple arguments", template: `{{argCount "a" "b" "c"}}`, expectedOutput: "3", }, { name: "Argument types", template: `{{argTypes "string" 42}}`, expectedOutput: "string,number", }, { name: "Argument access methods", template: `{{argAccess "test"}}`, expectedOutput: "test:test", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result, err := interpreter.Interpret("", tc.template) if err != nil { t.Errorf("Unexpected error: %v", err) } else if result != tc.expectedOutput { t.Errorf("Expected output '%s', got '%s'", tc.expectedOutput, result) } }) } } func TestFunctionLoadingOrder(t *testing.T) { // This test ensures that custom functions are loaded before templates // to prevent the "function not defined" error that was fixed tempDir := t.TempDir() templatesDir := filepath.Join(tempDir, "templates") err := os.MkdirAll(templatesDir, 0755) if err != nil { t.Fatalf("Failed to create templates directory: %v", err) } // Create a template that uses a custom function templateContent := `{{customFunc "test"}}` templateFile := filepath.Join(templatesDir, "test.tmpl") err = os.WriteFile(templateFile, []byte(templateContent), 0644) if err != nil { t.Fatalf("Failed to write template file: %v", err) } // Create manifest with the custom function manifestContent := `name: "Order Test" functions: customFunc: | function main() { return "custom:" + args[0]; } outputs: - path: "test.txt" template: "test"` manifestFile := filepath.Join(templatesDir, "manifest.yaml") err = os.WriteFile(manifestFile, []byte(manifestContent), 0644) if err != nil { t.Fatalf("Failed to write manifest file: %v", err) } // Create empty Masonry file masonryFile := filepath.Join(tempDir, "empty.masonry") err = os.WriteFile(masonryFile, []byte(""), 0644) if err != nil { t.Fatalf("Failed to write Masonry file: %v", err) } // Test that InterpretToFiles works (this would fail if functions aren't loaded first) interpreter := NewTemplateInterpreter() outputs, err := interpreter.InterpretToFiles(masonryFile, templatesDir, "manifest.yaml") if err != nil { t.Fatalf("InterpretToFiles failed: %v", err) } // Check that the custom function was executed testContent, exists := outputs["test.txt"] if !exists { t.Errorf("Expected output file test.txt not found") } else if testContent != "custom:test" { t.Errorf("Expected 'custom:test', got '%s'", testContent) } }