use super::*;
use proptest::prelude::*;
#[test]
fn test_empty_query() {
let mut tracker = BraceTracker::new();
tracker.rebuild("");
assert_eq!(tracker.context_at(0), None);
assert!(!tracker.is_in_object(0));
}
#[test]
fn test_simple_object() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{name");
assert_eq!(tracker.context_at(1), Some(BraceType::Curly));
assert!(tracker.is_in_object(1));
assert!(tracker.is_in_object(5));
}
#[test]
fn test_simple_array() {
let mut tracker = BraceTracker::new();
tracker.rebuild("[1, 2");
assert_eq!(tracker.context_at(1), Some(BraceType::Square));
assert!(!tracker.is_in_object(1));
}
#[test]
fn test_simple_paren() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map(");
assert_eq!(tracker.context_at(4), Some(BraceType::Paren));
assert!(!tracker.is_in_object(4));
}
#[test]
fn test_closed_braces() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{name: .name}");
assert_eq!(tracker.context_at(13), None);
}
#[test]
fn test_object_in_array() {
let mut tracker = BraceTracker::new();
tracker.rebuild("[{na");
assert_eq!(tracker.context_at(2), Some(BraceType::Curly));
assert!(tracker.is_in_object(2));
}
#[test]
fn test_array_in_object() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{items: [na");
assert_eq!(tracker.context_at(9), Some(BraceType::Square));
assert!(!tracker.is_in_object(9));
}
#[test]
fn test_deep_nesting() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{a: [{b: (c");
assert_eq!(tracker.context_at(10), Some(BraceType::Paren));
assert!(!tracker.is_in_object(10));
}
#[test]
fn test_braces_in_string() {
let mut tracker = BraceTracker::new();
tracker.rebuild("\"{braces}\"");
assert_eq!(tracker.context_at(5), None);
assert!(!tracker.is_in_object(5));
}
#[test]
fn test_escaped_quote_in_string() {
let mut tracker = BraceTracker::new();
tracker.rebuild("\"say \\\"hi\\\" {here\"");
assert_eq!(tracker.context_at(12), None);
}
#[test]
fn test_escaped_backslash_in_string() {
let mut tracker = BraceTracker::new();
tracker.rebuild("\"path\\\\{dir\"");
assert_eq!(tracker.context_at(8), None);
}
#[test]
fn test_string_then_real_braces() {
let mut tracker = BraceTracker::new();
tracker.rebuild("\"{fake}\" | {real");
assert_eq!(tracker.context_at(12), Some(BraceType::Curly));
assert!(tracker.is_in_object(12));
}
#[test]
fn test_object_key_after_comma() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{name: .name, ag");
assert!(tracker.is_in_object(14));
}
#[test]
fn test_real_jq_pattern_select() {
let mut tracker = BraceTracker::new();
tracker.rebuild("select(.active)");
assert_eq!(tracker.context_at(15), None);
}
#[test]
fn test_real_jq_pattern_map() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map({na");
assert!(tracker.is_in_object(5));
tracker.rebuild("map({name: .name})");
assert_eq!(tracker.context_at(18), None);
assert_eq!(tracker.context_at(5), None);
}
#[test]
fn test_mismatched_braces() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{test]");
assert!(tracker.is_in_object(5));
}
#[test]
fn test_unclosed_string() {
let mut tracker = BraceTracker::new();
tracker.rebuild("\"unclosed {");
assert_eq!(tracker.context_at(10), None);
}
#[test]
fn test_is_stale() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{test");
assert!(!tracker.is_stale("{test"));
assert!(tracker.is_stale("{test2"));
assert!(tracker.is_stale(""));
}
#[test]
fn test_context_at_position_zero() {
let mut tracker = BraceTracker::new();
tracker.rebuild("{test");
assert_eq!(tracker.context_at(0), None);
}
proptest! {
#[test]
fn prop_rebuild_never_panics(query in ".*") {
let mut tracker = BraceTracker::new();
tracker.rebuild(&query);
}
#[test]
fn prop_context_at_never_panics(query in ".*", pos in 0usize..1000) {
let mut tracker = BraceTracker::new();
tracker.rebuild(&query);
let _ = tracker.context_at(pos);
let _ = tracker.is_in_object(pos);
}
#[test]
fn prop_string_braces_ignored(
prefix in "[a-z .|]*",
string_content in "[a-z{}\\[\\]()]*",
suffix in "[a-z .|]*"
) {
let query_with_string_braces = format!("{}\"{}\"{}", prefix, string_content, suffix);
let query_with_empty_string = format!("{}\"\"{}", prefix, suffix);
let mut tracker1 = BraceTracker::new();
let mut tracker2 = BraceTracker::new();
tracker1.rebuild(&query_with_string_braces);
tracker2.rebuild(&query_with_empty_string);
let pos_after_string1 = prefix.len() + string_content.len() + 2; let pos_after_string2 = prefix.len() + 2;
prop_assert_eq!(
tracker1.context_at(pos_after_string1),
tracker2.context_at(pos_after_string2),
"Context after string should be same regardless of braces inside string"
);
}
#[test]
fn prop_is_in_object_consistent(query in ".*", pos in 0usize..500) {
let mut tracker = BraceTracker::new();
tracker.rebuild(&query);
let context = tracker.context_at(pos);
let is_object = tracker.is_in_object(pos);
prop_assert_eq!(
is_object,
context == Some(BraceType::Curly),
"is_in_object should match context_at == Curly"
);
}
#[test]
fn prop_element_context_functions_always_detected(
func in "(map|select|sort_by|group_by|unique_by|min_by|max_by|recurse|walk)",
partial in "[a-z]{0,5}"
) {
let query = format!("{}(.{}", func, partial);
let mut tracker = BraceTracker::new();
tracker.rebuild(&query);
prop_assert!(
tracker.is_in_element_context(query.len()),
"Should detect element context in '{}' at position {}",
query,
query.len()
);
}
#[test]
fn prop_non_element_functions_never_detected(
func in "(limit|has|del|getpath|split|join|test|match)",
partial in "[a-z]{0,5}"
) {
let query = format!("{}(.{}", func, partial);
let mut tracker = BraceTracker::new();
tracker.rebuild(&query);
prop_assert!(
!tracker.is_in_element_context(query.len()),
"Should NOT detect element context in '{}' at position {}",
query,
query.len()
);
}
}
#[test]
fn test_element_context_in_map() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map(.");
assert!(tracker.is_in_element_context(5));
}
#[test]
fn test_element_context_in_select() {
let mut tracker = BraceTracker::new();
tracker.rebuild("select(.");
assert!(tracker.is_in_element_context(8));
}
#[test]
fn test_element_context_all_element_functions() {
let functions = [
"map",
"select",
"sort_by",
"group_by",
"unique_by",
"min_by",
"max_by",
"recurse",
"walk",
];
for func in functions {
let query = format!("{}(.field", func);
let mut tracker = BraceTracker::new();
tracker.rebuild(&query);
assert!(
tracker.is_in_element_context(query.len()),
"Function '{}' should provide element context",
func
);
}
}
#[test]
fn test_no_element_context_outside_function() {
let mut tracker = BraceTracker::new();
tracker.rebuild(".field");
assert!(!tracker.is_in_element_context(6));
}
#[test]
fn test_no_element_context_in_limit() {
let mut tracker = BraceTracker::new();
tracker.rebuild("limit(5; .");
assert!(!tracker.is_in_element_context(10));
}
#[test]
fn test_no_element_context_in_has() {
let mut tracker = BraceTracker::new();
tracker.rebuild("has(.");
assert!(!tracker.is_in_element_context(5));
}
#[test]
fn test_element_context_nested_in_limit() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map(limit(5; .");
assert!(
tracker.is_in_element_context(14),
"Should detect element context from outer map even when inside limit"
);
}
#[test]
fn test_element_context_with_object_inside() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map({name: .");
assert!(tracker.is_in_element_context(12));
}
#[test]
fn test_element_context_with_array_inside() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map([.");
assert!(tracker.is_in_element_context(6));
}
#[test]
fn test_no_element_context_grouping_parens() {
let mut tracker = BraceTracker::new();
tracker.rebuild("(.x + .");
assert!(!tracker.is_in_element_context(7));
}
#[test]
fn test_element_context_after_pipe_in_function() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map(. | .");
assert!(tracker.is_in_element_context(10));
}
#[test]
fn test_element_context_string_with_paren() {
let mut tracker = BraceTracker::new();
tracker.rebuild("\"(\" | map(.");
assert!(tracker.is_in_element_context(11));
}
#[test]
fn test_element_context_closed_function() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map(.field) | .");
assert!(!tracker.is_in_element_context(15));
}
#[test]
fn test_element_context_nested_element_functions() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map(select(.");
assert!(tracker.is_in_element_context(12));
}
#[test]
fn test_element_context_whitespace_before_paren() {
let mut tracker = BraceTracker::new();
tracker.rebuild("map (.");
assert!(tracker.is_in_element_context(6));
}
#[test]
fn test_no_element_context_unknown_function() {
let mut tracker = BraceTracker::new();
tracker.rebuild("myfunc(.");
assert!(!tracker.is_in_element_context(8));
}
#[test]
fn test_function_context_enum_debug() {
let ctx = FunctionContext::ElementIterator("map");
assert!(format!("{:?}", ctx).contains("ElementIterator"));
assert!(format!("{:?}", ctx).contains("map"));
}
#[test]
fn test_brace_info_struct() {
let info = BraceInfo {
pos: 5,
brace_type: BraceType::Paren,
context: Some(FunctionContext::ElementIterator("select")),
};
assert_eq!(info.pos, 5);
assert_eq!(info.brace_type, BraceType::Paren);
assert!(matches!(
info.context,
Some(FunctionContext::ElementIterator("select"))
));
}