Skip to content

Commit 88ac3b1

Browse files
MagicalTuxclaude
andcommitted
Add comprehensive tests for math operators, value conversion, and outvalue
- Add math operator tests: +, -, *, /, %, <<, >>, &, ^ - Add comparison operator tests: <, <=, >, >=, ==, != - Add logical operator tests: &&, ||, ! - Add float math operator tests - Add MatchValueType tests for all int/uint/float variants - Add bool conversion tests from various numeric types - Add AsFloat/AsInt tests for various input types - Add edge case tests for non-numeric values Coverage improved from ~62% to ~64.5% 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 277a5f5 commit 88ac3b1

File tree

3 files changed

+331
-2
lines changed

3 files changed

+331
-2
lines changed

math_test.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,204 @@ func TestTemplateInclusion(t *testing.T) {
167167
}
168168
}
169169

170+
func TestMathOperatorsInt(t *testing.T) {
171+
tests := []struct {
172+
name string
173+
template string
174+
expected string
175+
}{
176+
{"add", `{{set _x=(1 + 2)}}{{_x}}{{/set}}`, "3"},
177+
{"subtract", `{{set _x=(5 - 3)}}{{_x}}{{/set}}`, "2"},
178+
{"multiply", `{{set _x=(3 * 4)}}{{_x}}{{/set}}`, "12"},
179+
{"divide", `{{set _x=(10 / 2)}}{{_x}}{{/set}}`, "5"},
180+
{"modulo", `{{set _x=(10 % 3)}}{{_x}}{{/set}}`, "1"},
181+
{"precedence", `{{set _x=(2 + 3 * 4)}}{{_x}}{{/set}}`, "14"},
182+
{"parentheses", `{{set _x=((2 + 3) * 4)}}{{_x}}{{/set}}`, "20"},
183+
{"negative", `{{set _x=(0 - 5)}}{{_x}}{{/set}}`, "-5"},
184+
{"shift_left", `{{set _x=(1 << 3)}}{{_x}}{{/set}}`, "8"},
185+
{"shift_right", `{{set _x=(8 >> 2)}}{{_x}}{{/set}}`, "2"},
186+
{"bitwise_and", `{{set _x=(7 & 3)}}{{_x}}{{/set}}`, "3"},
187+
{"bitwise_xor", `{{set _x=(5 ^ 3)}}{{_x}}{{/set}}`, "6"},
188+
}
189+
190+
for _, tt := range tests {
191+
t.Run(tt.name, func(t *testing.T) {
192+
engine := tpl.New()
193+
engine.Raw.TemplateData["main"] = tt.template
194+
195+
ctx := context.Background()
196+
197+
if err := engine.Compile(ctx); err != nil {
198+
t.Fatalf("Compile failed: %v", err)
199+
}
200+
201+
result, err := engine.ParseAndReturn(ctx, "main")
202+
if err != nil {
203+
t.Fatalf("ParseAndReturn failed: %v", err)
204+
}
205+
206+
if result != tt.expected {
207+
t.Errorf("got %q, want %q", result, tt.expected)
208+
}
209+
})
210+
}
211+
}
212+
213+
func TestMathOperatorsFloat(t *testing.T) {
214+
tests := []struct {
215+
name string
216+
template string
217+
expected string
218+
}{
219+
{"add", `{{set _x=(1.5 + 2.5)}}{{_x}}{{/set}}`, "4"},
220+
{"subtract", `{{set _x=(5.5 - 3.0)}}{{_x}}{{/set}}`, "2.5"},
221+
{"multiply", `{{set _x=(2.5 * 4)}}{{_x}}{{/set}}`, "10"},
222+
{"divide", `{{set _x=(10.0 / 4)}}{{_x}}{{/set}}`, "2.5"},
223+
}
224+
225+
for _, tt := range tests {
226+
t.Run(tt.name, func(t *testing.T) {
227+
engine := tpl.New()
228+
engine.Raw.TemplateData["main"] = tt.template
229+
230+
ctx := context.Background()
231+
232+
if err := engine.Compile(ctx); err != nil {
233+
t.Fatalf("Compile failed: %v", err)
234+
}
235+
236+
result, err := engine.ParseAndReturn(ctx, "main")
237+
if err != nil {
238+
t.Fatalf("ParseAndReturn failed: %v", err)
239+
}
240+
241+
if result != tt.expected {
242+
t.Errorf("got %q, want %q", result, tt.expected)
243+
}
244+
})
245+
}
246+
}
247+
248+
func TestComparisonOperatorsInt(t *testing.T) {
249+
tests := []struct {
250+
name string
251+
template string
252+
expected string
253+
}{
254+
{"less_than_true", `{{if (1 < 2)}}yes{{else}}no{{/if}}`, "yes"},
255+
{"less_than_false", `{{if (2 < 1)}}yes{{else}}no{{/if}}`, "no"},
256+
{"less_equal_true", `{{if (2 <= 2)}}yes{{else}}no{{/if}}`, "yes"},
257+
{"less_equal_false", `{{if (3 <= 2)}}yes{{else}}no{{/if}}`, "no"},
258+
{"greater_than_true", `{{if (3 > 2)}}yes{{else}}no{{/if}}`, "yes"},
259+
{"greater_than_false", `{{if (1 > 2)}}yes{{else}}no{{/if}}`, "no"},
260+
{"greater_equal_true", `{{if (2 >= 2)}}yes{{else}}no{{/if}}`, "yes"},
261+
{"greater_equal_false", `{{if (1 >= 2)}}yes{{else}}no{{/if}}`, "no"},
262+
{"equal_true", `{{if (5 == 5)}}yes{{else}}no{{/if}}`, "yes"},
263+
{"equal_false", `{{if (5 == 6)}}yes{{else}}no{{/if}}`, "no"},
264+
{"not_equal_true", `{{if (5 != 6)}}yes{{else}}no{{/if}}`, "yes"},
265+
{"not_equal_false", `{{if (5 != 5)}}yes{{else}}no{{/if}}`, "no"},
266+
}
267+
268+
for _, tt := range tests {
269+
t.Run(tt.name, func(t *testing.T) {
270+
engine := tpl.New()
271+
engine.Raw.TemplateData["main"] = tt.template
272+
273+
ctx := context.Background()
274+
275+
if err := engine.Compile(ctx); err != nil {
276+
t.Fatalf("Compile failed: %v", err)
277+
}
278+
279+
result, err := engine.ParseAndReturn(ctx, "main")
280+
if err != nil {
281+
t.Fatalf("ParseAndReturn failed: %v", err)
282+
}
283+
284+
if result != tt.expected {
285+
t.Errorf("got %q, want %q", result, tt.expected)
286+
}
287+
})
288+
}
289+
}
290+
291+
func TestLogicalOperators(t *testing.T) {
292+
tests := []struct {
293+
name string
294+
template string
295+
vars map[string]any
296+
expected string
297+
}{
298+
{"and_true_true", `{{if ({{_a}} && {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": true, "_b": true}, "yes"},
299+
{"and_true_false", `{{if ({{_a}} && {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": true, "_b": false}, "no"},
300+
{"and_false_true", `{{if ({{_a}} && {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": false, "_b": true}, "no"},
301+
{"and_false_false", `{{if ({{_a}} && {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": false, "_b": false}, "no"},
302+
{"or_true_true", `{{if ({{_a}} || {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": true, "_b": true}, "yes"},
303+
{"or_true_false", `{{if ({{_a}} || {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": true, "_b": false}, "yes"},
304+
{"or_false_true", `{{if ({{_a}} || {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": false, "_b": true}, "yes"},
305+
{"or_false_false", `{{if ({{_a}} || {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": false, "_b": false}, "no"},
306+
{"not_true", `{{if (!{{_a}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": true}, "no"},
307+
{"not_false", `{{if (!{{_a}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": false}, "yes"},
308+
}
309+
310+
for _, tt := range tests {
311+
t.Run(tt.name, func(t *testing.T) {
312+
engine := tpl.New()
313+
engine.Raw.TemplateData["main"] = tt.template
314+
315+
ctx := context.Background()
316+
ctx = tpl.ValuesCtx(ctx, tt.vars)
317+
318+
if err := engine.Compile(ctx); err != nil {
319+
t.Fatalf("Compile failed: %v", err)
320+
}
321+
322+
result, err := engine.ParseAndReturn(ctx, "main")
323+
if err != nil {
324+
t.Fatalf("ParseAndReturn failed: %v", err)
325+
}
326+
327+
if result != tt.expected {
328+
t.Errorf("got %q, want %q", result, tt.expected)
329+
}
330+
})
331+
}
332+
}
333+
334+
func TestMathWithVariables(t *testing.T) {
335+
tests := []struct {
336+
name string
337+
template string
338+
vars map[string]any
339+
expected string
340+
}{
341+
{"add_vars", `{{set _x=({{_a}} + {{_b}})}}{{_x}}{{/set}}`, map[string]any{"_a": 3, "_b": 4}, "7"},
342+
{"multiply_vars", `{{set _x=({{_a}} * {{_b}})}}{{_x}}{{/set}}`, map[string]any{"_a": 3, "_b": 4}, "12"},
343+
{"compare_vars", `{{if ({{_a}} < {{_b}})}}yes{{else}}no{{/if}}`, map[string]any{"_a": 3, "_b": 5}, "yes"},
344+
{"float_vars", `{{set _x=({{_a}} + {{_b}})}}{{_x}}{{/set}}`, map[string]any{"_a": 1.5, "_b": 2.5}, "4"},
345+
}
346+
347+
for _, tt := range tests {
348+
t.Run(tt.name, func(t *testing.T) {
349+
engine := tpl.New()
350+
engine.Raw.TemplateData["main"] = tt.template
351+
352+
ctx := context.Background()
353+
ctx = tpl.ValuesCtx(ctx, tt.vars)
354+
355+
if err := engine.Compile(ctx); err != nil {
356+
t.Fatalf("Compile failed: %v", err)
357+
}
358+
359+
result, err := engine.ParseAndReturn(ctx, "main")
360+
if err != nil {
361+
t.Fatalf("ParseAndReturn failed: %v", err)
362+
}
363+
364+
if result != tt.expected {
365+
t.Errorf("got %q, want %q", result, tt.expected)
366+
}
367+
})
368+
}
369+
}
370+

outvalue_test.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ func TestOutValueMethods(t *testing.T) {
7070
func TestNewValueCtx(t *testing.T) {
7171
ctx := context.Background()
7272
val := tpl.NewValue("test")
73-
73+
7474
// Create ValueCtx with NewValueCtx
7575
valueCtx := tpl.NewValueCtx(ctx, val)
7676
if valueCtx == nil {
7777
t.Errorf("NewValueCtx() returned nil")
7878
}
79-
79+
8080
// Test Raw method
8181
raw, err := valueCtx.Raw()
8282
if err != nil {
@@ -85,4 +85,73 @@ func TestNewValueCtx(t *testing.T) {
8585
if raw != "test" {
8686
t.Errorf("Raw() = %v, want %v", raw, "test")
8787
}
88+
}
89+
90+
func TestAsFloatVariousTypes(t *testing.T) {
91+
ctx := context.Background()
92+
93+
tests := []struct {
94+
name string
95+
value any
96+
expected float64
97+
}{
98+
{"float64", float64(3.14), 3.14},
99+
{"int64", int64(42), 42.0},
100+
{"uint64", uint64(42), 42.0},
101+
{"bool_true", true, 1.0},
102+
{"bool_false", false, 0.0},
103+
{"string_number", "3.14", 3.14},
104+
{"nil", nil, 0.0},
105+
}
106+
107+
for _, tt := range tests {
108+
t.Run(tt.name, func(t *testing.T) {
109+
result := tpl.AsOutValue(ctx, tpl.NewValue(tt.value)).AsFloat(ctx)
110+
if result != tt.expected {
111+
t.Errorf("AsFloat() = %v, want %v", result, tt.expected)
112+
}
113+
})
114+
}
115+
}
116+
117+
func TestAsIntVariousTypes(t *testing.T) {
118+
ctx := context.Background()
119+
120+
tests := []struct {
121+
name string
122+
value any
123+
expected int64
124+
}{
125+
{"int64", int64(42), 42},
126+
{"uint64", uint64(42), 42},
127+
{"float64", float64(3.7), 4},
128+
{"bool_true", true, 1},
129+
{"bool_false", false, 0},
130+
{"string_number", "42", 42},
131+
{"nil", nil, 0},
132+
}
133+
134+
for _, tt := range tests {
135+
t.Run(tt.name, func(t *testing.T) {
136+
result := tpl.AsOutValue(ctx, tpl.NewValue(tt.value)).AsInt(ctx)
137+
if result != tt.expected {
138+
t.Errorf("AsInt() = %v, want %v", result, tt.expected)
139+
}
140+
})
141+
}
142+
}
143+
144+
func TestAsNumericEdgeCases(t *testing.T) {
145+
ctx := context.Background()
146+
147+
// Test with a value that can't be converted to a number
148+
nonNumVal := tpl.AsOutValue(ctx, tpl.NewValue(map[string]any{}))
149+
floatResult := nonNumVal.AsFloat(ctx)
150+
if floatResult != 0 {
151+
t.Errorf("AsFloat() on non-numeric = %v, want 0", floatResult)
152+
}
153+
intResult := nonNumVal.AsInt(ctx)
154+
if intResult != 0 {
155+
t.Errorf("AsInt() on non-numeric = %v, want 0", intResult)
156+
}
88157
}

value_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,19 @@ func TestValueCtxMatchValueType(t *testing.T) {
208208
}{
209209
{"to_bool_true", "1", true, true},
210210
{"to_bool_false", "0", false, false},
211+
{"to_bool_from_int", 42, true, true},
212+
{"to_bool_from_zero", 0, false, false},
211213
{"to_int", "42", int(0), int(42)},
214+
{"to_int8", "42", int8(0), int8(42)},
215+
{"to_int16", "42", int16(0), int16(42)},
216+
{"to_int32", "42", int32(0), int32(42)},
212217
{"to_int64", "42", int64(0), int64(42)},
218+
{"to_uint8", "42", uint8(0), uint8(42)},
219+
{"to_uint16", "42", uint16(0), uint16(42)},
220+
{"to_uint32", "42", uint32(0), uint32(42)},
221+
{"to_uint64", "42", uint64(0), uint64(42)},
222+
{"to_uint", "42", uint(0), uint(42)},
223+
{"to_float32", "3.14", float32(0), float32(3.14)},
213224
{"to_float64", "3.14", float64(0), float64(3.14)},
214225
{"to_string", 42, "", "42"},
215226
{"to_bytes", "hello", []byte{}, []byte("hello")},
@@ -236,3 +247,51 @@ func TestValueCtxMatchValueType(t *testing.T) {
236247
})
237248
}
238249
}
250+
251+
func TestValueCtxMatchValueTypeBoolFromNumber(t *testing.T) {
252+
ctx := context.Background()
253+
254+
tests := []struct {
255+
name string
256+
value any
257+
expected bool
258+
}{
259+
{"from_int64_nonzero", int64(42), true},
260+
{"from_int64_zero", int64(0), false},
261+
{"from_uint64_nonzero", uint64(42), true},
262+
{"from_uint64_zero", uint64(0), false},
263+
{"from_float64_nonzero", float64(3.14), true},
264+
{"from_float64_zero", float64(0), false},
265+
{"from_nil", nil, false},
266+
}
267+
268+
for _, tt := range tests {
269+
t.Run(tt.name, func(t *testing.T) {
270+
v := tpl.NewValue(tt.value)
271+
result, err := v.WithCtx(ctx).MatchValueType(true)
272+
if err != nil {
273+
t.Fatalf("MatchValueType failed: %v", err)
274+
}
275+
if result != tt.expected {
276+
t.Errorf("got %v, want %v", result, tt.expected)
277+
}
278+
})
279+
}
280+
}
281+
282+
func TestValueCtxMatchValueTypeBuffer(t *testing.T) {
283+
ctx := context.Background()
284+
285+
v := tpl.NewValue("hello")
286+
result, err := v.WithCtx(ctx).MatchValueType(&bytes.Buffer{})
287+
if err != nil {
288+
t.Fatalf("MatchValueType failed: %v", err)
289+
}
290+
buf, ok := result.(*bytes.Buffer)
291+
if !ok {
292+
t.Fatalf("expected *bytes.Buffer, got %T", result)
293+
}
294+
if buf.String() != "hello" {
295+
t.Errorf("got %q, want %q", buf.String(), "hello")
296+
}
297+
}

0 commit comments

Comments
 (0)