@@ -197,18 +197,19 @@ fn fn_split(args: &[Value]) -> error::Result<Value> {
197197}
198198
199199fn 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
214215fn 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