use cargo_brief::cli::{
ApiArgs, BriefDirect, CodeArgs, ExamplesArgs, FeaturesArgs, FilterArgs, GlobalArgs, RemoteOpts,
SearchArgs, SummaryArgs, TargetArgs, TsArgs,
};
use cargo_brief::model::{CrateModel, compute_reachable_set};
use cargo_brief::render::{render_leaf_item, render_leaf_not_found, render_module_api};
use cargo_brief::resolve;
use cargo_brief::rustdoc_json;
use cargo_brief::search;
use cargo_brief::summary;
use clap::CommandFactory;
fn fixture_model() -> CrateModel {
let metadata = resolve::load_cargo_metadata(Some("test_fixture/Cargo.toml"))
.expect("Failed to load cargo metadata");
let json_path = rustdoc_json::generate_rustdoc_json(
"test-fixture",
"nightly",
Some("test_fixture/Cargo.toml"),
true,
&metadata.target_dir,
false,
false, )
.expect("Failed to generate rustdoc JSON for test fixture");
let krate =
rustdoc_json::parse_rustdoc_json(&json_path).expect("Failed to parse test fixture JSON");
CrateModel::from_crate(krate)
}
#[test]
fn test_generate_rustdoc_json_finds_renamed_lib_target_json() {
let metadata = resolve::load_cargo_metadata(Some("test_fixture/Cargo.toml"))
.expect("Failed to load cargo metadata");
let doc_dir = metadata.target_dir.join("doc");
let package_named_json = doc_dir.join("renamed_lib_package.json");
let lib_named_json = doc_dir.join("renamed_lib_actual.json");
let _ = std::fs::remove_file(&package_named_json);
let _ = std::fs::remove_file(&lib_named_json);
std::fs::create_dir_all(&doc_dir).expect("Failed to create rustdoc output directory");
std::fs::write(&package_named_json, "{}").expect("Failed to write stale package-derived JSON");
let json_path = rustdoc_json::generate_rustdoc_json(
"renamed-lib-package",
"nightly",
Some("test_fixture/Cargo.toml"),
true,
&metadata.target_dir,
false,
false,
)
.expect("Failed to generate rustdoc JSON for renamed lib fixture");
assert_eq!(
json_path.file_name().and_then(|name| name.to_str()),
Some("renamed_lib_actual.json")
);
assert!(lib_named_json.exists());
assert!(
package_named_json.exists(),
"test fixture should leave a stale package-derived JSON in place"
);
let cached_path = rustdoc_json::generate_rustdoc_json(
"renamed-lib-package",
"definitely-not-a-real-toolchain",
Some("test_fixture/Cargo.toml"),
true,
&metadata.target_dir,
false,
true,
)
.expect("Failed to find cached rustdoc JSON for renamed lib fixture");
assert_eq!(cached_path, lib_named_json);
}
fn default_filter() -> FilterArgs {
FilterArgs {
no_structs: false,
no_enums: false,
no_traits: false,
no_functions: false,
no_aliases: false,
no_constants: false,
no_unions: false,
no_macros: false,
no_docs: false,
no_crate_docs: false,
doc_lines: None,
compact: false,
verbose_metadata: false,
all: false,
no_feature_gates: false,
}
}
fn default_args() -> ApiArgs {
ApiArgs {
target: TargetArgs {
crate_name: "test-fixture".to_string(),
module_path: None,
at_package: None,
at_mod: None,
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
},
filter: default_filter(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
depth: 1,
recursive: true,
no_expand_glob: false,
}
}
fn render_full(model: &CrateModel, args: &ApiArgs) -> String {
render_module_api(
model,
args.target.module_path.as_deref(),
args,
None,
true,
None,
)
}
fn render_module(model: &CrateModel, args: &ApiArgs, module: &str) -> String {
render_module_api(model, Some(module), args, None, true, None)
}
#[test]
fn test_struct_fields_visible_same_crate() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub struct PubStruct"),
"PubStruct should appear"
);
assert!(output.contains("pub pub_field: i32"), "pub field visible");
assert!(
output.contains("pub(crate) crate_field: i32"),
"pub(crate) field visible in same crate"
);
}
#[test]
fn test_struct_private_struct_hidden() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
!output.contains("PrivateStruct"),
"PrivateStruct should be hidden"
);
}
#[test]
fn test_struct_external_crate_view() {
let model = fixture_model();
let args = default_args();
let output = render_module_api(
&model, None, &args, None, false, None, );
assert!(
output.contains("pub struct PubStruct"),
"PubStruct visible externally"
);
assert!(
output.contains("pub pub_field: i32"),
"pub field visible externally"
);
assert!(
!output.contains("crate_field"),
"pub(crate) field hidden externally"
);
assert!(
!output.contains("CrateStruct"),
"CrateStruct hidden externally"
);
assert!(
!output.contains("SuperStruct"),
"SuperStruct hidden externally"
);
}
#[test]
fn test_plain_enum() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub enum PlainEnum"),
"PlainEnum should appear"
);
assert!(output.contains("Alpha,"), "Alpha variant");
assert!(output.contains("Beta,"), "Beta variant");
assert!(output.contains("Gamma,"), "Gamma variant");
}
#[test]
fn test_tuple_enum() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub enum TupleEnum"),
"TupleEnum should appear"
);
assert!(output.contains("One(i32)"), "tuple variant with one field");
assert!(
output.contains("Two(String, bool)"),
"tuple variant with two fields"
);
assert!(output.contains("Empty,"), "plain variant in tuple enum");
}
#[test]
fn test_struct_enum() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub enum StructEnum"),
"StructEnum should appear"
);
assert!(output.contains("x: f64"), "struct variant field x");
assert!(output.contains("y: f64"), "struct variant field y");
assert!(output.contains("name: String"), "struct variant field name");
}
#[test]
fn test_free_functions() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub fn free_function(x: i32, y: i32) -> i32;"),
"regular function"
);
assert!(
output.contains("pub async fn async_function()"),
"async function"
);
assert!(
output.contains("pub const fn const_function(x: u32) -> u32;"),
"const function"
);
assert!(
output.contains("pub unsafe fn unsafe_function(ptr: *const u8) -> u8;"),
"unsafe function"
);
}
#[test]
fn test_generic_struct() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub struct GenericStruct<T: Clone, U = ()>"),
"generic struct with bounds and default"
);
assert!(output.contains("pub value: T"), "generic field T");
assert!(output.contains("pub extra: U"), "generic field U");
}
#[test]
fn test_generic_trait() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub trait GenericTrait<T: Send + Sync>: Clone"),
"generic trait with bounds"
);
assert!(output.contains("type Output;"), "associated type in trait");
assert!(
output.contains("fn process(&self, input: T)"),
"generic method"
);
}
#[test]
fn test_generic_function() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains(
"pub fn generic_function<T: std::fmt::Debug + Clone>(items: &[T]) -> Vec<T>;"
),
"generic function"
);
}
#[test]
fn test_trait_definition() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(output.contains("pub trait MyTrait"), "MyTrait definition");
assert!(
output.contains("fn do_thing(&self) -> bool;"),
"trait method"
);
}
#[test]
fn test_trait_impl() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("MyTrait"),
"MyTrait should appear in summary comment:\n{output}"
);
assert!(
output.contains("impl Converter for PubStruct"),
"rich trait impl should remain expanded:\n{output}"
);
}
#[test]
fn test_constant() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(output.contains("pub const MY_CONST: i32"), "constant");
}
#[test]
fn test_static() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub static GLOBAL_COUNT:"),
"static variable"
);
assert!(
output.contains("pub static mut MUTABLE_GLOBAL: i32"),
"mutable static"
);
}
#[test]
fn test_macro() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("macro_rules! my_macro"),
"macro_rules definition"
);
}
#[test]
fn test_union() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(output.contains("pub union MyUnion"), "union definition");
assert!(output.contains("pub int_val: i32"), "union field int_val");
assert!(
output.contains("pub float_val: f32"),
"union field float_val"
);
}
#[test]
fn test_reexport() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub use outer::PubStruct as ReExported; // struct"),
"re-export with alias and kind annotation"
);
}
#[test]
fn test_doc_comments_preserved() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("/// A documented trait."),
"trait doc comment"
);
assert!(output.contains("/// Trait method."), "method doc comment");
assert!(
output.contains("/// A plain enum (C-like)."),
"enum doc comment"
);
assert!(
output.contains("/// A regular public function."),
"function doc comment"
);
assert!(
output.contains("/// A generic struct."),
"struct doc comment"
);
assert!(
output.contains("/// A static variable."),
"static doc comment"
);
assert!(output.contains("/// A union type."), "union doc comment");
}
#[test]
fn test_depth_zero_shows_collapsed_modules() {
let model = fixture_model();
let mut args = default_args();
args.recursive = false;
args.depth = 0;
let output = render_full(&model, &args);
assert!(
output.contains("mod outer { /* ... */ }"),
"module collapsed at depth 0"
);
assert!(
!output.contains("pub struct PubStruct"),
"PubStruct hidden at depth 0"
);
}
#[test]
fn test_depth_one_shows_outer_but_inner_collapsed() {
let model = fixture_model();
let mut args = default_args();
args.recursive = false;
args.depth = 1;
let output = render_full(&model, &args);
assert!(
output.contains("pub struct PubStruct"),
"PubStruct at depth 1"
);
assert!(
output.contains("mod inner { /* ... */ }"),
"inner module collapsed at depth 1"
);
}
#[test]
fn test_no_structs_flag() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_structs = true;
let output = render_full(&model, &args);
assert!(
!output.contains("pub struct PubStruct"),
"structs filtered out"
);
assert!(output.contains("pub enum PlainEnum"), "enums still shown");
}
#[test]
fn test_no_enums_flag() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_enums = true;
let output = render_full(&model, &args);
assert!(!output.contains("pub enum PlainEnum"), "enums filtered out");
assert!(
output.contains("pub struct PubStruct"),
"structs still shown"
);
}
#[test]
fn test_no_functions_flag() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_functions = true;
let output = render_full(&model, &args);
assert!(
!output.contains("pub fn free_function"),
"functions filtered out"
);
assert!(
output.contains("pub struct PubStruct"),
"structs still shown"
);
}
#[test]
fn test_no_traits_flag() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_traits = true;
let output = render_full(&model, &args);
assert!(!output.contains("pub trait MyTrait"), "traits filtered out");
}
#[test]
fn test_no_constants_flag() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_constants = true;
let output = render_full(&model, &args);
assert!(
!output.contains("pub const MY_CONST"),
"constants filtered out"
);
assert!(
!output.contains("pub static GLOBAL_COUNT"),
"statics also filtered by no_constants"
);
}
#[test]
fn test_no_macros_flag() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_macros = true;
let output = render_full(&model, &args);
assert!(
!output.contains("macro_rules! my_macro"),
"macros filtered out"
);
}
#[test]
fn test_no_unions_flag() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_unions = true;
let output = render_full(&model, &args);
assert!(!output.contains("pub union MyUnion"), "unions filtered out");
}
#[test]
fn test_target_module_outer() {
let model = fixture_model();
let args = default_args();
let output = render_module(&model, &args, "outer");
assert!(
output.contains("pub struct PubStruct"),
"PubStruct in outer module"
);
assert!(
!output.contains("pub use outer::PubStruct as ReExported"),
"re-export is in root, not outer"
);
}
#[test]
fn test_target_module_inner() {
let model = fixture_model();
let args = default_args();
let output = render_module(&model, &args, "outer::inner");
assert!(
output.contains("pub struct InnerPub"),
"InnerPub in inner module"
);
assert!(
!output.contains("pub struct PubStruct"),
"PubStruct not in inner"
);
}
#[test]
fn test_same_crate_visibility() {
let model = fixture_model();
let args = default_args();
let output = render_module_api(&model, None, &args, None, true, None);
assert!(
output.contains("pub(crate) struct CrateStruct"),
"CrateStruct visible in same crate"
);
}
#[test]
fn test_external_visibility_hides_crate_items() {
let model = fixture_model();
let args = default_args();
let output = render_module_api(&model, None, &args, None, false, None);
assert!(
!output.contains("CrateStruct"),
"CrateStruct hidden externally"
);
assert!(
!output.contains("crate_method"),
"crate_method hidden externally"
);
assert!(
output.contains("pub fn pub_method"),
"pub_method visible externally"
);
}
#[test]
fn test_crate_header() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.starts_with("// crate test_fixture\n"),
"crate header"
);
}
#[test]
fn test_inherent_impl() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(output.contains("impl PubStruct {"), "inherent impl block");
assert!(
output.contains("pub fn pub_method(&self) -> i32;"),
"method in impl block"
);
}
#[test]
fn test_simple_trait_impl_collapsed_to_summary() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
!output.contains("impl MyTrait for PubStruct { .. }"),
"simple trait impl should NOT appear as individual line:\n{output}"
);
let summary_line = output
.lines()
.find(|l| l.contains("// PubStruct:") && l.contains("MyTrait"));
assert!(
summary_line.is_some(),
"PubStruct summary should mention MyTrait:\n{output}"
);
}
#[test]
fn test_trait_impl_with_assoc_type_shows_type() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("impl Converter for PubStruct {"),
"trait impl with assoc type should have braces"
);
assert!(
output.contains("type Output = String;"),
"associated type should be shown"
);
assert!(
!output.contains("fn convert(&self) -> String;"),
"methods should be omitted in trait impl with assoc type"
);
}
#[test]
fn test_root_items_no_indent() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
let lines: Vec<&str> = output.lines().collect();
let mod_line = lines.iter().find(|l| l.contains("mod outer")).unwrap();
assert!(
mod_line.starts_with("mod outer"),
"top-level module should have no indent, got: '{mod_line}'"
);
}
fn search_output(model: &CrateModel, pattern: &str) -> String {
let filter = default_filter();
search::render_search(model, pattern, &filter, None, None, true, None)
}
#[test]
fn search_finds_struct_by_name() {
let model = fixture_model();
let output = search_output(&model, "PubStruct");
assert!(
output.contains("struct outer::PubStruct"),
"Should find PubStruct:\n{output}"
);
}
#[test]
fn search_finds_struct_field() {
let model = fixture_model();
let output = search_output(&model, "pub_field");
assert!(
output.contains("field outer::PubStruct::pub_field: i32"),
"Should find pub_field:\n{output}"
);
}
#[test]
fn search_finds_enum_variant() {
let model = fixture_model();
let output = search_output(&model, "Alpha");
assert!(
output.contains("variant outer::PlainEnum::Alpha"),
"Should find Alpha variant:\n{output}"
);
}
#[test]
fn search_finds_tuple_variant() {
let model = fixture_model();
let output = search_output(&model, "TupleEnum One");
assert!(
output.contains("variant outer::TupleEnum::One"),
"Should find One variant with multi-word AND:\n{output}"
);
}
#[test]
fn search_finds_struct_variant() {
let model = fixture_model();
let output = search_output(&model, "Point");
assert!(
output.contains("variant outer::StructEnum::Point"),
"Should find Point variant:\n{output}"
);
assert!(
output.contains("x: f64"),
"Point variant should show fields:\n{output}"
);
}
#[test]
fn search_finds_method() {
let model = fixture_model();
let output = search_output(&model, "pub_method");
assert!(
output.contains("fn outer::PubStruct::pub_method"),
"Should find pub_method:\n{output}"
);
}
#[test]
fn search_finds_free_function() {
let model = fixture_model();
let output = search_output(&model, "free_function");
assert!(
output.contains("fn outer::free_function"),
"Should find free_function:\n{output}"
);
}
#[test]
fn search_finds_trait() {
let model = fixture_model();
let output = search_output(&model, "MyTrait");
assert!(
output.contains("trait outer::MyTrait"),
"Should find MyTrait:\n{output}"
);
}
#[test]
fn search_finds_trait_method() {
let model = fixture_model();
let output = search_output(&model, "do_thing");
assert!(
output.contains("fn outer::MyTrait::do_thing"),
"Should find trait method do_thing:\n{output}"
);
}
#[test]
fn search_finds_constant() {
let model = fixture_model();
let output = search_output(&model, "MY_CONST");
assert!(
output.contains("const outer::MY_CONST"),
"Should find MY_CONST:\n{output}"
);
}
#[test]
fn search_finds_static() {
let model = fixture_model();
let output = search_output(&model, "GLOBAL_COUNT");
assert!(
output.contains("static outer::GLOBAL_COUNT"),
"Should find GLOBAL_COUNT:\n{output}"
);
}
#[test]
fn search_finds_type_alias() {
let model = fixture_model();
let output = search_output(&model, "Alias");
assert!(
output.contains("type outer::Alias"),
"Should find type alias:\n{output}"
);
}
#[test]
fn search_finds_union() {
let model = fixture_model();
let output = search_output(&model, "MyUnion");
assert!(
output.contains("union outer::MyUnion"),
"Should find MyUnion:\n{output}"
);
}
#[test]
fn search_finds_union_field() {
let model = fixture_model();
let output = search_output(&model, "int_val");
assert!(
output.contains("field outer::MyUnion::int_val"),
"Should find union field:\n{output}"
);
}
#[test]
fn search_finds_macro() {
let model = fixture_model();
let output = search_output(&model, "my_macro");
assert!(
output.contains("macro my_macro!"),
"Should find macro:\n{output}"
);
}
#[test]
fn search_case_insensitive() {
let model = fixture_model();
let output = search_output(&model, "pubstruct");
assert!(
output.contains("struct outer::PubStruct"),
"Case-insensitive search should find PubStruct:\n{output}"
);
}
#[test]
fn search_multi_word_and() {
let model = fixture_model();
let output = search_output(&model, "outer pub_method");
assert!(
output.contains("pub_method"),
"Multi-word AND should match method by exact name:\n{output}"
);
assert!(
!output.contains("free_function"),
"free_function doesn't match 'pub_method':\n{output}"
);
}
#[test]
fn search_no_functions_excludes_methods() {
let model = fixture_model();
let mut filter = default_filter();
filter.no_functions = true;
let output = search::render_search(&model, "pub_method", &filter, None, None, true, None);
assert!(
!output.contains("fn "),
"--no-functions should exclude methods:\n{output}"
);
}
#[test]
fn search_result_count_in_header() {
let model = fixture_model();
let output = search_output(&model, "Alpha");
assert!(
output.contains("(2 results)"),
"Header should show result count:\n{output}"
);
}
#[test]
fn search_shows_doc_comment() {
let model = fixture_model();
let output = search_output(&model, "free_function");
assert!(
output.contains("/// A regular public function."),
"Should show first line of doc comment:\n{output}"
);
}
#[test]
fn search_associated_type() {
let model = fixture_model();
let output = search_output(&model, "Converter Output");
assert!(
output.contains("type outer::Converter::Output"),
"Should find trait associated type:\n{output}"
);
}
#[test]
fn search_results_sorted_by_kind_then_alpha() {
let model = fixture_model();
let output = search_output(&model, "outer");
let lines: Vec<&str> = output
.lines()
.filter(|l| !l.starts_with("//") && !l.starts_with("///"))
.collect();
let first_fn = lines.iter().position(|l| l.starts_with("fn "));
let first_struct = lines.iter().position(|l| l.starts_with("struct "));
let first_enum = lines.iter().position(|l| l.starts_with("enum "));
let first_field = lines.iter().position(|l| l.starts_with("field "));
let first_variant = lines.iter().position(|l| l.starts_with("variant "));
if let (Some(f), Some(s)) = (first_fn, first_struct) {
assert!(f < s, "functions should come before structs:\n{output}");
}
if let (Some(s), Some(e)) = (first_struct, first_enum) {
assert!(s < e, "structs should come before enums:\n{output}");
}
if let (Some(e), Some(f)) = (first_enum, first_field) {
assert!(e < f, "enums should come before fields:\n{output}");
}
if let (Some(f), Some(v)) = (first_field, first_variant) {
assert!(f < v, "fields should come before variants:\n{output}");
}
}
#[test]
fn search_results_alpha_within_kind() {
let model = fixture_model();
let output = search_output(&model, "Enum");
let enum_lines: Vec<&str> = output.lines().filter(|l| l.starts_with("enum ")).collect();
if enum_lines.len() >= 2 {
for pair in enum_lines.windows(2) {
assert!(
pair[0] <= pair[1],
"enums should be alphabetically sorted: {:?} vs {:?}",
pair[0],
pair[1]
);
}
}
}
#[test]
fn search_finds_reexport() {
let model = fixture_model();
let output = search_output(&model, "ReExported");
assert!(
output.contains("use outer::PubStruct as ReExported"),
"Should find re-export with alias:\n{output}"
);
}
#[test]
fn search_limit_truncates() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "outer", &filter, Some("3"), None, true, None);
let result_lines: Vec<&str> = output
.lines()
.filter(|l| !l.starts_with("//") && !l.starts_with("///"))
.collect();
assert!(
result_lines.len() <= 3,
"Should display at most 3 results, got {}:\n{output}",
result_lines.len()
);
assert!(
output.contains("// ... and "),
"Should show truncation message:\n{output}"
);
assert!(
output.contains(" more results"),
"Should show 'more results' suffix:\n{output}"
);
}
#[test]
fn search_limit_none_shows_all() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "outer", &filter, None, None, true, None);
assert!(
!output.contains("// ... and "),
"No truncation without limit:\n{output}"
);
}
#[test]
fn search_limit_paging() {
let model = fixture_model();
let filter = default_filter();
let full_output = search::render_search(&model, "outer", &filter, None, None, true, None);
let full_lines: Vec<&str> = full_output
.lines()
.filter(|l| !l.starts_with("//") && !l.starts_with("///"))
.collect();
let total = full_lines.len();
assert!(total > 5, "Need enough results to test paging, got {total}");
let output = search::render_search(&model, "outer", &filter, Some("2:3"), None, true, None);
let result_lines: Vec<&str> = output
.lines()
.filter(|l| !l.starts_with("//") && !l.starts_with("///"))
.collect();
assert_eq!(
result_lines.len(),
3,
"Should display exactly 3 results:\n{output}"
);
assert!(
output.contains("// (skipped 2 results)"),
"Should show skipped message:\n{output}"
);
assert!(
output.contains("// ... and "),
"Should show trailing truncation:\n{output}"
);
for (i, line) in result_lines.iter().enumerate() {
assert_eq!(
*line,
full_lines[2 + i],
"Paged result {i} should match full result at index {}",
2 + i
);
}
}
#[test]
fn no_docs_suppresses_doc_comments() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_docs = true;
let output = render_full(&model, &args);
assert!(
!output.contains("///"),
"--no-docs should suppress all doc comments:\n{output}"
);
assert!(
output.contains("pub struct PubStruct"),
"PubStruct should still appear with --no-docs"
);
}
#[test]
fn no_docs_search_suppresses_doc_comments() {
let model = fixture_model();
let mut filter = default_filter();
filter.no_docs = true;
let output = search::render_search(&model, "free_function", &filter, None, None, true, None);
assert!(
!output.contains("///"),
"--no-docs should suppress doc comments in search:\n{output}"
);
assert!(
output.contains("fn outer::free_function"),
"function should still appear:\n{output}"
);
}
#[test]
fn test_doc_lines_limits_output() {
let model = fixture_model();
let mut args = default_args();
args.filter.doc_lines = Some(1);
let output = render_full(&model, &args);
assert!(
output.contains("///"),
"--doc-lines 1 should show first doc line:\n{output}"
);
assert!(
output.contains("/// A generic struct."),
"first doc line should appear:\n{output}"
);
}
#[test]
fn test_doc_lines_zero_suppresses_docs() {
let model = fixture_model();
let mut args = default_args();
args.filter.doc_lines = Some(0);
let output = render_full(&model, &args);
assert!(
!output.contains("///"),
"--doc-lines 0 should suppress all doc comments:\n{output}"
);
assert!(
output.contains("pub struct PubStruct"),
"items should still appear:\n{output}"
);
}
#[test]
fn compact_struct_collapsed() {
let model = fixture_model();
let mut args = default_args();
args.filter.compact = true;
let output = render_full(&model, &args);
assert!(
output.contains("pub struct PubStruct { .. }"),
"compact should collapse struct fields:\n{output}"
);
assert!(
!output.contains("pub_field: i32"),
"compact should hide field details:\n{output}"
);
}
#[test]
fn compact_enum_variants_name_only() {
let model = fixture_model();
let mut args = default_args();
args.filter.compact = true;
let output = render_full(&model, &args);
assert!(
output.contains("Alpha, Beta, Gamma"),
"compact enum should show variant names:\n{output}"
);
}
#[test]
fn compact_trait_collapsed() {
let model = fixture_model();
let mut args = default_args();
args.filter.compact = true;
let output = render_full(&model, &args);
assert!(
output.contains("pub trait MyTrait { .. }"),
"compact should collapse trait:\n{output}"
);
assert!(
!output.contains("fn do_thing"),
"compact should hide trait methods:\n{output}"
);
}
#[test]
fn compact_impl_collapsed() {
let model = fixture_model();
let mut args = default_args();
args.filter.compact = true;
let output = render_full(&model, &args);
assert!(
output.contains("impl PubStruct { .. }"),
"compact should collapse inherent impl:\n{output}"
);
assert!(
!output.contains("pub fn pub_method"),
"compact should hide impl methods:\n{output}"
);
}
#[test]
fn compact_suppresses_docs() {
let model = fixture_model();
let mut args = default_args();
args.filter.compact = true;
let output = render_full(&model, &args);
assert!(
!output.contains("///"),
"compact implies no_docs:\n{output}"
);
}
#[test]
fn search_struct_shows_impl_summary() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "PubStruct", &filter, None, None, true, None);
assert!(
output.contains("// impl"),
"search should show impl summary for struct:\n{output}"
);
}
#[test]
fn methods_of_shows_only_methods_and_fields() {
let model = fixture_model();
let mut filter = default_filter();
filter.no_structs = true;
filter.no_enums = true;
filter.no_traits = true;
filter.no_unions = true;
filter.no_constants = true;
filter.no_macros = true;
filter.no_aliases = true;
let output = search::render_search_methods_of(
&model,
"PubStruct",
&filter,
None,
None,
true,
None,
"PubStruct",
);
assert!(
output.contains("pub_method"),
"should show methods:\n{output}"
);
assert!(
output.contains("pub_field"),
"should show fields:\n{output}"
);
assert!(
!output.contains("struct outer::PubStruct"),
"should not show struct definition:\n{output}"
);
assert!(
!output.contains("enum "),
"should not show enums:\n{output}"
);
assert!(
!output.contains("trait "),
"should not show traits:\n{output}"
);
}
#[test]
fn test_where_fn() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("where\n"),
"where_fn should have a where clause:\n{output}"
);
assert!(
output.contains("T: std::fmt::Display + Clone"),
"where_fn should show T bound:\n{output}"
);
assert!(
output.contains("U: Into<String>"),
"where_fn should show U bound:\n{output}"
);
}
#[test]
fn test_multi_where() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("T: std::fmt::Display"),
"multi_where should show T bound:\n{output}"
);
assert!(
output.contains("U: std::fmt::Debug + Clone"),
"multi_where should show U bound:\n{output}"
);
assert!(
output.contains("V: Into<String> + Send"),
"multi_where should show V bound:\n{output}"
);
}
#[test]
fn test_lifetime_where() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("'b: 'a"),
"lifetime_where should show lifetime bound:\n{output}"
);
}
#[test]
fn test_where_struct() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub struct WhereStruct<T> where T: std::fmt::Debug"),
"WhereStruct should have where clause:\n{output}"
);
}
#[test]
fn test_where_trait() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("pub trait WhereTrait<T> where T: Clone"),
"WhereTrait should have where clause:\n{output}"
);
}
#[test]
fn test_where_type_alias() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("type WhereAlias<T> where T: Ord"),
"WhereAlias should have where clause:\n{output}"
);
}
#[test]
fn test_where_impl_block() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("impl<T> WhereStruct<T> where T: std::fmt::Debug + Clone"),
"impl block for WhereStruct should have where clause:\n{output}"
);
}
#[test]
fn test_where_search_mode() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "where_fn", &filter, None, None, true, None);
assert!(
output.contains("where T: std::fmt::Display + Clone, U: Into<String>"),
"search mode should show compact where clause:\n{output}"
);
}
#[test]
fn test_impl_trait_resugared() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("impl_trait_fn(val: impl"),
"impl Trait should be re-sugared in parameter:\n{output}"
);
assert!(
!output.contains("impl_trait_fn<impl"),
"synthetic generic should not appear in generics:\n{output}"
);
}
#[test]
fn test_multi_impl_trait_resugared() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("a: impl") && output.contains("b: impl"),
"multiple impl Trait params should be re-sugared:\n{output}"
);
}
#[test]
fn test_impl_trait_search_mode() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "impl_trait_fn", &filter, None, None, true, None);
assert!(
output.contains("val: impl"),
"search mode should show re-sugared impl Trait:\n{output}"
);
assert!(
!output.contains("<impl"),
"search mode should not show synthetic generics:\n{output}"
);
}
#[test]
fn test_qualified_path_no_empty_trait() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
!output.contains("as >::"),
"Should not have empty trait path in qualified types:\n{output}"
);
}
#[test]
fn test_deprecated_function_attr() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("#[deprecated(since = \"0.1.0\", note = \"use new_function instead\")]"),
"deprecated function should show full #[deprecated(...)]:\n{output}"
);
}
#[test]
fn test_deprecated_struct_attr() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("#[deprecated = \"old struct\"]"),
"deprecated struct should show #[deprecated = \"...\"]:\n{output}"
);
}
#[test]
fn test_non_exhaustive_enum_attr() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("#[non_exhaustive]"),
"non-exhaustive enum should show #[non_exhaustive]:\n{output}"
);
}
#[test]
fn test_verbose_metadata_shows_repr() {
let model = fixture_model();
let mut args = default_args();
args.filter.verbose_metadata = true;
let output = render_full(&model, &args);
assert!(
output.contains("#[repr(C)]"),
"--verbose-metadata should show #[repr(C)] on MyUnion:\n{output}"
);
}
#[test]
fn test_default_hides_repr() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
!output.contains("#[repr(C)]"),
"default mode should NOT show #[repr(C)]:\n{output}"
);
}
#[test]
fn test_search_deprecated_marker() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(
&model,
"deprecated_function",
&filter,
None,
None,
true,
None,
);
assert!(
output.contains("[deprecated]"),
"search should show [deprecated] marker:\n{output}"
);
}
#[test]
fn test_search_non_exhaustive_marker() {
let model = fixture_model();
let filter = default_filter();
let output =
search::render_search(&model, "NonExhaustiveEnum", &filter, None, None, true, None);
assert!(
output.contains("[non_exhaustive]"),
"search should show [non_exhaustive] marker:\n{output}"
);
}
#[test]
fn test_crate_docs_rendered() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("//! Test fixture crate for cargo-brief integration tests."),
"crate-level docs should appear in output:\n{output}"
);
assert!(
output.contains("//!\n"),
"empty doc line should render as bare //!:\n{output}"
);
assert!(
output.contains("//! This crate exercises all supported item types."),
"second paragraph should appear:\n{output}"
);
}
#[test]
fn test_crate_docs_after_header() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
let header_pos = output
.find("// crate test_fixture")
.expect("crate header missing");
let doc_pos = output
.find("//! Test fixture crate")
.expect("crate doc missing");
assert!(
doc_pos > header_pos,
"crate docs should appear after the crate header"
);
}
#[test]
fn test_crate_docs_suppressed_by_no_docs() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_docs = true;
let output = render_full(&model, &args);
assert!(
!output.contains("//!"),
"--no-docs should suppress crate-level docs:\n{output}"
);
}
#[test]
fn test_crate_docs_suppressed_by_compact() {
let model = fixture_model();
let mut args = default_args();
args.filter.compact = true;
let output = render_full(&model, &args);
assert!(
!output.contains("//!"),
"--compact should suppress crate-level docs:\n{output}"
);
}
#[test]
fn test_crate_docs_limited_by_doc_lines() {
let model = fixture_model();
let mut args = default_args();
args.filter.doc_lines = Some(1);
let output = render_full(&model, &args);
assert!(
output.contains("//! Test fixture crate"),
"--doc-lines 1 should show first line:\n{output}"
);
let crate_doc_lines: Vec<&str> = output.lines().filter(|l| l.starts_with("//!")).collect();
assert_eq!(
crate_doc_lines.len(),
1,
"--doc-lines 1 should limit to 1 crate doc line, got: {crate_doc_lines:?}"
);
}
#[test]
fn test_crate_docs_suppressed_by_doc_lines_zero() {
let model = fixture_model();
let mut args = default_args();
args.filter.doc_lines = Some(0);
let output = render_full(&model, &args);
assert!(
!output.contains("//!"),
"--doc-lines 0 should suppress crate-level docs:\n{output}"
);
}
#[test]
fn test_crate_docs_suppressed_by_no_crate_docs() {
let model = fixture_model();
let mut args = default_args();
args.filter.no_crate_docs = true;
let output = render_full(&model, &args);
assert!(
!output.contains("//!"),
"--no-crate-docs should suppress crate-level docs:\n{output}"
);
assert!(
output.contains("///"),
"--no-crate-docs should NOT suppress item docs:\n{output}"
);
}
#[test]
fn test_crate_docs_not_in_search_mode() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "PubStruct", &filter, None, None, true, None);
assert!(
!output.contains("//!"),
"search mode should not show crate-level docs:\n{output}"
);
}
#[test]
fn test_trait_impl_summary_format() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
let summary = output.lines().find(|l| l.contains("// DerivedStruct:"));
assert!(
summary.is_some(),
"DerivedStruct should have a trait impl summary:\n{output}"
);
let summary = summary.unwrap();
assert!(
summary.contains("Clone"),
"summary should include Clone: {summary}"
);
assert!(
summary.contains("Debug"),
"summary should include Debug: {summary}"
);
assert!(
summary.contains("Eq"),
"summary should include Eq: {summary}"
);
assert!(
summary.contains("Hash"),
"summary should include Hash: {summary}"
);
assert!(
summary.contains("PartialEq"),
"summary should include PartialEq: {summary}"
);
assert!(
summary.contains("Display"),
"summary should include Display: {summary}"
);
}
#[test]
fn test_trait_impl_summary_sorted() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
let summary = output
.lines()
.find(|l| l.contains("// DerivedStruct:"))
.expect("DerivedStruct summary missing");
let traits_part = summary.split(": ").nth(1).unwrap();
let traits: Vec<&str> = traits_part.split(", ").collect();
let mut sorted = traits.clone();
sorted.sort();
assert_eq!(traits, sorted, "traits should be alphabetically sorted");
}
#[test]
fn test_trait_impl_all_expands() {
let model = fixture_model();
let mut args = default_args();
args.filter.all = true;
let output = render_full(&model, &args);
assert!(
output.contains("impl MyTrait for PubStruct { .. }"),
"--all should expand simple trait impls:\n{output}"
);
}
#[test]
fn test_trait_impl_rich_not_collapsed() {
let model = fixture_model();
let args = default_args();
let output = render_full(&model, &args);
assert!(
output.contains("impl Converter for PubStruct {"),
"rich trait impl should remain expanded:\n{output}"
);
assert!(
output.contains("type Output = String;"),
"associated type should be shown:\n{output}"
);
}
#[test]
fn test_search_smart_case_insensitive() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "pubstruct", &filter, None, None, true, None);
assert!(
output.contains("struct outer::PubStruct"),
"all-lowercase pattern should match PubStruct (case-insensitive):\n{output}"
);
}
#[test]
fn test_search_smart_case_sensitive() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(&model, "PubStruct", &filter, None, None, true, None);
assert!(
output.contains("struct outer::PubStruct"),
"uppercase pattern should find PubStruct (case-sensitive):\n{output}"
);
let non_comment_lines: Vec<&str> = output
.lines()
.filter(|l| !l.starts_with("//") && !l.starts_with("///") && !l.is_empty())
.collect();
for line in &non_comment_lines {
assert!(
line.contains("PubStruct"),
"case-sensitive search should only return items with exact case 'PubStruct': {line}"
);
}
}
#[test]
fn test_search_or_comma() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(
&model,
"PlainEnum,TupleEnum",
&filter,
None,
None,
true,
None,
);
assert!(
output.contains("PlainEnum"),
"OR search should find PlainEnum:\n{output}"
);
assert!(
output.contains("TupleEnum"),
"OR search should find TupleEnum:\n{output}"
);
}
#[test]
fn test_search_or_no_cross_match() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search(
&model,
"PlainEnum,TupleEnum",
&filter,
None,
None,
true,
None,
);
assert!(
!output.contains("PlainStruct"),
"OR search should not cross-match PlainStruct:\n{output}"
);
}
#[test]
fn test_glob_reexport_search_finds_trait() {
let model = fixture_model();
let filter = default_filter();
let reachable = compute_reachable_set(&model);
let output = search::render_search(
&model,
"GlobTrait",
&filter,
None,
None,
false,
Some(&reachable),
);
assert!(
output.contains("GlobTrait"),
"search should find GlobTrait via glob re-export:\n{output}"
);
assert!(
!output.contains("hidden_reexport::"),
"search path should NOT include private module hidden_reexport:\n{output}"
);
}
#[test]
fn test_glob_reexport_search_finds_struct() {
let model = fixture_model();
let filter = default_filter();
let reachable = compute_reachable_set(&model);
let output = search::render_search(
&model,
"GlobStruct",
&filter,
None,
None,
false,
Some(&reachable),
);
assert!(
output.contains("GlobStruct"),
"search should find GlobStruct via glob re-export:\n{output}"
);
assert!(
!output.contains("hidden_reexport::"),
"search path should NOT include private module hidden_reexport:\n{output}"
);
}
#[test]
fn test_glob_reexport_api_renders_items() {
let model = fixture_model();
let args = default_args();
let reachable = compute_reachable_set(&model);
let output = render_module_api(&model, None, &args, None, false, Some(&reachable));
assert!(
output.contains("GlobTrait"),
"API render should include GlobTrait via glob re-export:\n{output}"
);
assert!(
output.contains("GlobStruct"),
"API render should include GlobStruct via glob re-export:\n{output}"
);
assert!(
!output.contains("mod hidden_reexport"),
"API should NOT show private module hidden_reexport:\n{output}"
);
}
#[test]
fn test_nested_private_glob_reexport_search() {
let model = fixture_model();
let filter = default_filter();
let reachable = compute_reachable_set(&model);
let output = search::render_search(
&model,
"NestedPrivate",
&filter,
None,
None,
false,
Some(&reachable),
);
assert!(
output.contains("NestedPrivateStruct"),
"search should find NestedPrivateStruct via nested private glob re-export:\n{output}"
);
assert!(
output.contains("NestedPrivateTrait"),
"search should find NestedPrivateTrait via nested private glob re-export:\n{output}"
);
assert!(
output.contains("outer::NestedPrivateStruct"),
"search path should be outer::NestedPrivateStruct (canonical):\n{output}"
);
assert!(
!output.contains("nested_private::"),
"search path should NOT include private module name nested_private:\n{output}"
);
}
#[test]
fn test_nested_private_glob_reexport_api() {
let model = fixture_model();
let args = default_args();
let reachable = compute_reachable_set(&model);
let output = render_module_api(&model, Some("outer"), &args, None, false, Some(&reachable));
assert!(
output.contains("NestedPrivateStruct"),
"API for outer module should include NestedPrivateStruct via nested private glob:\n{output}"
);
assert!(
output.contains("NestedPrivateTrait"),
"API for outer module should include NestedPrivateTrait via nested private glob:\n{output}"
);
assert!(
!output.contains("mod nested_private"),
"API should NOT show private module wrapper:\n{output}"
);
}
#[test]
fn test_glob_private_modules_inlined_from_root_depth3() {
let model = fixture_model();
let mut args = default_args();
args.depth = 3;
let reachable = compute_reachable_set(&model);
let output = render_module_api(&model, None, &args, None, false, Some(&reachable));
assert!(
!output.contains("mod nested_private"),
"depth 3 render should NOT show private module nested_private:\n{output}"
);
assert!(
!output.contains("mod hidden_reexport"),
"depth 3 render should NOT show private module hidden_reexport:\n{output}"
);
assert!(
output.contains("NestedPrivateStruct"),
"depth 3 render should contain NestedPrivateStruct:\n{output}"
);
assert!(
output.contains("GlobTrait"),
"depth 3 render should contain GlobTrait:\n{output}"
);
}
fn default_examples_args() -> ExamplesArgs {
ExamplesArgs {
crate_name: "test-fixture".to_string(),
patterns: vec![],
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
context: "2".to_string(),
tests: None,
benches: None,
}
}
#[test]
fn test_examples_list_mode() {
let args = default_examples_args();
let output = cargo_brief::run_examples_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("@examples/example_usage.rs"),
"Should list the example file:\n{output}"
);
assert!(
output.contains("Example demonstrating basic usage"),
"Should include //! doc comment text:\n{output}"
);
assert!(
output.contains("// examples for"),
"Should have examples header:\n{output}"
);
assert!(
output.contains("// root:"),
"Should have root path header:\n{output}"
);
}
#[test]
fn test_examples_grep_mode() {
let mut args = default_examples_args();
args.patterns = vec!["PubStruct".to_string()];
let output = cargo_brief::run_examples_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("@examples/example_usage.rs"),
"Should show file with matches:\n{output}"
);
assert!(
output.contains('*'),
"Should have * markers on matching lines:\n{output}"
);
assert!(
output.contains("PubStruct"),
"Should contain the matched pattern:\n{output}"
);
}
#[test]
fn test_examples_grep_no_match() {
let mut args = default_examples_args();
args.patterns = vec!["nonexistent_xyzzy_pattern".to_string()];
let output = cargo_brief::run_examples_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("no matches"),
"Should indicate no matches:\n{output}"
);
}
#[test]
fn test_examples_grep_context_format() {
let mut args = default_examples_args();
args.patterns = vec!["pub_method".to_string()];
args.context = "1:1".to_string();
let output = cargo_brief::run_examples_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('*'),
"Should have * on match line:\n{output}"
);
assert!(
output.contains(':'),
"Should have line numbers with colons:\n{output}"
);
}
#[test]
fn test_examples_smart_case() {
let mut args = default_examples_args();
args.patterns = vec!["pubstruct".to_string()];
let output = cargo_brief::run_examples_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("PubStruct"),
"Lowercase pattern should match case-insensitively:\n{output}"
);
args.patterns = vec!["PUBSTRUCT".to_string()];
let output = cargo_brief::run_examples_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("no matches"),
"Uppercase pattern should not match:\n{output}"
);
}
#[test]
fn test_cross_crate_glob_phase1() {
let mut args = default_args();
args.no_expand_glob = true;
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("GlobSourceItem"),
"Phase 1 should list GlobSourceItem from glob-source:\n{output}"
);
assert!(
output.contains("GlobInnerItem"),
"Phase 1 should list GlobInnerItem from glob-inner via recursive expansion:\n{output}"
);
assert!(
output.contains("GlobInnerTrait"),
"Phase 1 should list GlobInnerTrait from glob-inner via recursive expansion:\n{output}"
);
assert!(
output.contains("pub use glob_source::GlobSourceItem"),
"Phase 1 should show individual pub use lines:\n{output}"
);
}
#[test]
fn test_cross_crate_glob_phase2() {
let args = default_args();
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("struct GlobInnerItem"),
"Phase 2 should inline full GlobInnerItem definition:\n{output}"
);
assert!(
output.contains("deep_field"),
"Phase 2 should include GlobInnerItem fields:\n{output}"
);
assert!(
output.contains("trait GlobInnerTrait"),
"Phase 2 should inline full GlobInnerTrait definition:\n{output}"
);
assert!(
output.contains("inner_method"),
"Phase 2 should include GlobInnerTrait methods:\n{output}"
);
assert!(
output.contains("struct GlobSourceItem"),
"Phase 2 should inline full GlobSourceItem definition:\n{output}"
);
}
#[test]
fn test_cross_crate_glob_search() {
let model = fixture_model();
let filter = default_filter();
let reachable = compute_reachable_set(&model);
let output = search::render_search(
&model,
"GlobSourceItem",
&filter,
None,
None,
false,
Some(&reachable),
);
assert!(
output.contains("GlobSourceItem"),
"search should find GlobSourceItem via cross-crate glob:\n{output}"
);
}
#[test]
fn methods_of_exact_match_excludes_similar_names() {
let model = fixture_model();
let mut filter = default_filter();
filter.no_structs = true;
filter.no_enums = true;
filter.no_traits = true;
filter.no_unions = true;
filter.no_constants = true;
filter.no_macros = true;
filter.no_aliases = true;
let output = search::render_search_methods_of(
&model, "Struct", &filter, None, None, true, None, "Struct",
);
assert!(
output.contains("(0 results)"),
"--methods-of Struct should not match PubStruct or DerivedStruct:\n{output}"
);
}
#[test]
fn methods_of_exact_match_finds_correct_type() {
let model = fixture_model();
let mut filter = default_filter();
filter.no_structs = true;
filter.no_enums = true;
filter.no_traits = true;
filter.no_unions = true;
filter.no_constants = true;
filter.no_macros = true;
filter.no_aliases = true;
let output = search::render_search_methods_of(
&model,
"PubStruct",
&filter,
None,
None,
true,
None,
"PubStruct",
);
assert!(
output.contains("pub_method"),
"--methods-of PubStruct should find pub_method:\n{output}"
);
assert!(
!output.contains("DerivedStruct"),
"--methods-of PubStruct should not include DerivedStruct items:\n{output}"
);
}
#[test]
fn test_summary_root_same_crate() {
let model = fixture_model();
let output = summary::render_summary(&model, None, true, None);
assert!(
output.starts_with("// crate test_fixture"),
"summary should start with crate header:\n{output}"
);
assert!(
output.contains("mod outer;"),
"summary should list mod outer:\n{output}"
);
assert!(
output.contains("traits"),
"outer module should have traits:\n{output}"
);
assert!(
output.contains("structs"),
"outer module should have structs:\n{output}"
);
assert!(
output.contains("// root:"),
"summary should have root items:\n{output}"
);
}
#[test]
fn test_summary_external_view() {
let model = fixture_model();
let reachable = compute_reachable_set(&model);
let output = summary::render_summary(&model, None, false, Some(&reachable));
assert!(
output.starts_with("// crate test_fixture"),
"summary should start with crate header:\n{output}"
);
assert!(
!output.contains("mod hidden_reexport"),
"pub(crate) module should not appear in external view:\n{output}"
);
assert!(
output.contains("mod outer;"),
"public module should appear:\n{output}"
);
}
#[test]
fn test_summary_module_scoped() {
let model = fixture_model();
let output = summary::render_summary(&model, Some("outer"), true, None);
assert!(
output.contains("// crate test_fixture::outer"),
"scoped summary should reference module in header:\n{output}"
);
assert!(
output.contains("mod inner;"),
"should list inner submodule:\n{output}"
);
assert!(
output.contains("// root:"),
"should have root counts for outer's items:\n{output}"
);
}
#[test]
fn test_summary_empty_module_omitted() {
let model = fixture_model();
let reachable = compute_reachable_set(&model);
let output = summary::render_summary(&model, Some("outer"), false, Some(&reachable));
for line in output.lines() {
if line.starts_with("mod ") {
assert!(
line.contains("//"),
"module line should have counts (empty modules omitted): {line}"
);
}
}
}
#[test]
fn test_summary_pipeline() {
let args = SummaryArgs {
target: TargetArgs {
crate_name: "test-fixture".to_string(),
module_path: None,
at_package: None,
at_mod: None,
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
},
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
};
let output = cargo_brief::run_summary_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("// crate test_fixture"),
"pipeline should produce summary with crate header:\n{output}"
);
assert!(
output.contains("mod outer"),
"pipeline should list outer module:\n{output}"
);
}
#[test]
fn test_summary_column_alignment() {
let model = fixture_model();
let output = summary::render_summary(&model, None, true, None);
let mod_lines: Vec<&str> = output.lines().filter(|l| l.starts_with("mod ")).collect();
if mod_lines.len() >= 2 {
let comment_positions: Vec<usize> = mod_lines.iter().filter_map(|l| l.find("//")).collect();
let first = comment_positions[0];
for (i, &pos) in comment_positions.iter().enumerate() {
assert_eq!(
pos, first,
"comment columns should be aligned: line {} has // at {}, expected {}",
i, pos, first
);
}
}
}
#[test]
fn test_summary_reexport_counted_as_target_kind() {
let model = fixture_model();
let reachable = compute_reachable_set(&model);
let output = summary::render_summary(&model, None, false, Some(&reachable));
let root_line = output
.lines()
.find(|l| l.starts_with("// root:"))
.expect("should have root line");
assert!(
root_line.contains("structs"),
"re-exported struct should be counted as struct at root:\n{output}"
);
}
#[test]
fn test_summary_named_module_reexport_external_view() {
let model = fixture_model();
let reachable = compute_reachable_set(&model);
let output = summary::render_summary(&model, None, false, Some(&reachable));
let mod_line = output
.lines()
.find(|l| l.starts_with("mod facade_pub;"))
.unwrap_or_else(|| panic!("named module re-export should appear:\n{output}"));
assert!(
mod_line.contains("1 structs"),
"facade_pub should report exactly 1 struct:\n{mod_line}"
);
assert!(
mod_line.contains("1 fns"),
"facade_pub should report exactly 1 fn:\n{mod_line}"
);
assert!(
mod_line.contains("1 traits"),
"facade_pub should report exactly 1 trait:\n{mod_line}"
);
assert!(
!output.contains("mod facade_inner"),
"private parent module must not leak into external summary:\n{output}"
);
}
#[test]
fn test_summary_named_module_reexport_same_crate() {
let model = fixture_model();
let output = summary::render_summary(&model, None, true, None);
let mod_line = output
.lines()
.find(|l| l.starts_with("mod facade_pub;"))
.unwrap_or_else(|| {
panic!("named module re-export should appear in same-crate summary:\n{output}")
});
assert!(
mod_line.contains("1 structs"),
"facade_pub should report exactly 1 struct in same-crate view:\n{mod_line}"
);
assert!(
mod_line.contains("1 fns"),
"facade_pub should report exactly 1 fn in same-crate view:\n{mod_line}"
);
assert!(
mod_line.contains("1 traits"),
"facade_pub should report exactly 1 trait in same-crate view:\n{mod_line}"
);
let root_facade_pub_count = output
.lines()
.filter(|l| l.starts_with("mod facade_pub;"))
.count();
assert_eq!(
root_facade_pub_count, 1,
"facade_pub should appear exactly once as a root-level module:\n{output}"
);
}
#[test]
fn test_summary_named_module_reexport_rename_alias() {
let model = fixture_model();
let reachable = compute_reachable_set(&model);
let output = summary::render_summary(&model, None, false, Some(&reachable));
assert!(
output.lines().any(|l| l.starts_with("mod facade_alias;")),
"renamed re-export should appear under alias name:\n{output}"
);
assert!(
!output.lines().any(|l| l.starts_with("mod facade_renamed;")),
"renamed module's own name must not leak into summary:\n{output}"
);
}
#[test]
fn test_summary_named_module_reexport_empty_suppressed() {
let model = fixture_model();
let reachable = compute_reachable_set(&model);
let external = summary::render_summary(&model, None, false, Some(&reachable));
assert!(
!external.contains("mod facade_empty"),
"empty re-exported module must not appear in external summary:\n{external}"
);
let same_crate = summary::render_summary(&model, None, true, None);
assert!(
!same_crate.contains("mod facade_empty"),
"empty re-exported module must not appear in same-crate summary:\n{same_crate}"
);
}
#[test]
fn test_search_kind_fn_only() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"free_function",
&filter,
None,
None,
true,
None,
None,
Some("fn"),
false,
None,
None,
);
assert!(
output.contains("fn "),
"search-kind fn should include functions:\n{output}"
);
assert!(
!output.contains("struct "),
"search-kind fn should exclude structs:\n{output}"
);
assert!(
!output.contains("enum "),
"search-kind fn should exclude enums:\n{output}"
);
}
#[test]
fn test_search_kind_struct_enum() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"Pub",
&filter,
None,
None,
true,
None,
None,
Some("struct,enum"),
false,
None,
None,
);
assert!(
output.contains("struct "),
"search-kind struct,enum should include structs:\n{output}"
);
assert!(
!output.contains("fn "),
"search-kind struct,enum should exclude functions:\n{output}"
);
}
#[test]
fn test_search_kind_no_match() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"PubStruct",
&filter,
None,
None,
true,
None,
None,
Some("macro"),
false,
None,
None,
);
assert!(
output.contains("(0 results)"),
"search-kind macro for PubStruct should find 0 results:\n{output}"
);
}
#[test]
fn search_glob_star_matches_suffix() {
let model = fixture_model();
let output = search_output(&model, "*Enum");
assert!(
output.contains("enum outer::PlainEnum"),
"glob *Enum should match PlainEnum:\n{output}"
);
assert!(
output.contains("enum outer::TupleEnum"),
"glob *Enum should match TupleEnum:\n{output}"
);
assert!(
output.contains("enum outer::StructEnum"),
"glob *Enum should match StructEnum:\n{output}"
);
assert!(
!output.contains("struct outer"),
"glob *Enum should not match structs:\n{output}"
);
}
#[test]
fn search_glob_question_mark() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model, "*::?lpha", &filter, None, None, true, None, None, None, true, None, None,
);
assert!(
output.contains("Alpha"),
"glob ?lpha should match Alpha:\n{output}"
);
assert!(
!output.contains("Beta"),
"glob ?lpha should not match Beta:\n{output}"
);
}
#[test]
fn search_glob_mid_pattern() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"*pub*method",
&filter,
None,
None,
true,
None,
None,
None,
true,
None,
None,
);
assert!(
output.contains("pub_method"),
"glob *pub*method should match pub_method:\n{output}"
);
}
#[test]
fn search_bare_word_still_substring() {
let model = fixture_model();
let output = search_output(&model, "Struct");
assert!(
output.contains("PubStruct"),
"bare word Struct should substring-match PubStruct:\n{output}"
);
}
#[test]
fn search_exclude_basic() {
let model = fixture_model();
let output = search_output(&model, "function -async");
assert!(
output.contains("free_function"),
"should find free_function:\n{output}"
);
assert!(
!output.contains("async_function"),
"should exclude async_function:\n{output}"
);
}
#[test]
fn search_exclude_glob() {
let model = fixture_model();
let output = search_output(&model, "*Enum -*Tuple*");
assert!(
output.contains("PlainEnum"),
"should find PlainEnum:\n{output}"
);
assert!(
!output.contains("TupleEnum"),
"glob exclusion should remove TupleEnum:\n{output}"
);
}
#[test]
fn search_exclude_global_across_or() {
let model = fixture_model();
let output = search_output(&model, "PlainEnum,TupleEnum -Alpha");
assert!(
!output.contains("::Alpha"),
"exclusion -Alpha should apply across OR groups:\n{output}"
);
assert!(
output.contains("Beta") || output.contains("PlainEnum") || output.contains("TupleEnum"),
"should still find non-excluded items:\n{output}"
);
}
#[test]
fn search_exact_match() {
let model = fixture_model();
let output = search_output(&model, "=Alpha");
assert!(
output.contains("Alpha"),
"=Alpha should find Alpha variant:\n{output}"
);
assert!(
output.contains("(1 results)") || output.contains("Alpha"),
"=Alpha should find results:\n{output}"
);
}
#[test]
fn search_exact_no_substring() {
let model = fixture_model();
let output = search_output(&model, "=Struct");
assert!(
output.contains("(0 results)"),
"=Struct should not match PubStruct (exact match only):\n{output}"
);
}
#[test]
fn search_exact_case_insensitive() {
let model = fixture_model();
let output = search_output(&model, "=alpha");
assert!(
output.contains("Alpha"),
"=alpha should match Alpha (smart-case: all lowercase = case insensitive):\n{output}"
);
}
#[test]
fn search_combined_exact_and_exclude() {
let model = fixture_model();
let output = search_output(&model, "=pub_method,=pub_field -pub_field");
assert!(
output.contains("pub_method"),
"should keep pub_method:\n{output}"
);
assert!(
!output.contains("::pub_field"),
"should exclude pub_field:\n{output}"
);
}
#[test]
fn search_glob_and_substring_and() {
let model = fixture_model();
let output = search_output(&model, "outer *Struct");
assert!(
output.contains("PubStruct"),
"should match outer::PubStruct:\n{output}"
);
assert!(
output.contains("GenericStruct"),
"should match outer::GenericStruct:\n{output}"
);
}
#[test]
fn test_search_kind_trait() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"My",
&filter,
None,
None,
true,
None,
None,
Some("trait"),
false,
None,
None,
);
assert!(
output.contains("trait "),
"search-kind trait should include traits:\n{output}"
);
assert!(
!output.contains("struct "),
"search-kind trait should not include structs:\n{output}"
);
}
#[test]
fn test_search_kind_const() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"MY_CONST",
&filter,
None,
None,
true,
None,
None,
Some("const"),
false,
None,
None,
);
assert!(
output.contains("const "),
"search-kind const should include constants:\n{output}"
);
}
fn build_test_fixture_index() -> cargo_brief::cross_crate::CrossCrateIndex {
let metadata = resolve::load_cargo_metadata(Some("test_fixture/Cargo.toml"))
.expect("Failed to load cargo metadata");
let json_path = rustdoc_json::generate_rustdoc_json(
"test-fixture",
"nightly",
Some("test_fixture/Cargo.toml"),
true,
&metadata.target_dir,
false,
false,
)
.expect("Failed to generate rustdoc JSON");
let krate =
rustdoc_json::parse_rustdoc_json(&json_path).expect("Failed to parse test fixture JSON");
let model = CrateModel::from_crate(krate);
let workspace_members: std::collections::HashSet<String> =
metadata.workspace_packages.into_iter().collect();
let available_packages = rustdoc_json::load_lockfile_packages(Some("test_fixture/Cargo.toml"));
cargo_brief::cross_crate::build_cross_crate_index(
&model,
"nightly",
Some("test_fixture/Cargo.toml"),
&metadata.target_dir,
false,
&workspace_members,
&available_packages,
)
}
#[test]
fn test_cross_crate_index_has_accessible_items() {
let index = build_test_fixture_index();
assert!(
!index.items.is_empty(),
"CrossCrateIndex should contain items from cross-crate re-exports"
);
assert!(
!index.source_models.is_empty(),
"CrossCrateIndex should have loaded sub-crate models"
);
}
#[test]
fn test_cross_crate_index_glob_flattened_paths() {
let index = build_test_fixture_index();
let paths: Vec<&str> = index
.items
.iter()
.map(|e| e.accessible_path.as_str())
.collect();
assert!(
paths.iter().any(|p| *p == "GlobSourceItem"),
"GlobSourceItem should be glob-flattened to root level.\nPaths: {paths:?}"
);
assert!(
paths.iter().any(|p| *p == "GlobInnerItem"),
"GlobInnerItem should be glob-flattened to root level.\nPaths: {paths:?}"
);
assert!(
paths.iter().any(|p| *p == "GlobInnerTrait"),
"GlobInnerTrait should be glob-flattened to root level.\nPaths: {paths:?}"
);
}
#[test]
fn test_cross_crate_index_rename_tracking() {
let index = build_test_fixture_index();
let paths: Vec<&str> = index
.items
.iter()
.map(|e| e.accessible_path.as_str())
.collect();
assert!(
paths.iter().any(|p| *p == "inner_alias"),
"inner_alias module should exist from `pub use glob_inner as inner_alias`.\nPaths: {paths:?}"
);
}
#[test]
fn test_cross_crate_index_dedup() {
let index = build_test_fixture_index();
let glob_inner_paths: Vec<&str> = index
.items
.iter()
.filter(|e| {
e.accessible_path == "GlobInnerItem"
|| e.accessible_path == "inner_alias::GlobInnerItem"
})
.map(|e| e.accessible_path.as_str())
.collect();
assert!(
glob_inner_paths.len() <= 2,
"GlobInnerItem should appear at most twice (glob-flattened + alias): {glob_inner_paths:?}"
);
}
#[test]
fn test_cross_crate_search_accessible_paths() {
let index = build_test_fixture_index();
let filter = default_filter();
let output = search::search_cross_crate_index(
&index,
"test_fixture",
"GlobInnerItem",
&filter,
None,
None,
None,
false,
None,
None,
);
assert!(
output.contains("GlobInnerItem"),
"Cross-crate search should find GlobInnerItem:\n{output}"
);
assert!(
!output.contains("glob_inner::GlobInnerItem"),
"Cross-crate search should not show internal crate path 'glob_inner::':\n{output}"
);
}
#[test]
fn test_cross_crate_api_virtual_tree() {
let mut args = default_args();
args.recursive = true;
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("mod inner_alias"),
"API output should contain virtual 'mod inner_alias' tree:\n{output}"
);
}
#[test]
fn test_cross_crate_search_all_item_types() {
let index = build_test_fixture_index();
let filter = default_filter();
let output = search::search_cross_crate_index(
&index,
"test_fixture",
"GlobInner",
&filter,
None,
None,
None,
false,
None,
None,
);
assert!(
output.contains("GlobInnerItem"),
"Should find GlobInnerItem struct:\n{output}"
);
assert!(
output.contains("GlobInnerTrait"),
"Should find GlobInnerTrait:\n{output}"
);
}
#[test]
fn test_cross_crate_in_returns_excludes_non_functions() {
let index = build_test_fixture_index();
let filter = default_filter();
let output = search::search_cross_crate_index(
&index,
"test_fixture",
"Glob",
&filter,
None,
None,
None,
false,
None,
Some("String"),
);
assert!(
!output.contains("GlobInnerItem"),
"--in-returns should exclude structs from cross-crate index:\n{output}"
);
}
#[test]
fn test_cross_crate_in_params_no_name_pattern_no_panic() {
let index = build_test_fixture_index();
let filter = default_filter();
let output = search::search_cross_crate_index(
&index,
"test_fixture",
"",
&filter,
None,
None,
None,
false,
Some("i32"),
None,
);
assert!(
output.is_empty(),
"--in-params i32 with no matching functions should produce empty output:\n{output}"
);
}
#[test]
fn test_cross_crate_in_params_exclusion_is_parameter_scoped() {
let index = build_test_fixture_index();
let output = search::search_cross_crate_index(
&index,
"test_fixture",
"glob_source_fn",
&default_filter(),
None,
None,
None,
false,
Some("PathBuf -Option"),
None,
);
assert!(
output.contains("glob_source_fn"),
"cross-crate --in-params should accept when one parameter satisfies the full pattern:\n{output}"
);
}
#[test]
fn test_cross_crate_in_returns_exclusion_filters_return_type() {
let index = build_test_fixture_index();
let output = search::search_cross_crate_index(
&index,
"test_fixture",
"glob_source_fn",
&default_filter(),
None,
None,
None,
false,
None,
Some("Result -String"),
);
assert!(
output.is_empty(),
"cross-crate --in-returns exclusions should reject matching return type strings:\n{output}"
);
}
#[test]
fn test_cross_crate_in_returns_exclusion_does_not_match_item_path() {
let index = build_test_fixture_index();
let output = search::search_cross_crate_index(
&index,
"test_fixture",
"glob_source_fn",
&default_filter(),
None,
None,
None,
false,
None,
Some("Result -glob_source_fn"),
);
assert!(
output.contains("glob_source_fn"),
"cross-crate --in-returns exclusions should apply to return type strings, not item paths:\n{output}"
);
}
#[test]
fn test_leaf_struct_resolution() {
let model = fixture_model();
let args = default_args();
let (item_id, item) = model
.find_item_in_module("outer", "PubStruct")
.expect("PubStruct should be found in outer");
let output = render_leaf_item(&model, item, item_id, &args, None, true, None);
assert!(
output.contains("pub struct PubStruct"),
"Should render PubStruct definition:\n{output}"
);
assert!(
output.contains("pub fn pub_method"),
"Should render impl methods:\n{output}"
);
assert!(
!output.contains("PlainEnum"),
"Should NOT contain sibling items:\n{output}"
);
assert!(
!output.contains("pub trait MyTrait"),
"Should NOT contain sibling trait definitions:\n{output}"
);
}
#[test]
fn test_leaf_trait_resolution() {
let model = fixture_model();
let args = default_args();
let (item_id, item) = model
.find_item_in_module("outer", "MyTrait")
.expect("MyTrait should be found in outer");
let output = render_leaf_item(&model, item, item_id, &args, None, true, None);
assert!(
output.contains("pub trait MyTrait"),
"Should render MyTrait definition:\n{output}"
);
assert!(
output.contains("fn do_thing"),
"Should render trait methods:\n{output}"
);
assert!(
!output.contains("PubStruct"),
"Should NOT contain sibling structs:\n{output}"
);
}
#[test]
fn test_leaf_reexport_resolution() {
let model = fixture_model();
let args = default_args();
let (item_id, item) = model
.find_item_in_module("", "ReExported")
.expect("ReExported should be found at crate root");
let output = render_leaf_item(&model, item, item_id, &args, None, true, None);
assert!(
output.contains("pub struct PubStruct"),
"Should render the actual PubStruct definition via re-export:\n{output}"
);
}
#[test]
fn test_leaf_root_level_item() {
let model = fixture_model();
let args = default_args();
let (item_id, item) = model
.find_item_in_module("", "DeprecatedStruct")
.expect("DeprecatedStruct should be found at crate root");
let output = render_leaf_item(&model, item, item_id, &args, None, true, None);
assert!(
output.contains("DeprecatedStruct"),
"Should render DeprecatedStruct:\n{output}"
);
}
#[test]
fn test_leaf_not_found_shows_available() {
let model = fixture_model();
let output = render_leaf_not_found(&model, "outer", "NonExistent", true, None);
assert!(
output.contains("ERROR: item 'NonExistent' not found in module 'outer'"),
"Should show error message:\n{output}"
);
assert!(
output.contains("Available items:"),
"Should list available items:\n{output}"
);
assert!(
output.contains("PubStruct (struct)"),
"Should list PubStruct as available:\n{output}"
);
assert!(
output.contains("MyTrait (trait)"),
"Should list MyTrait as available:\n{output}"
);
assert!(
output.contains("TIP: Try `search NonExistent`"),
"Should show search tip:\n{output}"
);
}
#[test]
fn test_leaf_private_item_not_visible_external() {
let model = fixture_model();
let args = default_args();
let reachable = compute_reachable_set(&model);
let result = model.find_item_in_module("outer", "PrivateStruct");
if let Some((item_id, item)) = result {
let output = render_leaf_item(&model, item, item_id, &args, None, false, Some(&reachable));
assert!(
output.contains("not visible from observer position"),
"Private item should not be rendered in external view:\n{output}"
);
}
}
#[test]
fn test_leaf_module_wins_over_item() {
let model = fixture_model();
let module = model.find_module("outer::inner");
assert!(module.is_some(), "inner should resolve as a module");
let leaf = model.find_item_in_module("outer", "inner");
assert!(
leaf.is_none(),
"find_item_in_module should skip Module items"
);
}
#[test]
fn test_leaf_resolution_via_pipeline() {
use cargo_brief::cli::RemoteOpts;
let mut args = default_args();
args.target.module_path = Some("outer::PubStruct".to_string());
let output =
cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).expect("pipeline should work");
assert!(
output.contains("pub struct PubStruct"),
"Pipeline should resolve leaf item PubStruct:\n{output}"
);
assert!(
output.contains("pub fn pub_method"),
"Pipeline should include impl methods:\n{output}"
);
assert!(
!output.contains("PlainEnum"),
"Pipeline should NOT contain siblings:\n{output}"
);
}
#[test]
fn test_leaf_not_found_via_pipeline() {
use cargo_brief::cli::RemoteOpts;
let mut args = default_args();
args.target.module_path = Some("outer::NonExistent".to_string());
let output =
cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).expect("pipeline should work");
assert!(
output.contains("ERROR: item 'NonExistent' not found"),
"Pipeline should show leaf-not-found error:\n{output}"
);
assert!(
output.contains("Available items:"),
"Pipeline should list available items:\n{output}"
);
}
#[test]
fn search_members_flag_expands_all_members() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"PubStruct",
&filter,
None,
None,
true,
None,
None,
None,
true, None,
None,
);
assert!(
output.contains("pub_field"),
"--members should show fields:\n{output}"
);
assert!(
output.contains("pub_method"),
"--members should show methods:\n{output}"
);
}
#[test]
fn search_default_suppresses_members() {
let model = fixture_model();
let output = search_output(&model, "PubStruct");
assert!(
output.contains("struct outer::PubStruct"),
"struct itself should appear:\n{output}"
);
assert!(
!output.contains("pub_field"),
"fields should be suppressed by default:\n{output}"
);
assert!(
!output.contains("pub_method"),
"methods should be suppressed by default:\n{output}"
);
}
#[test]
fn search_exact_name_shows_member() {
let model = fixture_model();
let output = search_output(&model, "pub_field");
assert!(
output.contains("pub_field"),
"exact field name should show the field:\n{output}"
);
assert!(
output.contains("struct") && output.contains("PubStruct"),
"parent struct should appear as context:\n{output}"
);
}
#[test]
fn search_collapsed_display_format() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"PubStruct",
&filter,
None,
None,
true,
None,
None,
None,
true, None,
None,
);
assert!(
output.contains("-::"),
"--members should produce collapsed -:: display:\n{output}"
);
}
#[test]
fn search_members_sort_by_path() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"PubStruct",
&filter,
None,
None,
true,
None,
None,
None,
true, None,
None,
);
let lines: Vec<&str> = output
.lines()
.filter(|l| !l.starts_with("//") && !l.starts_with("///"))
.collect();
assert!(
lines.first().map_or(false, |l| l.contains("PubStruct")),
"path-based sort should put parent type first:\n{output}"
);
}
#[test]
fn search_members_with_limit() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"PubStruct",
&filter,
Some("2"),
None,
true,
None,
None,
None,
true, None,
None,
);
let result_lines: Vec<&str> = output
.lines()
.filter(|l| !l.starts_with("//") && !l.starts_with("///"))
.collect();
assert!(
result_lines.len() <= 2,
"--limit 2 should cap results to 2, got {}:\n{output}",
result_lines.len()
);
assert!(
output.contains("more results"),
"should indicate more results:\n{output}"
);
}
#[test]
fn search_collapsed_variant_display() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"PlainEnum",
&filter,
None,
None,
true,
None,
None,
None,
true, None,
None,
);
assert!(
output.contains("-::"),
"enum variants should use collapsed -:: display:\n{output}"
);
assert!(
output.contains("Alpha"),
"variant Alpha should be present:\n{output}"
);
}
fn default_search_args(patterns: Vec<&str>) -> SearchArgs {
SearchArgs {
crate_name: "test-fixture".to_string(),
patterns: patterns.into_iter().map(str::to_string).collect(),
filter: default_filter(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
at_package: None,
at_mod: None,
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
limit: None,
methods_of: None,
search_kind: None,
members: false,
in_params: None,
in_returns: None,
}
}
#[test]
fn methods_of_no_stack_overflow() {
let args = SearchArgs {
crate_name: "test-fixture".to_string(),
patterns: vec![],
filter: default_filter(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
at_package: None,
at_mod: None,
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
limit: None,
methods_of: Some("PubStruct".to_string()),
search_kind: None,
members: false,
in_params: None,
in_returns: None,
};
let result = cargo_brief::run_search_pipeline(&args, &RemoteOpts::default());
assert!(
result.is_ok(),
"run_search_pipeline failed: {:?}",
result.err()
);
let output = result.unwrap();
assert!(
output.contains("pub_method"),
"should contain methods of PubStruct:\n{output}"
);
}
#[test]
fn search_pipeline_header_counts_cross_crate_results() {
let args = default_search_args(vec!["GlobInnerItem"]);
let output = cargo_brief::run_search_pipeline(&args, &RemoteOpts::default())
.expect("search pipeline failed");
assert!(
output.starts_with("// crate test_fixture — search: \"GlobInnerItem\" (1 results)"),
"first header should include cross-crate appended results:\n{output}"
);
assert!(
output.contains("struct GlobInnerItem"),
"search pipeline should still render the cross-crate result:\n{output}"
);
}
#[test]
fn search_pipeline_header_counts_limited_cross_crate_total() {
let mut args = default_search_args(vec!["GlobInnerItem"]);
args.limit = Some("0".to_string());
let output = cargo_brief::run_search_pipeline(&args, &RemoteOpts::default())
.expect("search pipeline failed");
assert!(
output.starts_with("// crate test_fixture — search: \"GlobInnerItem\" (1 results)"),
"limited search header should report total matches, not rendered rows:\n{output}"
);
assert!(
output.contains("// ... and 1 more results"),
"limited search should keep pagination based on total matches:\n{output}"
);
assert!(
!output.contains("struct GlobInnerItem"),
"limit 0 should not render the matching row:\n{output}"
);
}
#[test]
fn test_in_returns_string() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"*",
&filter,
None,
None,
true,
None,
None,
None,
false,
None,
Some("String"),
);
assert!(
output.contains("async_function"),
"--in-returns String should include async_function() -> String:\n{output}"
);
assert!(
output.contains("where_fn"),
"--in-returns String should include where_fn() -> String:\n{output}"
);
assert!(
!output.contains("free_function"),
"--in-returns String should exclude free_function() -> i32:\n{output}"
);
}
#[test]
fn test_in_params_i32() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"*",
&filter,
None,
None,
true,
None,
None,
None,
false,
Some("i32"),
None,
);
assert!(
output.contains("free_function"),
"--in-params i32 should include free_function(x: i32, y: i32):\n{output}"
);
assert!(
!output.contains("async_function"),
"--in-params i32 should exclude async_function (no params):\n{output}"
);
}
#[test]
fn test_in_params_and_in_returns_and_semantics() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"*",
&filter,
None,
None,
true,
None,
None,
None,
false,
Some("i32"),
Some("i32"),
);
assert!(
output.contains("free_function"),
"--in-params i32 --in-returns i32 should include free_function:\n{output}"
);
assert!(
!output.contains("async_function"),
"--in-params i32 --in-returns i32 should exclude async_function:\n{output}"
);
assert!(
!output.contains("struct "),
"type filter should exclude structs:\n{output}"
);
}
#[test]
fn test_in_params_no_name_pattern_collects_all() {
let model = fixture_model();
let filter = default_filter();
let output = search::render_search_filtered(
&model,
"",
&filter,
None,
None,
true,
None,
None,
None,
false,
Some("i32"),
None,
);
assert!(
output.contains("free_function"),
"empty name pattern + --in-params i32 should still find free_function:\n{output}"
);
}
#[test]
fn test_in_params_exclusion_is_parameter_scoped() {
let model = fixture_model();
let output = search::render_search_filtered(
&model,
"*",
&default_filter(),
None,
None,
true,
None,
None,
None,
false,
Some("impl -Debug"),
None,
);
assert!(
output.contains("multi_impl_trait"),
"--in-params should accept a function when one parameter satisfies the full pattern:\n{output}"
);
}
#[test]
fn test_in_returns_name_pattern_and_semantics() {
let model = fixture_model();
let output = search::render_search_filtered(
&model,
"free",
&default_filter(),
None,
None,
true,
None,
None,
None,
false,
None,
Some("i32"),
);
assert!(
output.contains("free_function"),
"name 'free' + --in-returns i32 should find free_function:\n{output}"
);
assert!(
!output.contains("async_function"),
"async_function does not match name 'free':\n{output}"
);
}
#[test]
fn test_in_returns_exclusion_filters_return_type() {
let model = fixture_model();
let output = search::render_search_filtered(
&model,
"*",
&default_filter(),
None,
None,
true,
None,
None,
None,
false,
None,
Some("String -String"),
);
assert!(
output.contains("(0 results)"),
"--in-returns exclusions should reject matching return type strings:\n{output}"
);
assert!(
!output.contains("where_fn"),
"where_fn returns String and should be excluded by -String:\n{output}"
);
}
#[test]
fn test_in_returns_exclusion_does_not_match_item_path() {
let model = fixture_model();
let output = search::render_search_filtered(
&model,
"*",
&default_filter(),
None,
None,
true,
None,
None,
None,
false,
None,
Some("String -where_fn"),
);
assert!(
output.contains("where_fn"),
"--in-returns exclusions should apply to return type strings, not item paths:\n{output}"
);
}
#[test]
fn test_in_returns_matches_member_methods_with_methods_of() {
let model = fixture_model();
let output = search::render_search_filtered(
&model,
"pub_method",
&default_filter(),
None,
None,
true,
None,
Some("PubStruct"),
None,
false,
None,
Some("i32"),
);
assert!(
output.contains("pub_method"),
"--in-returns should filter impl methods as functions when --methods-of is active:\n{output}"
);
}
#[test]
fn test_type_filter_header_lists_active_filters() {
let model = fixture_model();
let output = search::render_search_filtered(
&model,
"",
&default_filter(),
None,
None,
true,
None,
None,
None,
false,
Some("i32"),
Some("i32 -String"),
);
assert!(
output.starts_with(
"// crate test_fixture — search: \"\" in-params: \"i32\" in-returns: \"i32 -String\""
),
"search header should describe active type filters:\n{output}"
);
}
#[test]
fn test_in_returns_excludes_non_functions() {
let model = fixture_model();
let mut filter = default_filter();
filter.no_structs = false;
let output = search::render_search_filtered(
&model,
"*",
&filter,
None,
None,
true,
None,
None,
None,
false,
None,
Some("String"),
);
assert!(
!output.contains("struct "),
"--in-returns should exclude structs even if no_structs=false:\n{output}"
);
}
#[test]
fn search_help_mentions_quoted_type_filter_exclusions() {
let mut cmd = BriefDirect::command();
let search_cmd = cmd
.find_subcommand_mut("search")
.expect("search subcommand should exist");
let help = search_cmd.render_long_help().to_string();
assert!(
help.contains("--in-params \"TokenStream -Option\""),
"search help should show quoted type-filter exclusions:\n{help}"
);
assert!(
help.contains("Quote multi-token"),
"search help should explain multi-token type-filter quoting:\n{help}"
);
}
#[test]
fn test_named_cross_crate_reexport_expanded() {
let args = default_args();
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("struct NamedSourceItem"),
"Named re-export should be expanded to full definition:\n{output}"
);
assert!(
output.contains("ns_field"),
"Named re-export should include struct fields:\n{output}"
);
assert!(
!output.contains("pub use named_source::NamedSourceItem;"),
"Expanded named re-export should not show pub use line:\n{output}"
);
}
#[test]
fn test_named_cross_crate_trait_reexport_expanded() {
let args = default_args();
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("trait NamedSourceTrait"),
"Named trait re-export should be expanded:\n{output}"
);
assert!(
output.contains("named_method"),
"Named trait re-export should include methods:\n{output}"
);
assert!(
!output.contains("pub use named_source::NamedSourceTrait;"),
"Expanded named trait re-export should not show pub use line:\n{output}"
);
}
#[test]
fn test_named_cross_crate_reexport_no_expand() {
let mut args = default_args();
args.no_expand_glob = true;
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("pub use named_source::NamedSourceItem;"),
"With --no-expand-glob, named re-export should stay as pub use:\n{output}"
);
}
fn default_ts_args() -> TsArgs {
TsArgs {
crate_name: "test-fixture".to_string(),
query: String::new(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
captures: false,
context: "0".to_string(),
src_only: false,
limit: None,
quiet: false,
}
}
#[test]
fn test_ts_finds_function_items() {
let mut args = default_ts_args();
args.query = "(function_item name: (identifier) @name)".to_string();
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("free_function"),
"Should find free_function:\n{output}"
);
}
#[test]
fn test_ts_captures_mode() {
let mut args = default_ts_args();
args.query = "(function_item name: (identifier) @name)".to_string();
args.captures = true;
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("@name: free_function"),
"Captures mode should show @name: free_function:\n{output}"
);
}
#[test]
fn test_ts_finds_struct_items() {
let mut args = default_ts_args();
args.query = "(struct_item name: (type_identifier) @name)".to_string();
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("PubStruct"),
"Should find PubStruct:\n{output}"
);
}
#[test]
fn test_ts_invalid_query_error() {
let mut args = default_ts_args();
args.query = "(invalid_syntax!!!".to_string();
let result = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default());
assert!(result.is_err(), "Invalid query should return Err");
let err = result.unwrap_err().to_string();
assert!(
err.contains("Invalid tree-sitter query"),
"Error should mention invalid query: {err}"
);
}
#[test]
fn test_ts_no_matches() {
let mut args = default_ts_args();
args.query = "(while_expression) @w".to_string();
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("// no matches"),
"No matches should show comment:\n{output}"
);
}
#[test]
fn test_ts_context() {
let mut args = default_ts_args();
args.query = "(function_item name: (identifier) @name)".to_string();
args.context = "1".to_string();
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('*'),
"Context mode should have * markers:\n{output}"
);
}
#[test]
fn test_ts_captureless_query() {
let mut args = default_ts_args();
args.query = "(function_item)".to_string();
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("free_function"),
"Capture-less query should still match functions:\n{output}"
);
assert!(
!output.contains("// no matches"),
"Should not be empty:\n{output}"
);
}
#[test]
fn test_ts_verbatim_shows_pattern_root() {
let mut args = default_ts_args();
args.query = "(impl_item trait: (type_identifier) @t (#eq? @t \"MyTrait\"))".to_string();
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("impl MyTrait for PubStruct"),
"Verbatim mode should show full impl block:\n{output}"
);
}
#[test]
fn test_ts_src_only_excludes_examples() {
let mut args = default_ts_args();
args.query = "(function_item name: (identifier) @n (#eq? @n \"main\"))".to_string();
args.src_only = true;
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("// no matches"),
"src_only should not find main() from examples/:\n{output}"
);
}
#[test]
fn test_ts_src_only_finds_src() {
let mut args = default_ts_args();
args.query = "(function_item name: (identifier) @name)".to_string();
args.src_only = true;
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("free_function"),
"src_only should still find functions in src/:\n{output}"
);
}
#[test]
fn test_ts_limit() {
let mut args = default_ts_args();
args.query = "(function_item)".to_string();
args.limit = Some("3".to_string());
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
let at_count = output.lines().filter(|l| l.starts_with('@')).count();
assert_eq!(
at_count, 3,
"limit=3 should produce exactly 3 @-lines:\n{output}"
);
}
#[test]
fn test_ts_limit_offset() {
let mut args = default_ts_args();
args.query = "(function_item)".to_string();
args.limit = Some("2:2".to_string());
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
let at_count = output.lines().filter(|l| l.starts_with('@')).count();
assert_eq!(
at_count, 2,
"limit=2:2 should produce exactly 2 @-lines:\n{output}"
);
let mut args_no_offset = default_ts_args();
args_no_offset.query = "(function_item)".to_string();
args_no_offset.limit = Some("2".to_string());
let output_no_offset =
cargo_brief::run_ts_pipeline(&args_no_offset, &RemoteOpts::default()).unwrap();
let first_with_offset = output.lines().find(|l| l.starts_with('@'));
let first_without_offset = output_no_offset.lines().find(|l| l.starts_with('@'));
assert_ne!(
first_with_offset, first_without_offset,
"Offset should skip the first matches"
);
}
#[test]
fn test_ts_quiet() {
let mut args = default_ts_args();
args.query = "(function_item)".to_string();
args.quiet = true;
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.lines().all(|l| l.starts_with('@') || l.is_empty()),
"quiet mode should only have @file:line lines:\n{output}"
);
assert!(
!output.contains("fn "),
"quiet mode should not contain source text:\n{output}"
);
}
#[test]
fn test_ts_quiet_captures() {
let mut args = default_ts_args();
args.query = "(function_item name: (identifier) @name)".to_string();
args.captures = true;
args.quiet = true;
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('@'),
"quiet captures should still have @-lines:\n{output}"
);
assert!(
!output.contains("@name:"),
"quiet captures should not show capture text:\n{output}"
);
}
#[test]
fn test_ts_no_matches_hint() {
let mut args = default_ts_args();
args.query = "(while_expression) @w".to_string();
let output = cargo_brief::run_ts_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("tree-sitter.github.io"),
"No-match output should include playground hint:\n{output}"
);
}
fn default_code_args() -> CodeArgs {
CodeArgs {
args: vec!["test-fixture".to_string()],
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
src_only: false,
no_deps: true,
all_deps: false,
limit: None,
quiet: false,
refs: false,
refs_only: false,
in_type: None,
}
}
#[test]
fn test_code_finds_fn() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "free_function".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("free_function"),
"Should find free_function:\n{output}"
);
}
#[test]
fn test_code_finds_struct() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "struct".into(), "PubStruct".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("PubStruct"),
"Should find PubStruct:\n{output}"
);
}
#[test]
fn test_code_finds_enum() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "enum".into(), "PlainEnum".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("PlainEnum"),
"Should find PlainEnum:\n{output}"
);
}
#[test]
fn test_code_finds_trait() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "trait".into(), "MyTrait".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("trait MyTrait"),
"Should find trait MyTrait:\n{output}"
);
}
#[test]
fn test_code_finds_const() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "const".into(), "MY_CONST".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("MY_CONST"),
"Should find MY_CONST:\n{output}"
);
}
#[test]
fn test_code_finds_field() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "field".into(), "pub_field".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("pub_field"),
"Should find pub_field:\n{output}"
);
assert!(
output.contains("struct PubStruct"),
"Parent context should show struct PubStruct:\n{output}"
);
}
#[test]
fn test_code_finds_type_alias() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "type".into(), "Alias".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(output.contains("Alias"), "Should find Alias:\n{output}");
}
#[test]
fn test_code_finds_impl() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "impl".into(), "PubStruct".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("impl PubStruct"),
"Should find impl PubStruct:\n{output}"
);
}
#[test]
fn test_code_finds_macro() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "macro".into(), "my_macro".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("my_macro"),
"Should find my_macro:\n{output}"
);
}
#[test]
fn test_code_catch_all() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "PubStruct".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("struct PubStruct"),
"Catch-all should find struct PubStruct:\n{output}"
);
assert!(
output.contains("impl PubStruct"),
"Catch-all should find impl PubStruct:\n{output}"
);
}
#[test]
fn test_code_case_insensitive() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "pubstruct".into()]; let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("PubStruct"),
"All-lowercase search should find PubStruct (case-insensitive):\n{output}"
);
}
#[test]
fn test_code_case_sensitive() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "Pubstruct".into()]; let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("no definitions found"),
"Mixed-case 'Pubstruct' should not find PubStruct (case-sensitive):\n{output}"
);
}
#[test]
fn test_code_no_matches() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "nonexistent_xyz".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("no definitions found"),
"Nonexistent name should produce no-match message:\n{output}"
);
}
#[test]
fn test_code_quiet_mode() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "free_function".into()];
args.quiet = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('@'),
"Quiet mode should have @file:line:\n{output}"
);
assert!(
!output.contains("x + y"),
"Quiet mode should not contain function body:\n{output}"
);
}
#[test]
fn test_code_limit() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "free_function".into()];
args.limit = Some("1".to_string());
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
let at_count = output.lines().filter(|l| l.starts_with('@')).count();
assert_eq!(
at_count, 1,
"limit=1 should produce exactly 1 @-line:\n{output}"
);
}
#[test]
fn test_code_module_context() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "struct".into(), "PubStruct".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("test-fixture::outer") || output.contains("test_fixture::outer"),
"Context should contain outer module:\n{output}"
);
}
#[test]
fn test_code_parent_context_impl_method() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "pub_method".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("impl PubStruct"),
"Parent context should show impl PubStruct:\n{output}"
);
}
#[test]
fn test_code_kind_without_name_errors() {
let mut args = default_code_args();
args.args = vec!["fn".into()];
let result = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default());
assert!(result.is_err(), "Kind without name should error");
let err = result.unwrap_err().to_string();
assert!(
err.contains("item kind"),
"Error should mention item kind: {err}"
);
}
#[test]
fn test_code_unknown_kind_errors() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "xyz".into(), "foo".into()];
let result = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default());
assert!(result.is_err(), "Unknown kind should error");
let err = result.unwrap_err().to_string();
assert!(
err.contains("Unknown item kind"),
"Error should mention unknown kind: {err}"
);
}
#[test]
fn test_code_src_only() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "main".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
let found_without = output.contains("main");
args.src_only = true;
let output_src = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
found_without,
"Without src_only should find main:\n{output}"
);
assert!(
output_src.contains("no fn definitions found"),
"With src_only should not find main from examples:\n{output_src}"
);
}
#[test]
fn test_code_all_deps_finds_dep_struct() {
let mut args = default_code_args();
args.no_deps = false;
args.all_deps = true;
args.args = vec![
"test-fixture".into(),
"struct".into(),
"GlobSourceItem".into(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("GlobSourceItem"),
"all_deps should find GlobSourceItem in dep:\n{output}"
);
}
#[test]
fn test_code_all_deps_finds_named_dep() {
let mut args = default_code_args();
args.no_deps = false;
args.all_deps = true;
args.args = vec![
"test-fixture".into(),
"struct".into(),
"NamedSourceItem".into(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("NamedSourceItem"),
"all_deps should find NamedSourceItem in named-source dep:\n{output}"
);
}
#[test]
fn test_code_no_deps_excludes_dep_items() {
let mut args = default_code_args();
args.no_deps = true;
args.args = vec![
"test-fixture".into(),
"struct".into(),
"GlobSourceItem".into(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("no struct definitions found"),
"no_deps should not find GlobSourceItem:\n{output}"
);
}
#[test]
fn test_code_default_finds_accessible_deps() {
let mut args = default_code_args();
args.no_deps = false;
args.all_deps = false;
args.args = vec![
"test-fixture".into(),
"struct".into(),
"GlobSourceItem".into(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("GlobSourceItem"),
"Default mode should find GlobSourceItem (accessible via glob re-export):\n{output}"
);
}
#[test]
fn test_code_default_finds_transitive_accessible() {
let mut args = default_code_args();
args.no_deps = false;
args.all_deps = false;
args.args = vec![
"test-fixture".into(),
"struct".into(),
"GlobInnerItem".into(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("GlobInnerItem"),
"Default mode should find GlobInnerItem (transitive via glob-source→glob-inner):\n{output}"
);
}
#[test]
fn test_code_all_deps_module_context() {
let mut args = default_code_args();
args.no_deps = false;
args.all_deps = true;
args.args = vec![
"test-fixture".into(),
"struct".into(),
"GlobSourceItem".into(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("glob")
&& !output.contains("test-fixture::")
&& !output.contains("test_fixture::"),
"Context should reference glob dep crate, not test_fixture:\n{output}"
);
}
#[test]
fn test_code_dep_search_with_limit() {
let mut args = default_code_args();
args.no_deps = false;
args.all_deps = true;
args.args = vec!["test-fixture".into(), "GlobSourceItem".into()];
args.limit = Some("1".to_string());
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
let at_count = output.lines().filter(|l| l.starts_with('@')).count();
assert_eq!(
at_count, 1,
"limit=1 should produce exactly 1 @-line:\n{output}"
);
}
#[test]
fn test_code_all_deps_quiet() {
let mut args = default_code_args();
args.no_deps = false;
args.all_deps = true;
args.quiet = true;
args.args = vec![
"test-fixture".into(),
"struct".into(),
"GlobSourceItem".into(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('@'),
"Quiet mode should have @file:line:\n{output}"
);
assert!(
!output.contains("pub struct"),
"Quiet mode should not contain struct body:\n{output}"
);
}
#[test]
fn test_code_one_arg_catch_all() {
let mut args = default_code_args();
args.args = vec!["PubStruct".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("PubStruct"),
"1-arg catch-all should find PubStruct:\n{output}"
);
}
#[test]
fn test_code_two_arg_kind_implicit_self() {
let mut args = default_code_args();
args.args = vec!["struct".into(), "PubStruct".into()];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("PubStruct"),
"2-arg kind form should find PubStruct with implicit self:\n{output}"
);
}
#[test]
fn test_code_self_searches_workspace_members() {
let mut args = default_code_args();
args.args = vec!["self".into(), "GlobSourceItem".into()];
args.no_deps = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("GlobSourceItem"),
"self with no_deps should find GlobSourceItem in workspace member:\n{output}"
);
}
#[test]
fn test_code_named_target_no_workspace_expansion() {
let mut args = default_code_args();
args.args = vec![
"test-fixture".into(),
"struct".into(),
"GlobSourceItem".into(),
];
args.no_deps = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("no struct definitions found"),
"Named target should not find GlobSourceItem from other workspace members:\n{output}"
);
}
#[test]
fn test_code_kind_without_name_1arg() {
let mut args = default_code_args();
args.args = vec!["struct".into()];
let result = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default());
assert!(result.is_err(), "Kind-only 1-arg should error");
let err = result.unwrap_err().to_string();
assert!(
err.contains("item kind"),
"Error should mention item kind: {err}"
);
}
#[test]
fn test_code_refs_shows_defs_and_refs() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "free_function".into()];
args.refs = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains(" in "),
"Should have definition context line:\n{output}"
);
assert!(
output.contains("// --- References ---"),
"Should have references separator:\n{output}"
);
assert!(
output.contains('*'),
"Should have * marker on grep match lines:\n{output}"
);
}
#[test]
fn test_code_refs_only_skips_definitions() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "PubStruct".into()];
args.refs_only = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('*'),
"refs-only should have * markers:\n{output}"
);
assert!(
!output.contains(" in "),
"refs-only should not contain definition context lines:\n{output}"
);
}
#[test]
fn test_code_refs_only_quiet() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "PubStruct".into()];
args.refs_only = true;
args.quiet = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('@'),
"Quiet refs-only should have @file:line:\n{output}"
);
assert!(
!output.contains('*'),
"Quiet refs-only should not have * markers:\n{output}"
);
}
#[test]
fn test_code_in_type_filters_to_parent() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "pub_method".into()];
args.in_type = Some("PubStruct".to_string());
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("impl PubStruct"),
"Should find pub_method in impl PubStruct:\n{output}"
);
}
#[test]
fn test_code_in_type_excludes_non_matching() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "pub_method".into()];
args.in_type = Some("NonExistent".to_string());
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("no fn definitions found"),
"Non-matching --in should produce no-match message:\n{output}"
);
}
#[test]
fn test_code_in_type_case_insensitive() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "pub_method".into()];
args.in_type = Some("pubstruct".to_string()); let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("impl PubStruct"),
"Lowercase --in should match PubStruct (case-insensitive):\n{output}"
);
}
#[test]
fn test_code_in_type_with_refs() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "fn".into(), "pub_method".into()];
args.in_type = Some("PubStruct".to_string());
args.refs = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("impl PubStruct"),
"Defs should be filtered by --in:\n{output}"
);
assert!(
output.contains("// --- References ---"),
"Should have references section:\n{output}"
);
}
#[test]
fn test_code_refs_only_ignores_kind() {
let mut args = default_code_args();
args.args = vec!["test-fixture".into(), "struct".into(), "PubStruct".into()];
args.refs_only = true;
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains('*'),
"refs-only should find grep matches (kind ignored):\n{output}"
);
}
fn default_features_args() -> FeaturesArgs {
FeaturesArgs {
crate_name: "test-fixture".to_string(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
}
}
#[test]
fn test_features_local_renders_toml_section() {
let args = default_features_args();
let output = cargo_brief::run_features_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("[features]"),
"missing [features] header:\n{output}"
);
assert!(
output.contains("default = ["),
"missing default line:\n{output}"
);
}
#[test]
fn test_features_local_default_group() {
let args = default_features_args();
let output = cargo_brief::run_features_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("\"full\""),
"default should include 'full':\n{output}"
);
}
#[test]
fn test_features_local_named_features_alphabetical() {
let args = default_features_args();
let output = cargo_brief::run_features_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("experimental"),
"missing 'experimental':\n{output}"
);
assert!(output.contains("extra"), "missing 'extra':\n{output}");
assert!(output.contains("full"), "missing 'full':\n{output}");
let exp_pos = output.find("experimental = ").unwrap();
let extra_pos = output.find("extra = ").unwrap();
let full_pos = output.find("full = ").unwrap();
assert!(exp_pos < extra_pos, "experimental should come before extra");
assert!(extra_pos < full_pos, "extra should come before full");
}
#[test]
#[ignore]
fn test_features_remote_serde() {
let mut args = default_features_args();
args.crate_name = "serde@1".to_string();
args.manifest_path = None;
let mut remote = RemoteOpts::default();
remote.crates = true;
let output = cargo_brief::run_features_pipeline(&args, &remote).unwrap();
assert!(
output.contains("[features]"),
"remote features should have [features] header:\n{output}"
);
assert!(
output.contains("derive"),
"serde remote features should include 'derive':\n{output}"
);
}
fn cfg_items_args() -> ApiArgs {
let mut args = default_args();
args.target.module_path = Some("cfg_items".to_string());
args
}
#[test]
fn test_cfg_not_feature_annotation() {
let args = cfg_items_args();
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("#[cfg(not(feature = \"experimental\"))]"),
"NotFeatureGated should have #[cfg(not(...))] annotation:\n{output}"
);
}
#[test]
fn test_cfg_any_two_features_annotation() {
let args = cfg_items_args();
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
output.contains("#[cfg(any(feature = \"extra\", feature = \"experimental\"))]"),
"AnyTwoFeatures should have #[cfg(any(...))] annotation:\n{output}"
);
}
#[test]
fn test_cfg_no_gate_no_annotation() {
let args = cfg_items_args();
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
let lines: Vec<&str> = output.lines().collect();
for (i, line) in lines.iter().enumerate() {
if line.contains("struct NoGate") {
if i > 0 {
assert!(
!lines[i - 1].contains("#[cfg("),
"NoGate should not have a cfg annotation:\n{output}"
);
}
return;
}
}
assert!(
output.contains("struct NoGate"),
"NoGate should appear in cfg_items:\n{output}"
);
}
#[test]
fn test_cfg_no_feature_gates_flag_suppresses_annotations() {
let mut args = cfg_items_args();
args.filter.no_feature_gates = true;
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
assert!(
!output.contains("#[cfg(feature"),
"--no-feature-gates should suppress all #[cfg(feature...)] annotations:\n{output}"
);
}
#[test]
fn test_cfg_mixed_predicate_reconstructs() {
#[cfg(unix)]
{
let args = cfg_items_args();
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default()).unwrap();
if output.contains("MixedPredicate") {
assert!(
output.contains("#[cfg(all(feature = \"extra\", unix))]"),
"mixed predicate should reconstruct as #[cfg(all(...))]:\n{output}"
);
}
}
}
#[test]
fn test_features_offline_bails_with_user_error() {
let tmpdir = tempfile::tempdir().expect("tempdir");
let args = FeaturesArgs {
crate_name: "serde@1".to_string(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
manifest_path: None,
};
unsafe { std::env::set_var("CARGO_BRIEF_CACHE_DIR", tmpdir.path()) };
let mut remote = RemoteOpts::default();
remote.crates = true;
let result = cargo_brief::run_features_pipeline(&args, &remote);
let _ = result;
unsafe { std::env::remove_var("CARGO_BRIEF_CACHE_DIR") };
}
fn proc_macro_api_args() -> ApiArgs {
ApiArgs {
target: TargetArgs {
crate_name: "proc-macro-fixture".to_string(),
module_path: None,
at_package: None,
at_mod: None,
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
},
filter: default_filter(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
depth: 1,
recursive: true,
no_expand_glob: false,
}
}
fn proc_macro_summary_args() -> SummaryArgs {
SummaryArgs {
target: TargetArgs {
crate_name: "proc-macro-fixture".to_string(),
module_path: None,
at_package: None,
at_mod: None,
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
},
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
}
}
#[test]
fn test_proc_macro_api() {
let output = cargo_brief::run_api_pipeline(&proc_macro_api_args(), &RemoteOpts::default())
.expect("api pipeline failed for proc-macro-fixture");
assert!(
output.contains("#[proc_macro]\npub macro my_bang! {"),
"bang proc-macro should render #[proc_macro] and bang syntax:\n{output}"
);
assert!(
output.contains("#[proc_macro_attribute]\npub macro my_attr {"),
"attribute proc-macro should render without bang syntax:\n{output}"
);
assert!(
output.contains(
"#[proc_macro_derive(MyDerive, attributes(my_helper))]\npub macro MyDerive {"
),
"derive proc-macro should render helper attributes and derive syntax:\n{output}"
);
}
#[test]
fn test_proc_macro_summary_counts() {
let output =
cargo_brief::run_summary_pipeline(&proc_macro_summary_args(), &RemoteOpts::default())
.expect("summary pipeline failed for proc-macro-fixture");
assert!(
output.contains("1 proc_macros"),
"summary should count 1 bang proc-macro:\n{output}"
);
assert!(
output.contains("1 attr_macros"),
"summary should count 1 attribute proc-macro:\n{output}"
);
assert!(
output.contains("1 derive_macros"),
"summary should count 1 derive proc-macro:\n{output}"
);
}
#[test]
fn test_proc_macro_no_macros_flag() {
let mut args = proc_macro_api_args();
args.filter.no_macros = true;
let output = cargo_brief::run_api_pipeline(&args, &RemoteOpts::default())
.expect("api pipeline failed for proc-macro-fixture with --no-macros");
assert!(
!output.contains("#[proc_macro]"),
"--no-macros should suppress bang proc-macros:\n{output}"
);
assert!(
!output.contains("#[proc_macro_attribute]"),
"--no-macros should suppress attribute proc-macros:\n{output}"
);
assert!(
!output.contains("#[proc_macro_derive"),
"--no-macros should suppress derive proc-macros:\n{output}"
);
}
#[test]
fn test_proc_macro_search() {
let args = SearchArgs {
crate_name: "proc-macro-fixture".to_string(),
patterns: vec!["my".to_string()],
filter: default_filter(),
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
at_package: None,
at_mod: None,
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
limit: None,
methods_of: None,
search_kind: None,
members: false,
in_params: None,
in_returns: None,
};
let output = cargo_brief::run_search_pipeline(&args, &RemoteOpts::default())
.expect("search pipeline failed for proc-macro-fixture");
assert!(
output.contains("proc_macro") && output.contains("my_bang!"),
"bang proc-macro should appear as 'proc_macro ...my_bang!;':\n{output}"
);
assert!(
output.contains("attr_macro") && output.contains("#[") && output.contains("my_attr]"),
"attribute proc-macro should appear as 'attr_macro #[my_attr];':\n{output}"
);
assert!(
output.contains("derive_macro") && output.contains("MyDerive"),
"derive proc-macro should appear as 'derive_macro #[derive(MyDerive)];':\n{output}"
);
}
#[test]
fn test_proc_macro_code_kinds() {
let base_args = CodeArgs {
args: vec![],
global: GlobalArgs {
toolchain: "nightly".to_string(),
verbose: false,
},
manifest_path: Some("test_fixture/Cargo.toml".to_string()),
src_only: false,
no_deps: true,
all_deps: false,
limit: None,
quiet: false,
refs: false,
refs_only: false,
in_type: None,
};
for (kind, name) in [
("proc-macro", "my_bang"),
("attr-macro", "my_attr"),
("derive-macro", "my_derive"),
] {
let mut args = base_args.clone();
args.args = vec![
"proc-macro-fixture".to_string(),
kind.to_string(),
name.to_string(),
];
let output = cargo_brief::run_code_pipeline(&args, &RemoteOpts::default())
.unwrap_or_else(|e| panic!("code pipeline failed for {kind} {name}: {e}"));
assert!(
output.contains(name),
"code {kind} {name} should find function definition:\n{output}"
);
}
}