ruchy 4.2.0

A systems scripting language that transpiles to idiomatic Rust with extreme quality engineering
Documentation
//! Tests for utility modules and common patterns

use ruchy::utils::common_patterns::*;

#[test]
fn test_is_valid_identifier() {
    // Valid identifiers
    assert!(is_valid_identifier("foo"));
    assert!(is_valid_identifier("_bar"));
    assert!(is_valid_identifier("var123"));
    assert!(is_valid_identifier("CamelCase"));
    assert!(is_valid_identifier("snake_case"));
    assert!(is_valid_identifier("_"));
    assert!(is_valid_identifier("_123"));

    // Invalid identifiers
    assert!(!is_valid_identifier(""));
    assert!(!is_valid_identifier("123abc")); // starts with digit
    assert!(!is_valid_identifier("foo-bar")); // contains hyphen
    assert!(!is_valid_identifier("foo bar")); // contains space
    assert!(!is_valid_identifier("foo.bar")); // contains dot
    assert!(!is_valid_identifier("foo@bar")); // contains @
}

#[test]
fn test_is_keyword() {
    // Keywords
    assert!(is_keyword("let"));
    assert!(is_keyword("if"));
    assert!(is_keyword("else"));
    assert!(is_keyword("while"));
    assert!(is_keyword("for"));
    assert!(is_keyword("return"));
    assert!(is_keyword("fun"));
    assert!(is_keyword("match"));
    assert!(is_keyword("true"));
    assert!(is_keyword("false"));

    // Not keywords
    assert!(!is_keyword("foo"));
    assert!(!is_keyword("Let")); // case sensitive
    assert!(!is_keyword("IF"));
    assert!(!is_keyword(""));
    assert!(!is_keyword("true1"));
}

#[test]
fn test_escape_string() {
    assert_eq!(escape_string("hello"), "hello");
    assert_eq!(escape_string("hello\nworld"), "hello\\nworld");
    assert_eq!(escape_string("tab\there"), "tab\\there");
    assert_eq!(escape_string("quote\"test"), "quote\\\"test");
    assert_eq!(escape_string("backslash\\test"), "backslash\\\\test");
    assert_eq!(escape_string("\r\n"), "\\r\\n");
}

#[test]
fn test_unescape_string() {
    assert_eq!(unescape_string("hello").unwrap(), "hello");
    assert_eq!(unescape_string("hello\\nworld").unwrap(), "hello\nworld");
    assert_eq!(unescape_string("tab\\there").unwrap(), "tab\there");
    assert_eq!(unescape_string("quote\\\"test").unwrap(), "quote\"test");
    assert_eq!(
        unescape_string("backslash\\\\test").unwrap(),
        "backslash\\test"
    );
    assert_eq!(unescape_string("\\r\\n").unwrap(), "\r\n");

    // Invalid escape sequences
    assert!(unescape_string("\\q").is_err()); // invalid escape
    assert!(unescape_string("\\").is_err()); // incomplete escape
}

#[test]
fn test_capitalize() {
    assert_eq!(capitalize("hello"), "Hello");
    assert_eq!(capitalize("WORLD"), "World");
    assert_eq!(capitalize("foo_bar"), "Foo_bar");
    assert_eq!(capitalize(""), "");
    assert_eq!(capitalize("a"), "A");
    assert_eq!(capitalize("123abc"), "123abc"); // no change if starts with digit
}

#[test]
fn test_snake_to_camel() {
    assert_eq!(snake_to_camel("hello_world"), "helloWorld");
    assert_eq!(snake_to_camel("foo_bar_baz"), "fooBarBaz");
    assert_eq!(snake_to_camel("simple"), "simple");
    assert_eq!(snake_to_camel("_private"), "_private");
    assert_eq!(snake_to_camel("__double"), "__double");
    assert_eq!(snake_to_camel(""), "");
}

#[test]
fn test_camel_to_snake() {
    assert_eq!(camel_to_snake("helloWorld"), "hello_world");
    assert_eq!(camel_to_snake("fooBarBaz"), "foo_bar_baz");
    assert_eq!(camel_to_snake("simple"), "simple");
    assert_eq!(camel_to_snake("HTTPServer"), "httpserver");
    assert_eq!(
        camel_to_snake("getHTTPResponseCode"),
        "get_httpresponse_code"
    );
    assert_eq!(camel_to_snake(""), "");
}

#[test]
fn test_is_numeric() {
    assert!(is_numeric("123"));
    assert!(is_numeric("0"));
    assert!(is_numeric("999999"));

    assert!(!is_numeric(""));
    assert!(!is_numeric("123a"));
    assert!(!is_numeric("a123"));
    assert!(!is_numeric("12.34")); // has dot
    assert!(!is_numeric("-123")); // has minus
}

#[test]
fn test_is_float() {
    assert!(is_float("3.14"));
    assert!(is_float("0.0"));
    assert!(is_float("123.456"));
    assert!(is_float(".5")); // leading dot
    assert!(is_float("5.")); // trailing dot

    assert!(!is_float("123")); // integer
    assert!(!is_float(""));
    assert!(!is_float("3.14.15")); // multiple dots
    assert!(!is_float("abc"));
}

#[test]
fn test_strip_comments() {
    assert_eq!(strip_comments("hello // comment"), "hello ");
    assert_eq!(strip_comments("// full line comment\ncode"), "\ncode");
    assert_eq!(strip_comments("no comment"), "no comment");
    assert_eq!(
        strip_comments("url: http://example.com"),
        "url: http://example.com"
    );
}

#[test]
fn test_count_lines() {
    assert_eq!(count_lines(""), 0);
    assert_eq!(count_lines("single line"), 1);
    assert_eq!(count_lines("line1\nline2"), 2);
    assert_eq!(count_lines("line1\nline2\nline3"), 3);
    assert_eq!(count_lines("line1\n\nline3"), 3); // empty line counts
    assert_eq!(count_lines("\n\n\n"), 4); // newlines only
}

#[test]
fn test_indent_string() {
    assert_eq!(indent_string("hello", 2), "  hello");
    assert_eq!(indent_string("line1\nline2", 2), "  line1\n  line2");
    assert_eq!(indent_string("", 4), "");
    assert_eq!(indent_string("test", 0), "test");
}

#[test]
fn test_trim_indent() {
    assert_eq!(trim_indent("  hello"), "hello");
    assert_eq!(trim_indent("    line1\n    line2"), "line1\nline2");
    assert_eq!(trim_indent("\t\ttabbed"), "tabbed");
    assert_eq!(trim_indent("no indent"), "no indent");
}

#[test]
fn test_split_at_delimiter() {
    assert_eq!(
        split_at_delimiter("foo,bar,baz", ','),
        vec!["foo", "bar", "baz"]
    );
    assert_eq!(split_at_delimiter("a|b|c", '|'), vec!["a", "b", "c"]);
    assert_eq!(split_at_delimiter("single", ','), vec!["single"]);
    assert_eq!(split_at_delimiter("", ','), vec![""]);
}

#[test]
fn test_common_prefix() {
    assert_eq!(common_prefix(&["hello", "help", "helpful"]), "hel");
    assert_eq!(common_prefix(&["test", "testing", "tester"]), "test");
    assert_eq!(common_prefix(&["abc", "def", "ghi"]), "");
    assert_eq!(common_prefix(&["same", "same", "same"]), "same");
    assert_eq!(common_prefix(&[]), "");
}

#[test]
fn test_levenshtein_distance() {
    assert_eq!(levenshtein_distance("kitten", "sitting"), 3);
    assert_eq!(levenshtein_distance("saturday", "sunday"), 3);
    assert_eq!(levenshtein_distance("", "abc"), 3);
    assert_eq!(levenshtein_distance("abc", ""), 3);
    assert_eq!(levenshtein_distance("same", "same"), 0);
}

// Property-based tests
#[cfg(test)]
mod property_tests {
    use super::*;
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn prop_escape_unescape_roundtrip(s in ".*") {
            let escaped = escape_string(&s);
            let unescaped = unescape_string(&escaped);

            // Should roundtrip successfully for valid strings
            if !s.contains('\\') || s.ends_with("\\\\") {
                prop_assert_eq!(unescaped.unwrap(), s);
            }
        }

        #[test]
        #[ignore = "Skip for now - needs fixing in conversion logic"]
        fn prop_snake_camel_roundtrip(s in "[a-z]+(_[a-z]+)*") {
            // Only test well-formed snake_case strings
            let camel = snake_to_camel(&s);
            let snake = camel_to_snake(&camel);

            prop_assert_eq!(s, snake);
        }

        #[test]
        fn prop_capitalize_first_char(s in "[a-z][a-z0-9]*") {
            let capitalized = capitalize(&s);
            prop_assert!(capitalized.chars().next().unwrap().is_uppercase());
        }

        #[test]
        fn prop_count_lines_consistency(s in ".*") {
            let count = count_lines(&s);
            let actual_lines: Vec<&str> = s.lines().collect();

            if s.is_empty() {
                prop_assert_eq!(count, 0);
            } else {
                prop_assert!(count >= actual_lines.len());
            }
        }
    }
}