use super::*;
#[test]
fn test_array_suggestion_appends_to_path() {
let json = r#"{"services": [{"name": "alice"}, {"name": "bob"}, {"name": "charlie"}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services");
app.query.as_mut().unwrap().execute(".services");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services".to_string()),
"base_query should be '.services'"
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::ArrayOfObjects),
"base_type should be ArrayOfObjects"
);
insert_suggestion_from_app(&mut app, &test_suggestion("[].name"));
assert_eq!(app.input.query(), ".services[].name");
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("alice"), "Should contain first element");
assert!(result.contains("bob"), "Should contain second element");
assert!(result.contains("charlie"), "Should contain third element");
let line_count = result.lines().count();
assert!(
line_count >= 3,
"Should return at least 3 lines for 3 array elements"
);
}
#[test]
fn test_simple_path_continuation_with_dot() {
let json = r#"{"user": {"name": "Alice", "age": 30, "address": {"city": "NYC"}}}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".user");
app.query.as_mut().unwrap().execute(".user");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".user".to_string())
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::Object)
);
app.input.textarea.insert_str(".na");
insert_suggestion_from_app(&mut app, &test_suggestion("name"));
assert_eq!(app.input.query(), ".user.name");
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("Alice"));
}
#[test]
fn test_array_suggestion_replaces_partial_field() {
let json =
r#"{"services": [{"serviceArn": "arn1"}, {"serviceArn": "arn2"}, {"serviceArn": "arn3"}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services");
app.query.as_mut().unwrap().execute(".services");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services".to_string())
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::ArrayOfObjects)
);
app.input.textarea.insert_char('.');
app.input.textarea.insert_char('s');
insert_suggestion_from_app(&mut app, &test_suggestion("[].serviceArn"));
assert_eq!(app.input.query(), ".services[].serviceArn");
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("arn1"), "Should contain first serviceArn");
assert!(result.contains("arn2"), "Should contain second serviceArn");
assert!(result.contains("arn3"), "Should contain third serviceArn");
let null_count = result.matches("null").count();
assert_eq!(
null_count, 0,
"Should not have any null values - query should iterate all array elements"
);
}
#[test]
fn test_array_suggestion_replaces_trailing_dot() {
let json = r#"{"services": [{"deploymentConfiguration": {"x": 1}}, {"deploymentConfiguration": {"x": 2}}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services");
app.query.as_mut().unwrap().execute(".services");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services".to_string()),
"base_query should be '.services'"
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::ArrayOfObjects),
"base_type should be ArrayOfObjects"
);
app.input.textarea.insert_char('.');
insert_suggestion_from_app(&mut app, &test_suggestion("[].deploymentConfiguration"));
assert_eq!(app.input.query(), ".services[].deploymentConfiguration");
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("x"));
assert!(result.contains("1"));
assert!(result.contains("2"));
}
#[test]
fn test_nested_array_suggestion_replaces_trailing_dot() {
let json = r#"{"services": [{"capacityProviderStrategy": [{"base": 0, "weight": 1}]}]}"#;
let mut app = test_app(json);
app.input
.textarea
.insert_str(".services[].capacityProviderStrategy[]");
app.query
.as_mut()
.unwrap()
.execute(".services[].capacityProviderStrategy[]");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services[].capacityProviderStrategy[]".to_string())
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::Object)
);
app.input.textarea.insert_char('.');
insert_suggestion_from_app(&mut app, &test_suggestion("base"));
assert_eq!(
app.input.query(),
".services[].capacityProviderStrategy[].base"
);
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("0"));
}
#[test]
fn test_array_suggestion_after_pipe() {
let json = r#"{"services": [{"name": "svc1"}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services");
app.query.as_mut().unwrap().execute(".services");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services".to_string())
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::ArrayOfObjects)
);
app.input.textarea.insert_str(" | .");
insert_suggestion_from_app(&mut app, &test_suggestion(".[].name"));
assert_eq!(app.input.query(), ".services | .[].name");
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("svc1"));
}
#[test]
fn test_array_suggestion_after_pipe_exact_user_flow() {
let json = r#"{"services": [{"capacityProviderStrategy": [{"base": 0}]}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".ser");
insert_suggestion_from_app(&mut app, &test_suggestion(".services"));
assert_eq!(app.input.query(), ".services");
execute_query_and_wait(&mut app);
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services".to_string()),
"base should be '.services' after insertion executed it"
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::ArrayOfObjects)
);
app.input.textarea.insert_str(" | .");
insert_suggestion_from_app(&mut app, &test_suggestion(".[].capacityProviderStrategy"));
assert_eq!(
app.input.query(),
".services | .[].capacityProviderStrategy"
);
}
#[test]
fn test_pipe_after_typing_space() {
let json = r#"{"services": [{"name": "svc1"}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services");
app.query.as_mut().unwrap().execute(".services");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services".to_string())
);
app.input.textarea.insert_char(' ');
app.query.as_mut().unwrap().execute(".services ");
app.input.textarea.insert_char('|');
app.query.as_mut().unwrap().execute(".services |");
app.input.textarea.insert_str(" .");
insert_suggestion_from_app(&mut app, &test_suggestion(".[].name"));
assert_eq!(app.input.query(), ".services | .[].name");
}
#[test]
fn test_suggestions_persist_when_typing_partial_after_array() {
let json = r#"{"services": [{"capacityProviderStrategy": [{"base": 0, "weight": 1, "capacityProvider": "x"}]}]}"#;
let mut app = test_app(json);
app.input
.textarea
.insert_str(".services[].capacityProviderStrategy[]");
app.query
.as_mut()
.unwrap()
.execute(".services[].capacityProviderStrategy[]");
app.update_autocomplete();
assert!(
app.query
.as_ref()
.unwrap()
.last_successful_result_unformatted
.is_some()
);
let cached = app
.query
.as_ref()
.unwrap()
.last_successful_result_unformatted
.clone();
app.input.textarea.insert_char('.');
app.query
.as_mut()
.unwrap()
.execute(".services[].capacityProviderStrategy[].");
assert_eq!(
app.query
.as_ref()
.unwrap()
.last_successful_result_unformatted,
cached
);
app.input.textarea.insert_char('b');
app.query
.as_mut()
.unwrap()
.execute(".services[].capacityProviderStrategy[].b");
assert_eq!(
app.query
.as_ref()
.unwrap()
.last_successful_result_unformatted,
cached
);
app.update_autocomplete();
let suggestions = app.autocomplete.suggestions();
assert!(
!suggestions.is_empty(),
"Suggestions should persist when typing partial that returns null"
);
assert!(
suggestions.iter().any(|s| s.text.contains("base")),
"Should suggest 'base' field when filtering by 'b'"
);
}
#[test]
fn test_suggestions_persist_with_optional_chaining_and_partial() {
let json = r#"{
"services": [
{
"serviceName": "service1",
"capacityProviderStrategy": [
{"base": 0, "weight": 1, "capacityProvider": "FARGATE"},
{"base": 0, "weight": 2, "capacityProvider": "FARGATE_SPOT"}
]
},
{
"serviceName": "service2"
},
{
"serviceName": "service3",
"capacityProviderStrategy": [
{"base": 1, "weight": 3, "capacityProvider": "EC2"}
]
}
]
}"#;
let mut app = test_app(json);
app.input
.textarea
.insert_str(".services[].capacityProviderStrategy[]?");
app.query
.as_mut()
.unwrap()
.execute(".services[].capacityProviderStrategy[]?");
let cached_before_partial = app
.query
.as_ref()
.unwrap()
.last_successful_result_unformatted
.clone();
assert!(cached_before_partial.is_some());
assert!(cached_before_partial.as_ref().unwrap().contains("base"));
app.input.textarea.insert_char('.');
app.query
.as_mut()
.unwrap()
.execute(".services[].capacityProviderStrategy[]?.");
assert_eq!(
app.query
.as_ref()
.unwrap()
.last_successful_result_unformatted,
cached_before_partial
);
app.input.textarea.insert_char('b');
app.query
.as_mut()
.unwrap()
.execute(".services[].capacityProviderStrategy[]?.b");
assert_eq!(
app.query
.as_ref()
.unwrap()
.last_successful_result_unformatted,
cached_before_partial,
"Cache should not be overwritten by null result from partial field"
);
app.update_autocomplete();
let suggestions = app.autocomplete.suggestions();
assert!(
!suggestions.is_empty(),
"Suggestions should persist when typing partial after []?"
);
assert!(
suggestions.iter().any(|s| s.text.contains("base")),
"Should suggest 'base' field when filtering by 'b' after []?"
);
}
#[test]
fn test_field_access_after_jq_keyword_preserves_space() {
let json = r#"{"services": [{"capacityProviderStrategy": [{"base": 0}]}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services[]");
app.query.as_mut().unwrap().execute(".services[]");
app.input
.textarea
.insert_str(" | if has(\"capacityProviderStrategy\") then .ca");
insert_suggestion_from_app(&mut app, &test_suggestion(".capacityProviderStrategy"));
assert_eq!(
app.input.query(),
".services[] | if has(\"capacityProviderStrategy\") then .capacityProviderStrategy"
);
assert!(
app.input.query().contains("then .capacityProviderStrategy"),
"Should have space between 'then' and field name"
);
assert!(
!app.input.query().contains("thencapacityProviderStrategy"),
"Should NOT concatenate 'then' with field name"
);
}
#[test]
fn test_field_access_after_else_preserves_space() {
let json = r#"{"services": [{"name": "test"}]}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services[]");
app.query.as_mut().unwrap().execute(".services[]");
app.input
.textarea
.insert_str(" | if has(\"name\") then .name else .na");
insert_suggestion_from_app(&mut app, &test_suggestion(".name"));
assert!(
app.input.query().contains("else .name"),
"Should have space between 'else' and field name"
);
assert!(
!app.input.query().contains("elsename"),
"Should NOT concatenate 'else' with field name"
);
}
#[test]
fn test_autocomplete_inside_if_statement() {
let json = r#"{"services": [{"capacityProviderStrategy": [{"base": 0}]}]}"#;
let mut app = test_app(json);
app.input
.textarea
.insert_str(".services | if has(\"capacityProviderStrategy\") then .ca");
app.query
.as_mut()
.unwrap()
.execute(".services | if has(\"capacityProviderStrategy\") then .ca");
}
#[test]
fn test_root_field_suggestion() {
let json = r#"{"services": [{"name": "test"}], "status": "active"}"#;
let mut app = test_app(json);
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".".to_string()),
"base_query should be '.' initially"
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::Object),
"base_type should be Object"
);
app.input.textarea.insert_str(".");
insert_suggestion_from_app(&mut app, &test_suggestion(".services"));
assert_eq!(app.input.query(), ".services");
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("name"));
}
#[test]
fn test_field_suggestion_replaces_from_dot() {
let json = r#"{"name": "test", "age": 30}"#;
let mut app = test_app(json);
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".".to_string()),
"base_query should be '.' initially"
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::Object),
"base_type should be Object for root"
);
app.input.textarea.insert_str(".na");
insert_suggestion_from_app(&mut app, &test_suggestion("name"));
assert_eq!(app.input.query(), ".name");
}
#[test]
fn test_autocomplete_with_real_ecs_like_data() {
let json = r#"{
"services": [
{"serviceArn": "arn:aws:ecs:region:account:service/cluster/svc1", "serviceName": "service1"},
{"serviceArn": "arn:aws:ecs:region:account:service/cluster/svc2", "serviceName": "service2"},
{"serviceArn": "arn:aws:ecs:region:account:service/cluster/svc3", "serviceName": "service3"},
{"serviceArn": "arn:aws:ecs:region:account:service/cluster/svc4", "serviceName": "service4"},
{"serviceArn": "arn:aws:ecs:region:account:service/cluster/svc5", "serviceName": "service5"}
]
}"#;
let mut app = test_app(json);
app.input.textarea.insert_str(".services");
app.query.as_mut().unwrap().execute(".services");
assert_eq!(
app.query.as_ref().unwrap().base_query_for_suggestions,
Some(".services".to_string()),
"base_query should be '.services'"
);
assert_eq!(
app.query.as_ref().unwrap().base_type_for_suggestions,
Some(ResultType::ArrayOfObjects),
"base_type should be ArrayOfObjects"
);
app.input.textarea.insert_str(".s");
insert_suggestion_from_app(&mut app, &test_suggestion("[].serviceArn"));
let query_text = app.input.query();
assert_eq!(query_text, ".services[].serviceArn");
let result = app.query.as_ref().unwrap().result.as_ref().unwrap();
assert!(result.contains("svc1"));
assert!(result.contains("svc2"));
assert!(result.contains("svc3"));
assert!(result.contains("svc4"));
assert!(result.contains("svc5"));
let lines: Vec<&str> = result.lines().collect();
let non_null_lines: Vec<&str> = lines
.iter()
.filter(|line| !line.trim().contains("null"))
.copied()
.collect();
assert!(
non_null_lines.len() >= 5,
"Should have at least 5 non-null results, got {}",
non_null_lines.len()
);
}