use cli_fmt::help::*;
fn two_group_data() -> CliHelpData
{
CliHelpData
{
binary : "myapp".into(),
tagline : "example tool".into(),
groups : vec!
[
CommandGroup
{
name : "Group A".into(),
entries : vec!
[
CommandEntry { name : "cmd-one".into(), desc : "first command".into() },
CommandEntry { name : "cmd-two".into(), desc : "second command".into() },
],
},
CommandGroup
{
name : "Group B".into(),
entries : vec!
[
CommandEntry { name : "cmd-three".into(), desc : "third command".into() },
CommandEntry { name : "cmd-four".into(), desc : "fourth command".into() },
],
},
],
options : vec!
[
OptionEntry { name : "format::text|json".into(), desc : "Output format".into() },
OptionEntry { name : "dry::bool".into(), desc : "Dry-run".into() },
],
examples : vec!
[
ExampleEntry { invocation : "myapp cmd-one".into(), desc : Some( "run one".into() ) },
ExampleEntry { invocation : "myapp cmd-two".into(), desc : None },
],
}
}
fn no_tty_style() -> CliHelpStyle
{
CliHelpStyle { tty_detect : false, ..CliHelpStyle::default() }
}
#[ test ]
fn test_column_alignment()
{
let tmpl = CliHelpTemplate::new( no_tty_style(), two_group_data() );
let out = tmpl.render();
assert!(
out.contains( "cmd-one " ),
"cmd-one must be padded to 20 chars (cmd_name_width), got:\n{out}",
);
assert!(
out.contains( "dry::bool " ),
"dry::bool must be padded to 18 chars (opt_name_width), got:\n{out}",
);
assert!(
!out.contains( "\x1b[" ),
"no ANSI codes in no-TTY mode, got:\n{out}",
);
}
#[ test ]
fn test_no_ansi_codes()
{
let tmpl = CliHelpTemplate::new( no_tty_style(), two_group_data() );
let out = tmpl.render();
assert!(
!out.contains( "\x1b[" ),
"ANSI escape sequences must be absent with tty_detect=false, got:\n{out}",
);
}
#[ test ]
fn test_explicit_tty_detect_false()
{
let style = CliHelpStyle { tty_detect : false, ..CliHelpStyle::default() };
let tmpl = CliHelpTemplate::new( style, two_group_data() );
let out = tmpl.render();
assert!(
!out.contains( "\x1b[" ),
"explicit tty_detect=false must suppress all ANSI codes, got:\n{out}",
);
assert!( out.contains( "Group A" ), "group header must appear in output, got:\n{out}" );
assert!( out.contains( "cmd-one" ), "command name must appear in output, got:\n{out}" );
}
#[ test ]
fn test_name_not_truncated()
{
let style = CliHelpStyle { cmd_name_width : 10, tty_detect : false, ..CliHelpStyle::default() };
let data = CliHelpData
{
binary : "app".into(),
tagline : "test".into(),
groups : vec![ CommandGroup
{
name : "G".into(),
entries : vec![ CommandEntry { name : "eleven-char".into(), desc : "desc".into() } ],
}],
options : vec![],
examples : vec![],
};
let out = CliHelpTemplate::new( style, data ).render();
assert!(
out.contains( "eleven-char" ),
"11-char name must not be truncated when cmd_name_width=10, got:\n{out}",
);
}
#[ test ]
fn test_no_options_section()
{
let mut data = two_group_data();
data.options = vec![];
let out = CliHelpTemplate::new( no_tty_style(), data ).render();
assert!(
!out.contains( "Options:" ),
"Options section must be absent when options vec is empty, got:\n{out}",
);
}
#[ test ]
fn test_no_examples_section()
{
let mut data = two_group_data();
data.examples = vec![];
let out = CliHelpTemplate::new( no_tty_style(), data ).render();
assert!(
!out.contains( "Examples:" ),
"Examples section must be absent when examples vec is empty, got:\n{out}",
);
}
#[ test ]
fn test_single_group_binary_name()
{
let data = CliHelpData
{
binary : "myapp".into(),
tagline : "a single-group tool".into(),
groups : vec![ CommandGroup
{
name : "Cmds".into(),
entries : vec![ CommandEntry { name : "run".into(), desc : "run it".into() } ],
}],
options : vec![],
examples : vec![],
};
let out = CliHelpTemplate::new( no_tty_style(), data ).render();
assert!( out.contains( "Usage: myapp" ), "Usage: header with binary must appear, got:\n{out}" );
assert!( out.contains( "Commands:" ), "Commands: header must appear, got:\n{out}" );
assert!( out.contains( "Cmds" ), "group header must appear in output, got:\n{out}" );
assert!( out.contains( "run" ), "command name must appear in output, got:\n{out}" );
assert!(
!out.contains( "\x1b[" ),
"no ANSI codes with tty_detect=false, got:\n{out}",
);
}
#[ test ]
fn test_style_default_fields()
{
let s = CliHelpStyle::default();
assert_eq!( s.cmd_indent, 4 );
assert_eq!( s.cmd_name_width, 20 );
assert_eq!( s.grp_indent, 2 );
assert_eq!( s.opt_indent, 2 );
assert_eq!( s.opt_name_width, 18 );
assert_eq!( s.col_gap, 2 );
assert_eq!( s.example_indent, 2 );
}
#[ test ]
fn test_example_desc_rendered()
{
let out = CliHelpTemplate::new( no_tty_style(), two_group_data() ).render();
let first_line = out.lines()
.find( |l| l.contains( "myapp cmd-one" ) )
.unwrap_or_default();
assert!(
first_line.contains( "# run one" ),
"ExampleEntry with desc=Some must render '# run one' on the invocation line, got:\n{first_line:?}",
);
let second_line = out.lines()
.find( |l| l.contains( "myapp cmd-two" ) )
.unwrap_or_default();
assert!(
!second_line.contains( '#' ),
"ExampleEntry with desc=None must not render '#' on the invocation line, got:\n{second_line:?}",
);
}