use super::*;
fn assert_contains_all(result: &[String], expected: &[&str]) {
for exp in expected {
assert!(
result.contains(&exp.to_string()),
"Expected {:?} to contain {:?}",
result,
exp
);
}
}
fn assert_not_contains(result: &[String], not_expected: &[&str]) {
for exp in not_expected {
assert!(
!result.contains(&exp.to_string()),
"Expected {:?} to NOT contain {:?}",
result,
exp
);
}
}
mod extract_variables_tests {
use super::*;
#[test]
fn includes_builtin_variables() {
let result = extract_variables("");
assert_contains_all(&result, &["$ENV", "$__loc__"]);
}
#[test]
fn simple_as_binding() {
let result = extract_variables(". as $x | $x");
assert_contains_all(&result, &["$x", "$ENV", "$__loc__"]);
}
#[test]
fn multiple_as_bindings() {
let result = extract_variables(". as $a | . as $b | $a + $b");
assert_contains_all(&result, &["$a", "$b"]);
}
#[test]
fn reduce_binding() {
let result = extract_variables("reduce .[] as $item (0; . + $item)");
assert_contains_all(&result, &["$item"]);
}
#[test]
fn foreach_binding() {
let result = extract_variables("foreach .[] as $x (0; . + $x)");
assert_contains_all(&result, &["$x"]);
}
#[test]
fn label_binding() {
let result = extract_variables(
"label $out | foreach .[] as $x (0; . + $x; if . > 3 then ., break $out else . end)",
);
assert_contains_all(&result, &["$out", "$x"]);
}
#[test]
fn array_destructuring() {
let result = extract_variables(". as [$first, $second] | $first");
assert_contains_all(&result, &["$first", "$second"]);
}
#[test]
fn object_destructuring() {
let result = extract_variables(". as {name: $n, age: $a} | $n");
assert_contains_all(&result, &["$n", "$a"]);
}
#[test]
fn nested_array_destructuring() {
let result = extract_variables(". as [[$a, $b], $c] | $a");
assert_contains_all(&result, &["$a", "$b", "$c"]);
}
#[test]
fn deduplicates_repeated_definitions() {
let result = extract_variables(". as $x | . as $x | $x");
let count = result.iter().filter(|v| *v == "$x").count();
assert_eq!(count, 1, "Variable $x should appear only once");
}
#[test]
fn variable_with_underscore() {
let result = extract_variables(". as $my_var | $my_var");
assert_contains_all(&result, &["$my_var"]);
}
#[test]
fn variable_with_numbers() {
let result = extract_variables(". as $x123 | $x123");
assert_contains_all(&result, &["$x123"]);
}
#[test]
fn ignores_variables_in_strings() {
let result = extract_variables("\"as $fake\" | .");
assert_not_contains(&result, &["$fake"]);
}
#[test]
fn real_variable_alongside_string() {
let result = extract_variables(". as $real | \"$fake\" | $real");
assert_contains_all(&result, &["$real"]);
assert_not_contains(&result, &["$fake"]);
}
#[test]
fn escaped_quote_in_string() {
let result = extract_variables(r#""\\" as $x | $x"#);
assert_contains_all(&result, &["$x"]);
}
#[test]
fn as_within_identifier_not_matched() {
let result = extract_variables("has($x)");
assert_not_contains(&result, &["$x"]);
}
#[test]
fn empty_query() {
let result = extract_variables("");
assert_contains_all(&result, &["$ENV", "$__loc__"]);
assert_eq!(result.len(), 2);
}
#[test]
fn only_builtins_no_user_vars() {
let result = extract_variables(".foo | .bar");
assert_eq!(result.len(), 2);
assert_contains_all(&result, &["$ENV", "$__loc__"]);
}
#[test]
fn whitespace_after_as() {
let result = extract_variables(". as $spaced | $spaced");
assert_contains_all(&result, &["$spaced"]);
}
#[test]
fn complex_nested_expression() {
let result =
extract_variables("reduce (. as $outer | .[]) as $inner (0; . + $inner + $outer)");
assert_contains_all(&result, &["$outer", "$inner"]);
}
#[test]
fn map_with_variable() {
let result = extract_variables(".items as $data | map(. + $data)");
assert_contains_all(&result, &["$data"]);
}
}
mod extract_single_variable_tests {
use super::*;
#[test]
fn extracts_simple_variable() {
let chars: Vec<char> = "$foo".chars().collect();
let result = extract_single_variable(&chars, 0);
assert_eq!(result, Some(("$foo".to_string(), 4)));
}
#[test]
fn extracts_variable_with_underscore() {
let chars: Vec<char> = "$my_var".chars().collect();
let result = extract_single_variable(&chars, 0);
assert_eq!(result, Some(("$my_var".to_string(), 7)));
}
#[test]
fn extracts_variable_with_numbers() {
let chars: Vec<char> = "$x123".chars().collect();
let result = extract_single_variable(&chars, 0);
assert_eq!(result, Some(("$x123".to_string(), 5)));
}
#[test]
fn returns_none_for_empty_variable() {
let chars: Vec<char> = "$ ".chars().collect();
let result = extract_single_variable(&chars, 0);
assert_eq!(result, None);
}
#[test]
fn stops_at_delimiter() {
let chars: Vec<char> = "$foo | bar".chars().collect();
let result = extract_single_variable(&chars, 0);
assert_eq!(result, Some(("$foo".to_string(), 4)));
}
}
mod is_keyword_at_tests {
use super::*;
#[test]
fn matches_as_at_start() {
let chars: Vec<char> = "as $x".chars().collect();
assert!(is_keyword_at(&chars, 0, "as"));
}
#[test]
fn matches_as_after_space() {
let chars: Vec<char> = ". as $x".chars().collect();
assert!(is_keyword_at(&chars, 2, "as"));
}
#[test]
fn does_not_match_has() {
let chars: Vec<char> = "has($x)".chars().collect();
assert!(!is_keyword_at(&chars, 1, "as"));
}
#[test]
fn does_not_match_ask() {
let chars: Vec<char> = "ask".chars().collect();
assert!(!is_keyword_at(&chars, 0, "as"));
}
#[test]
fn matches_label() {
let chars: Vec<char> = "label $out".chars().collect();
assert!(is_keyword_at(&chars, 0, "label"));
}
}
mod extract_array_destructure_tests {
use super::*;
#[test]
fn extracts_simple_array() {
let chars: Vec<char> = "[$a, $b]".chars().collect();
let result = extract_array_destructure_variables(&chars, 0);
assert!(result.is_some());
let (vars, _) = result.unwrap();
assert_eq!(vars, vec!["$a", "$b"]);
}
#[test]
fn extracts_nested_array() {
let chars: Vec<char> = "[[$a], $b]".chars().collect();
let result = extract_array_destructure_variables(&chars, 0);
assert!(result.is_some());
let (vars, _) = result.unwrap();
assert_eq!(vars, vec!["$a", "$b"]);
}
#[test]
fn handles_whitespace() {
let chars: Vec<char> = "[ $a , $b ]".chars().collect();
let result = extract_array_destructure_variables(&chars, 0);
assert!(result.is_some());
let (vars, _) = result.unwrap();
assert_eq!(vars, vec!["$a", "$b"]);
}
}
mod extract_object_destructure_tests {
use super::*;
#[test]
fn extracts_simple_object() {
let chars: Vec<char> = "{name: $n, age: $a}".chars().collect();
let result = extract_object_destructure_variables(&chars, 0);
assert!(result.is_some());
let (vars, _) = result.unwrap();
assert_eq!(vars, vec!["$n", "$a"]);
}
#[test]
fn extracts_nested_object() {
let chars: Vec<char> = "{outer: {inner: $i}, other: $o}".chars().collect();
let result = extract_object_destructure_variables(&chars, 0);
assert!(result.is_some());
let (vars, _) = result.unwrap();
assert_eq!(vars, vec!["$i", "$o"]);
}
#[test]
fn returns_none_when_not_starting_with_brace() {
let chars: Vec<char> = "name: $n".chars().collect();
let result = extract_object_destructure_variables(&chars, 0);
assert_eq!(result, None);
}
#[test]
fn returns_none_when_position_out_of_bounds() {
let chars: Vec<char> = "{}".chars().collect();
let result = extract_object_destructure_variables(&chars, 10);
assert_eq!(result, None);
}
}
mod guard_check_tests {
use super::*;
#[test]
fn extract_single_variable_returns_none_for_non_dollar() {
let chars: Vec<char> = "foo".chars().collect();
let result = extract_single_variable(&chars, 0);
assert_eq!(result, None);
}
#[test]
fn extract_single_variable_returns_none_for_out_of_bounds() {
let chars: Vec<char> = "$x".chars().collect();
let result = extract_single_variable(&chars, 10);
assert_eq!(result, None);
}
#[test]
fn extract_array_destructure_returns_none_for_non_bracket() {
let chars: Vec<char> = "$x, $y".chars().collect();
let result = extract_array_destructure_variables(&chars, 0);
assert_eq!(result, None);
}
#[test]
fn extract_array_destructure_returns_none_for_out_of_bounds() {
let chars: Vec<char> = "[]".chars().collect();
let result = extract_array_destructure_variables(&chars, 10);
assert_eq!(result, None);
}
}
mod extract_variables_after_keyword_tests {
use super::*;
#[test]
fn returns_none_when_no_variable_after_as() {
let chars: Vec<char> = "as".chars().collect();
let result = extract_variables_after_keyword(&chars, 0);
assert_eq!(result, None);
}
#[test]
fn returns_none_when_no_variable_after_label() {
let chars: Vec<char> = "label".chars().collect();
let result = extract_variables_after_keyword(&chars, 0);
assert_eq!(result, None);
}
#[test]
fn returns_none_when_invalid_syntax_after_as() {
let chars: Vec<char> = "as 123".chars().collect();
let result = extract_variables_after_keyword(&chars, 0);
assert_eq!(result, None);
}
}