#![ cfg( feature = "output" ) ]
use cli_fmt::output::*;
use strs_tools::string::lines::*;
#[ test ]
fn output_config_default_is_no_processing()
{
let config = OutputConfig::default();
assert!( config.is_default() );
assert!( !config.has_processing() );
}
#[ test ]
fn output_config_with_head_has_processing()
{
let config = OutputConfig::default().with_head( 5 );
assert!( !config.is_default() );
assert!( config.has_processing() );
}
#[ test ]
fn output_config_with_tail_has_processing()
{
let config = OutputConfig::default().with_tail( 5 );
assert!( config.has_processing() );
}
#[ test ]
fn output_config_with_width_has_processing()
{
let config = OutputConfig::default().with_width( 80 );
assert!( config.has_processing() );
}
#[ test ]
fn select_streams_both()
{
let config = OutputConfig::default();
let result = process_output( "stdout", "stderr", &config );
assert_eq!( result.content, "stderr\nstdout" );
}
#[ test ]
fn select_streams_stdout_only()
{
let config = OutputConfig::default()
.with_stream_filter( StreamFilter::Stdout );
let result = process_output( "stdout", "stderr", &config );
assert_eq!( result.content, "stdout" );
}
#[ test ]
fn select_streams_stderr_only()
{
let config = OutputConfig::default()
.with_stream_filter( StreamFilter::Stderr );
let result = process_output( "stdout", "stderr", &config );
assert_eq!( result.content, "stderr" );
}
#[ test ]
fn select_streams_empty_stdout()
{
let config = OutputConfig::default();
let result = process_output( "", "stderr", &config );
assert_eq!( result.content, "stderr" );
}
#[ test ]
fn select_streams_empty_stderr()
{
let config = OutputConfig::default();
let result = process_output( "stdout", "", &config );
assert_eq!( result.content, "stdout" );
}
#[ test ]
fn select_streams_both_empty()
{
let config = OutputConfig::default();
let result = process_output( "", "", &config );
assert_eq!( result.content, "" );
assert_eq!( result.lines_omitted, 0 );
assert!( !result.width_truncated );
}
#[ test ]
fn head_basic()
{
let text = "line1\nline2\nline3\nline4\nline5";
let result = head( text, 2 );
assert_eq!( result, "line1\nline2" );
}
#[ test ]
fn head_exceeds_total()
{
let text = "line1\nline2\nline3";
let result = head( text, 10 );
assert_eq!( result, "line1\nline2\nline3" );
}
#[ test ]
fn head_exact()
{
let text = "line1\nline2\nline3";
let result = head( text, 3 );
assert_eq!( result, "line1\nline2\nline3" );
}
#[ test ]
fn head_empty()
{
let text = "";
let result = head( text, 5 );
assert_eq!( result, "" );
}
#[ test ]
fn tail_basic()
{
let text = "line1\nline2\nline3\nline4\nline5";
let result = tail( text, 2 );
assert_eq!( result, "line4\nline5" );
}
#[ test ]
fn tail_exceeds_total()
{
let text = "line1\nline2\nline3";
let result = tail( text, 10 );
assert_eq!( result, "line1\nline2\nline3" );
}
#[ test ]
fn tail_exact()
{
let text = "line1\nline2\nline3";
let result = tail( text, 3 );
assert_eq!( result, "line1\nline2\nline3" );
}
#[ test ]
fn tail_empty()
{
let text = "";
let result = tail( text, 5 );
assert_eq!( result, "" );
}
#[ test ]
fn head_tail_combined_no_overlap()
{
let config = OutputConfig::default()
.with_head( 2 )
.with_tail( 2 );
let result = process_output( "line1\nline2\nline3\nline4\nline5", "", &config );
assert_eq!( result.content, "line1\nline2\nline4\nline5" );
assert_eq!( result.lines_omitted, 1 );
}
#[ test ]
fn head_tail_overlap_shows_all()
{
let config = OutputConfig::default()
.with_head( 3 )
.with_tail( 3 );
let result = process_output( "line1\nline2\nline3\nline4\nline5", "", &config );
assert_eq!( result.content, "line1\nline2\nline3\nline4\nline5" );
assert_eq!( result.lines_omitted, 0 );
}
#[ test ]
fn head_tail_exact_fit()
{
let config = OutputConfig::default()
.with_head( 2 )
.with_tail( 1 );
let result = process_output( "line1\nline2\nline3", "", &config );
assert_eq!( result.content, "line1\nline2\nline3" );
assert_eq!( result.lines_omitted, 0 );
}
#[ test ]
fn width_no_truncation_needed()
{
let config = OutputConfig::default().with_width( 50 );
let result = process_output( "short line", "", &config );
assert!( result.content.starts_with( "short line" ) );
assert!( !result.width_truncated );
}
#[ test ]
fn width_truncation_with_arrow()
{
let config = OutputConfig::default().with_width( 10 );
let result = process_output( "this is a very long line that needs truncation", "", &config );
assert!( result.width_truncated );
assert!( result.content.contains( "→" ) );
assert!( result.content.len() < 50 );
}
#[ test ]
fn width_zero_disables()
{
let config = OutputConfig::default().with_width( 0 );
let result = process_output( "this is a very long line", "", &config );
assert_eq!( result.content, "this is a very long line" );
assert!( !result.width_truncated );
}
#[ test ]
fn width_custom_suffix()
{
let config = OutputConfig::default()
.with_width( 10 )
.with_suffix( "..." );
let result = process_output( "this is a very long line", "", &config );
assert!( result.width_truncated );
assert!( result.content.contains( "..." ) );
}
#[ test ]
fn width_exact_boundary()
{
let config = OutputConfig::default().with_width( 10 );
let result = process_output( "0123456789", "", &config );
assert!( result.content.starts_with( "0123456789" ) );
assert!( !result.width_truncated );
}
#[ test ]
fn ansi_preserved_when_no_truncation()
{
let config = OutputConfig::default().with_width( 50 );
let input = "\x1b[31mred text\x1b[0m";
let result = process_output( input, "", &config );
assert!( result.content.starts_with( input ) );
assert!( result.content.contains( "\x1b[31m" ) );
}
#[ test ]
fn ansi_preserved_with_truncation()
{
let config = OutputConfig::default().with_width( 8 );
let input = "\x1b[31mred text that is very long\x1b[0m";
let result = process_output( input, "", &config );
assert!( result.content.contains( "\x1b[31m" ) );
assert!( result.width_truncated );
}
#[ test ]
fn combined_head_and_width()
{
let config = OutputConfig::default()
.with_head( 2 )
.with_width( 10 );
let input = "line1 is very long\nline2 is also long\nline3\nline4";
let result = process_output( input, "", &config );
assert_eq!( result.lines_omitted, 2 );
assert!( result.width_truncated );
let lines : Vec< &str > = result.content.lines().collect();
assert_eq!( lines.len(), 2 );
}
#[ test ]
fn combined_tail_and_width()
{
let config = OutputConfig::default()
.with_tail( 2 )
.with_width( 10 );
let input = "line1\nline2\nline3 is very long\nline4 is also long";
let result = process_output( input, "", &config );
assert_eq!( result.lines_omitted, 2 );
let lines : Vec< &str > = result.content.lines().collect();
assert_eq!( lines.len(), 2 );
assert!(
result.content.contains( "line3" ),
"tail(2) must retain line3 (3rd line), got:\n{}", result.content
);
assert!(
result.content.contains( "line4" ),
"tail(2) must retain line4 (4th line), got:\n{}", result.content
);
assert!(
!result.content.contains( "line1" ),
"tail(2) must drop line1 (1st line), got:\n{}", result.content
);
assert!(
!result.content.contains( "line2" ),
"tail(2) must drop line2 (2nd line), got:\n{}", result.content
);
}
#[ test ]
fn combined_streams_head_width()
{
let config = OutputConfig::default()
.with_head( 3 )
.with_width( 15 );
let result = process_output( "out1\nout2 is long\nout3", "err1\nerr2 is also long", &config );
assert!( result.content.starts_with( "err1" ) );
let lines : Vec< &str > = result.content.lines().collect();
assert_eq!( lines.len(), 3 ); }
#[ test ]
fn merge_streams_ordering()
{
let result = merge_streams( "stdout", "stderr", &StreamFilter::Both );
assert_eq!( result, "stderr\nstdout" );
}
#[ test ]
fn process_output_head_lines_omitted()
{
let config = OutputConfig::default().with_head( 2 );
let result = process_output( "line1\nline2\nline3\nline4\nline5", "", &config );
assert_eq!( result.lines_omitted, 3 );
assert_eq!( result.content, "line1\nline2" );
}
#[ test ]
fn is_default_stream_filter()
{
let config = OutputConfig::default().with_stream_filter( StreamFilter::Stdout );
assert!(
!config.is_default(),
"config with stream_filter=Stdout must not satisfy is_default()"
);
}
#[ test ]
fn is_default_width_suffix()
{
let config = OutputConfig::default().with_suffix( "..." );
assert!(
!config.is_default(),
"config with width_suffix='...' must not satisfy is_default()"
);
}
#[ test ]
fn is_default_unicode_aware()
{
let config = OutputConfig::default().with_unicode_aware( true );
assert!(
!config.is_default(),
"config with unicode_aware=true must not satisfy is_default()"
);
}
#[ test ]
fn is_default_tail()
{
let config = OutputConfig::default().with_tail( 2 );
assert!(
!config.is_default(),
"config with tail=2 must not satisfy is_default()"
);
}
#[ test ]
fn is_default_width()
{
let config = OutputConfig::default().with_width( 5 );
assert!(
!config.is_default(),
"config with width=5 must not satisfy is_default()"
);
}
#[ test ]
fn head_zero_produces_empty()
{
let config = OutputConfig::default().with_head( 0 );
let result = process_output( "a\nb\nc", "", &config );
assert!(
result.content.is_empty(),
"head(0) must produce empty content, got:\n{:?}", result.content
);
assert_eq!(
result.lines_omitted, 3,
"head(0) on 3-line input must report 3 lines omitted"
);
}
#[ test ]
fn tail_zero_produces_empty()
{
let config = OutputConfig::default().with_tail( 0 );
let result = process_output( "a\nb\nc", "", &config );
assert!(
result.content.is_empty(),
"tail(0) must produce empty content, got:\n{:?}", result.content
);
assert_eq!(
result.lines_omitted, 3,
"tail(0) on 3-line input must report 3 lines omitted"
);
}
#[ test ]
fn width_one_truncates()
{
let config = OutputConfig::default().with_width( 1 );
let result = process_output( "hello", "", &config );
assert!(
result.width_truncated,
"width=1 must trigger truncation for 'hello' (5 visible chars), got:\n{:?}", result.content
);
}
#[ test ]
fn unicode_aware_truncation()
{
let config = OutputConfig::default()
.with_unicode_aware( true )
.with_width( 3 );
let result = process_output( "café", "", &config );
assert!(
result.width_truncated,
"unicode_aware=true must trigger truncation when visual width (4) > max_width (3), got:\n{:?}", result.content
);
}
#[ test ]
fn merge_streams_stderr_trailing_newline()
{
let result = merge_streams( "out", "err\n", &StreamFilter::Both );
assert_eq!(
result, "err\nout",
"stderr with trailing newline must not produce double-newline separator, got:\n{result:?}"
);
assert!(
!result.contains( "\n\n" ),
"result must not contain double-newline sequence, got:\n{result:?}"
);
}
#[ test ]
fn merge_streams_both_trailing_newlines_no_double_newline()
{
let result = merge_streams( "out\n", "err\n", &StreamFilter::Both );
assert_eq!(
result, "err\nout\n",
"both streams with trailing newlines must not produce double-newline, got:\n{result:?}"
);
assert!(
!result.contains( "\n\n" ),
"result must not contain double-newline sequence, got:\n{result:?}"
);
}
#[ test ]
fn merge_streams_stdout_trailing_newline_separator()
{
let result = merge_streams( "out\n", "err", &StreamFilter::Both );
assert_eq!(
result, "err\nout\n",
"stderr without trailing newline must get exactly one newline separator, got:\n{result:?}"
);
}
#[ test ]
fn output_config_new_matches_default()
{
let from_new = OutputConfig::new();
let from_default = OutputConfig::default();
assert_eq!( from_new.head, from_default.head, "head field must match" );
assert_eq!( from_new.tail, from_default.tail, "tail field must match" );
assert_eq!( from_new.width, from_default.width, "width field must match" );
assert_eq!( from_new.width_suffix, from_default.width_suffix, "width_suffix field must match" );
assert_eq!( from_new.stream_filter, from_default.stream_filter, "stream_filter field must match" );
assert_eq!( from_new.unicode_aware, from_default.unicode_aware, "unicode_aware field must match" );
assert!(
from_new.is_default(),
"OutputConfig::new() must satisfy is_default()"
);
}