go-twig/control_structures_test.go
semihalev 16c954898f Fix macro functionality and add comprehensive tests
- Fixed macro parameter parsing with improved tokenization handling
- Added support for proper module.function() syntax in templates
- Enhanced filter handling within macro variables
- Added support for variable interpolation in macro body
- Added comprehensive test suite for macros with various scenarios
- Improved overall coverage from 43.8% to 45.7%

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-03-11 13:15:07 +03:00

325 lines
8.9 KiB
Go

package twig
import (
"strings"
"testing"
)
// TestSimpleControlStructures tests simple control structures (if, for)
func TestSimpleControlStructures(t *testing.T) {
engine := New()
tests := []struct {
name string
source string
context map[string]interface{}
expected string
}{
// If statements
{
name: "Simple if (true)",
source: "{% if true %}yes{% endif %}",
context: nil,
expected: "yes",
},
{
name: "Simple if (false)",
source: "{% if false %}yes{% endif %}",
context: nil,
expected: "",
},
{
name: "If-else (true)",
source: "{% if true %}yes{% else %}no{% endif %}",
context: nil,
expected: "yes",
},
{
name: "If-else (false)",
source: "{% if false %}yes{% else %}no{% endif %}",
context: nil,
expected: "no",
},
{
name: "If-elseif-else (first true)",
source: "{% if true %}1{% elseif true %}2{% else %}3{% endif %}",
context: nil,
expected: "1",
},
{
name: "If-elseif-else (second true)",
source: "{% if false %}1{% elseif true %}2{% else %}3{% endif %}",
context: nil,
expected: "2",
},
{
name: "If-elseif-else (all false)",
source: "{% if false %}1{% elseif false %}2{% else %}3{% endif %}",
context: nil,
expected: "3",
},
{
name: "Nested if statements",
source: "{% if true %}outer{% if false %}inner-if{% else %}inner-else{% endif %}{% endif %}",
context: nil,
expected: "outerinner-else",
},
{
name: "If with variable condition",
source: "{% if value %}yes{% else %}no{% endif %}",
context: map[string]interface{}{"value": true},
expected: "yes",
},
{
name: "If with complex condition (and)",
source: "{% if value and otherValue %}yes{% else %}no{% endif %}",
context: map[string]interface{}{"value": true, "otherValue": true},
expected: "yes",
},
{
name: "If with complex condition (or)",
source: "{% if value or otherValue %}yes{% else %}no{% endif %}",
context: map[string]interface{}{"value": false, "otherValue": true},
expected: "yes",
},
{
name: "If with is test",
source: "{% if value is defined %}yes{% else %}no{% endif %}",
context: map[string]interface{}{"value": "something"},
expected: "yes",
},
{
name: "If with complex condition (parentheses)",
source: "{% if (a or b) and c %}yes{% else %}no{% endif %}",
context: map[string]interface{}{"a": true, "b": false, "c": true},
expected: "yes",
},
// For loops
{
name: "For loop with array",
source: "{% for item in items %}{{ item }}{% endfor %}",
context: map[string]interface{}{"items": []string{"a", "b", "c"}},
expected: "abc",
},
{
name: "For loop with array literal",
source: "{% for item in ['a', 'b', 'c'] %}{{ item }}{% endfor %}",
context: nil,
expected: "abc",
},
{
name: "For loop with map",
source: "{% for key, value in data %}{{ key }}:{{ value }};{% endfor %}",
context: map[string]interface{}{"data": map[string]interface{}{"a": 1, "b": 2, "c": 3}},
expected: "a:1;b:2;c:3;", // This is just a reference; actual map iteration order is checked separately
},
{
name: "For loop with loop variable",
source: "{% for item in items %}{{ loop.index }}:{{ item }};{% endfor %}",
context: map[string]interface{}{"items": []string{"a", "b", "c"}},
expected: "1:a;2:b;3:c;",
},
{
name: "Nested for loops",
source: "{% for i in items %}{% for j in items %}({{ i }},{{ j }}){% endfor %}{% endfor %}",
context: map[string]interface{}{"items": []string{"a", "b"}},
expected: "(a,a)(a,b)(b,a)(b,b)",
},
{
name: "For loop with if condition",
source: "{% for item in items %}{% if loop.first %}first:{% endif %}{{ item }}{% endfor %}",
context: map[string]interface{}{"items": []string{"a", "b", "c"}},
expected: "first:abc",
},
{
name: "For loop with else (non-empty)",
source: "{% for item in items %}{{ item }}{% else %}empty{% endfor %}",
context: map[string]interface{}{"items": []string{"a", "b", "c"}},
expected: "abc",
},
{
name: "For loop with else (empty)",
source: "{% for item in items %}{{ item }}{% else %}empty{% endfor %}",
context: map[string]interface{}{"items": []string{}},
expected: "empty",
},
{
name: "For loop with array access",
source: "{% for i in range(0, 2) %}{{ items[i] }}{% endfor %}",
context: map[string]interface{}{"items": []string{"a", "b", "c"}},
expected: "abc",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := engine.RegisterString("test", tt.source)
if err != nil {
t.Fatalf("Error registering template: %v", err)
}
result, err := engine.Render("test", tt.context)
if err != nil {
t.Fatalf("Error rendering template: %v", err)
}
// Special handling for map iteration which is non-deterministic in Go
if tt.name == "For loop with map" {
// Check that the result contains all the expected key-value pairs
expectedPairs := []string{"a:1;", "b:2;", "c:3;"}
for _, pair := range expectedPairs {
if !strings.Contains(result, pair) {
t.Errorf("Expected result to contain %q, but got: %q", pair, result)
}
}
} else if result != tt.expected {
t.Errorf("Expected: %q, Got: %q", tt.expected, result)
}
})
}
}
// TestRangeFunctionInForLoop tests the range function directly in a for loop
func TestRangeFunctionInForLoop(t *testing.T) {
engine := New()
// Test the actual rendering
tests := []struct {
name string
source string
expected string
}{
{
name: "Simple range function in for loop",
source: "{% for i in range(1, 3) %}{{ i }}{% endfor %}",
expected: "123",
},
{
name: "Range function with step in for loop",
source: "{% for i in range(1, 9, 2) %}{{ i }}{% endfor %}",
expected: "13579",
},
{
name: "Range function with loop variable",
source: "{% for i in range(1, 3) %}{{ loop.index }}:{{ i }};{% endfor %}",
expected: "1:1;2:2;3:3;",
},
{
name: "Range function with direct negative start",
source: "{% for i in range(-3, 0) %}{{ i }}{% endfor %}",
expected: "-3-2-10",
},
{
name: "Range function with parenthesized negative start",
source: "{% for i in range((-5), 0) %}{{ i }}{% endfor %}",
expected: "-5-4-3-2-10",
},
{
name: "Range with negative end value",
source: "{% for i in range(0, -3, -1) %}{{ i }}{% endfor %}",
expected: "0-1-2-3",
},
{
name: "Range with complex negative literals",
source: "{% for i in range((-10), (-5)) %}{{ i }}{% endfor %}",
expected: "-10-9-8-7-6-5",
},
{
name: "Range with arithmetic expressions for bounds",
source: "{% for i in range(0-5, 0) %}{{ i }}{% endfor %}",
expected: "-5-4-3-2-10",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := engine.RegisterString("test", tt.source)
if err != nil {
t.Fatalf("Error registering template: %v", err)
}
result, err := engine.Render("test", nil)
if err != nil {
t.Fatalf("Error rendering template: %v", err)
}
if result != tt.expected {
t.Errorf("Expected: %q, Got: %q", tt.expected, result)
}
})
}
}
// TestConditionalExpressions tests conditional expressions (ternary)
func TestConditionalExpressions(t *testing.T) {
engine := New()
tests := []struct {
name string
source string
context map[string]interface{}
expected string
}{
{
name: "Simple ternary (true)",
source: "{{ true ? 'yes' : 'no' }}",
context: nil,
expected: "yes",
},
{
name: "Simple ternary (false)",
source: "{{ false ? 'yes' : 'no' }}",
context: nil,
expected: "no",
},
{
name: "Ternary with variable",
source: "{{ value ? 'yes' : 'no' }}",
context: map[string]interface{}{"value": true},
expected: "yes",
},
{
name: "Ternary with complex condition",
source: "{{ (a or b) and c ? 'yes' : 'no' }}",
context: map[string]interface{}{"a": true, "b": false, "c": true},
expected: "yes",
},
{
name: "Nested ternary (outer true)",
source: "{{ true ? (true ? '1' : '2') : '3' }}",
context: nil,
expected: "1",
},
{
name: "Nested ternary (outer false)",
source: "{{ false ? '1' : (true ? '2' : '3') }}",
context: nil,
expected: "2",
},
{
name: "Ternary with arithmetic",
source: "{{ 5 > 2 ? 5 + 3 : 2 - 1 }}",
context: nil,
expected: "8",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := engine.RegisterString("test", tt.source)
if err != nil {
t.Fatalf("Error registering template: %v", err)
}
result, err := engine.Render("test", tt.context)
if err != nil {
t.Fatalf("Error rendering template: %v", err)
}
if result != tt.expected {
t.Errorf("Expected: %q, Got: %q", tt.expected, result)
}
})
}
}