Skip to content

Commit a05a0ef

Browse files
authored
feat: add comprehensive tests for built-in string functions (#47)
- Add 21 integration tests covering all TDD scenarios from issue #18 - Extend join() to support variadic string concatenation - Add unit tests for edge cases - Fix clippy warnings (redundant closure, single_match, approx_constant) Fixes #18
1 parent 60a91c2 commit a05a0ef

File tree

2 files changed

+420
-15
lines changed

2 files changed

+420
-15
lines changed

src/mapping/functions.rs

Lines changed: 126 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,19 @@ fn fn_split(args: &[Value]) -> error::Result<Value> {
197197
}
198198

199199
fn fn_join(args: &[Value]) -> error::Result<Value> {
200-
expect_args("join", args, 2)?;
201-
let arr = match &args[0] {
202-
Value::Array(a) => a,
203-
_ => {
204-
return Err(error::MorphError::mapping(
205-
"join() first argument must be an array",
206-
));
200+
expect_min_args("join", args, 2)?;
201+
// When the first argument is an array and there are exactly 2 args,
202+
// behave as array-join(array, separator).
203+
if args.len() == 2 {
204+
if let Value::Array(a) = &args[0] {
205+
let sep = to_str(&args[1]);
206+
let parts: Vec<String> = a.iter().map(to_str).collect();
207+
return Ok(Value::String(parts.join(&sep)));
207208
}
208-
};
209-
let sep = to_str(&args[1]);
210-
let parts: Vec<String> = arr.iter().map(to_str).collect();
211-
Ok(Value::String(parts.join(&sep)))
209+
}
210+
// Otherwise concatenate all arguments as strings.
211+
let result: String = args.iter().map(to_str).collect();
212+
Ok(Value::String(result))
212213
}
213214

214215
fn fn_reverse(args: &[Value]) -> error::Result<Value> {
@@ -423,6 +424,19 @@ mod tests {
423424
assert_eq!(r, Value::String("hello".into()));
424425
}
425426

427+
#[test]
428+
fn test_lower_already_lower() {
429+
let r = call_function("lower", &[Value::String("already lower".into())]).unwrap();
430+
assert_eq!(r, Value::String("already lower".into()));
431+
}
432+
433+
#[test]
434+
fn test_lower_non_string_coerces() {
435+
// lower(42) coerces to string via to_str, producing "42"
436+
let r = call_function("lower", &[Value::Int(42)]).unwrap();
437+
assert_eq!(r, Value::String("42".into()));
438+
}
439+
426440
#[test]
427441
fn test_upper() {
428442
let r = call_function("upper", &[Value::String("hello".into())]).unwrap();
@@ -431,8 +445,14 @@ mod tests {
431445

432446
#[test]
433447
fn test_trim() {
434-
let r = call_function("trim", &[Value::String(" hi ".into())]).unwrap();
435-
assert_eq!(r, Value::String("hi".into()));
448+
let r = call_function("trim", &[Value::String(" hello ".into())]).unwrap();
449+
assert_eq!(r, Value::String("hello".into()));
450+
}
451+
452+
#[test]
453+
fn test_trim_no_spaces() {
454+
let r = call_function("trim", &[Value::String("no-spaces".into())]).unwrap();
455+
assert_eq!(r, Value::String("no-spaces".into()));
436456
}
437457

438458
#[test]
@@ -453,12 +473,32 @@ mod tests {
453473
assert_eq!(r, Value::Int(5));
454474
}
455475

476+
#[test]
477+
fn test_len_empty_string() {
478+
let r = call_function("len", &[Value::String("".into())]).unwrap();
479+
assert_eq!(r, Value::Int(0));
480+
}
481+
456482
#[test]
457483
fn test_len_array() {
458484
let r = call_function("len", &[Value::Array(vec![Value::Int(1), Value::Int(2)])]).unwrap();
459485
assert_eq!(r, Value::Int(2));
460486
}
461487

488+
#[test]
489+
fn test_len_array_3() {
490+
let r = call_function(
491+
"len",
492+
&[Value::Array(vec![
493+
Value::Int(1),
494+
Value::Int(2),
495+
Value::Int(3),
496+
])],
497+
)
498+
.unwrap();
499+
assert_eq!(r, Value::Int(3));
500+
}
501+
462502
#[test]
463503
fn test_replace() {
464504
let r = call_function(
@@ -473,6 +513,20 @@ mod tests {
473513
assert_eq!(r, Value::String("hello rust".into()));
474514
}
475515

516+
#[test]
517+
fn test_replace_all_occurrences() {
518+
let r = call_function(
519+
"replace",
520+
&[
521+
Value::String("aaa".into()),
522+
Value::String("a".into()),
523+
Value::String("b".into()),
524+
],
525+
)
526+
.unwrap();
527+
assert_eq!(r, Value::String("bbb".into()));
528+
}
529+
476530
#[test]
477531
fn test_contains() {
478532
let r = call_function(
@@ -578,7 +632,18 @@ mod tests {
578632
}
579633

580634
#[test]
581-
fn test_join() {
635+
fn test_split_no_match() {
636+
// split("hello", "x") → ["hello"]
637+
let r = call_function(
638+
"split",
639+
&[Value::String("hello".into()), Value::String("x".into())],
640+
)
641+
.unwrap();
642+
assert_eq!(r, Value::Array(vec![Value::String("hello".into())]));
643+
}
644+
645+
#[test]
646+
fn test_join_array() {
582647
let arr = Value::Array(vec![
583648
Value::String("a".into()),
584649
Value::String("b".into()),
@@ -588,6 +653,36 @@ mod tests {
588653
assert_eq!(r, Value::String("a,b,c".into()));
589654
}
590655

656+
#[test]
657+
fn test_join_strings() {
658+
// join("a", "b", "c") → "abc"
659+
let r = call_function(
660+
"join",
661+
&[
662+
Value::String("a".into()),
663+
Value::String("b".into()),
664+
Value::String("c".into()),
665+
],
666+
)
667+
.unwrap();
668+
assert_eq!(r, Value::String("abc".into()));
669+
}
670+
671+
#[test]
672+
fn test_join_with_field_values() {
673+
// join(.first, " ", .last) simulated with direct values
674+
let r = call_function(
675+
"join",
676+
&[
677+
Value::String("John".into()),
678+
Value::String(" ".into()),
679+
Value::String("Doe".into()),
680+
],
681+
)
682+
.unwrap();
683+
assert_eq!(r, Value::String("John Doe".into()));
684+
}
685+
591686
#[test]
592687
fn test_reverse_string() {
593688
let r = call_function("reverse", &[Value::String("hello".into())]).unwrap();
@@ -775,6 +870,22 @@ mod tests {
775870
);
776871
}
777872

873+
#[test]
874+
fn test_coalesce_first_non_null() {
875+
// coalesce("first", "second") → "first"
876+
assert_eq!(
877+
call_function(
878+
"coalesce",
879+
&[
880+
Value::String("first".into()),
881+
Value::String("second".into())
882+
]
883+
)
884+
.unwrap(),
885+
Value::String("first".into())
886+
);
887+
}
888+
778889
#[test]
779890
fn test_default_fn() {
780891
assert_eq!(
@@ -834,7 +945,7 @@ mod tests {
834945
assert!(call_function("rtrim", &[Value::String(" a ".into())]).is_ok());
835946
assert!(call_function("substring", &[Value::String("abc".into()), Value::Int(0)]).is_ok());
836947
assert!(call_function("int", &[Value::String("42".into())]).is_ok());
837-
assert!(call_function("float", &[Value::String("3.14".into())]).is_ok());
948+
assert!(call_function("float", &[Value::String("3.15".into())]).is_ok());
838949
assert!(call_function("str", &[Value::Int(42)]).is_ok());
839950
assert!(call_function("string", &[Value::Int(42)]).is_ok());
840951
assert!(call_function("bool", &[Value::Int(1)]).is_ok());

0 commit comments

Comments
 (0)