use std::env;
use std::path::PathBuf;
use std::process::Command;
use rusty_figlet::{Banner, Figlet, FigletBuilder, FigletError, Font, Justify};
use static_assertions::assert_impl_all;
assert_impl_all!(FigletBuilder: Send, Sync);
assert_impl_all!(Figlet: Send, Sync);
assert_impl_all!(Banner: Send, Sync);
assert_impl_all!(FigletError: Send, Sync);
#[test]
fn figlet_error_is_static() {
fn assert_static<T: 'static>() {}
assert_static::<FigletError>();
}
#[test]
fn default_features_off_excludes_cli_deps() {
let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let output = Command::new(&cargo)
.args([
"tree",
"--no-default-features",
"--prefix",
"none",
"--edges",
"normal",
"--no-dedupe",
])
.current_dir(manifest_dir)
.output()
.expect("invoke cargo tree");
assert!(
output.status.success(),
"cargo tree failed (status={:?}):\nstdout:\n{}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8(output.stdout).expect("cargo tree utf-8");
let lines: Vec<&str> = stdout.lines().collect();
assert!(
lines.iter().any(|l| l.starts_with("rusty-figlet ")),
"cargo tree missing rusty-figlet:\n{stdout}"
);
assert!(
lines.iter().any(|l| l.starts_with("thiserror ")),
"cargo tree missing thiserror:\n{stdout}"
);
let forbidden = [
"clap ",
"clap_complete ",
"anstyle ",
"termcolor ",
"terminal_size ",
];
for crate_prefix in forbidden {
assert!(
!lines.iter().any(|l| l.starts_with(crate_prefix)),
"forbidden CLI-only crate `{}` present in default-features=false dep tree:\n{stdout}",
crate_prefix.trim_end()
);
}
}
#[test]
fn library_builds_without_default_features() {
let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let output = Command::new(&cargo)
.args(["build", "--no-default-features", "--lib"])
.current_dir(manifest_dir)
.output()
.expect("invoke cargo build");
assert!(
output.status.success(),
"cargo build --no-default-features --lib failed (status={:?}):\nstdout:\n{}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn font_bytes_renders_with_zero_fs_calls() {
const STANDARD: &[u8] = include_bytes!("../assets/fonts/standard.flf");
let banner = FigletBuilder::new()
.font_bytes(STANDARD)
.build()
.expect("build via font_bytes")
.render("X")
.expect("render");
let lines: Vec<String> = banner.lines().collect();
assert!(
!lines.is_empty(),
"font_bytes path produced an empty banner"
);
}
#[test]
fn font_bytes_matches_bundled_lookup() {
const STANDARD: &[u8] = include_bytes!("../assets/fonts/standard.flf");
let via_bytes: Vec<String> = FigletBuilder::new()
.font_bytes(STANDARD)
.build()
.expect("build via font_bytes")
.render("X")
.expect("render")
.lines()
.collect();
let via_bundled: Vec<String> = FigletBuilder::new()
.font(Font::Standard)
.build()
.expect("build via Font::Standard")
.render("X")
.expect("render")
.lines()
.collect();
assert_eq!(
via_bytes, via_bundled,
"font_bytes path diverged from bundled-lookup path"
);
}
#[test]
fn non_exhaustive_match_with_wildcard_compiles() {
let err = FigletError::Internal("test");
let described = match &err {
FigletError::FontNotFound { .. } => "missing font",
FigletError::FontParse { .. } => "bad font file",
FigletError::Io(_) => "io error",
FigletError::WidthTooNarrow { .. } => "width too narrow",
FigletError::Internal(_) => "internal error",
_ => "unknown (additive variant)",
};
assert_eq!(described, "internal error");
}
#[test]
fn figlet_error_source_chain_per_variant() {
use std::error::Error;
use std::io;
let not_found = FigletError::FontNotFound {
name: "x".into(),
searched: Vec::new(),
};
assert!(not_found.source().is_none());
let parse = FigletError::FontParse {
reason: "x".into(),
line: 1,
};
assert!(parse.source().is_none());
let too_narrow = FigletError::WidthTooNarrow {
needed: 10,
given: 5,
};
assert!(too_narrow.source().is_none());
let internal = FigletError::Internal("x");
assert!(internal.source().is_none());
let io_err: FigletError = io::Error::other("boom").into();
assert!(io_err.source().is_some());
}
#[test]
fn banner_rows_are_height_by_width_bounded() {
let figlet = FigletBuilder::new()
.font(Font::Standard)
.width(80)
.build()
.expect("build");
let height = figlet.render("X").unwrap().height();
let banner = figlet.render("HelloWorld").unwrap();
let lines: Vec<String> = banner.lines().collect();
assert_eq!(
lines.len() as u32,
height,
"banner row count must equal font height, got {} for height {}",
lines.len(),
height
);
let max_row = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
assert!(
max_row < 10_000,
"rendered row width {max_row} is unreasonably large for a 10-char input"
);
let mut it = banner.lines();
let first = it.next();
assert!(first.is_some(), "first .next() must yield a row");
}
#[test]
fn banner_display_matches_lines_iterator_loop() {
let banner = FigletBuilder::new()
.font(Font::Standard)
.build()
.expect("build")
.render("Hi")
.expect("render");
let via_display = format!("{banner}");
let via_loop: String = banner.lines().fold(String::new(), |mut acc, l| {
use std::fmt::Write as _;
let _ = writeln!(&mut acc, "{l}");
acc
});
assert_eq!(
via_display, via_loop,
"Display impl must drive the same lazy iterator data as the manual loop"
);
assert!(
via_display.ends_with('\n'),
"Banner Display impl must emit a trailing newline; got `{via_display}`"
);
}
#[test]
fn figlet_builder_fluent_chain_returns_self() {
let figlet: Figlet = FigletBuilder::new()
.font(Font::Standard)
.font_dirs(Vec::<PathBuf>::new())
.width(80)
.kerning()
.full_width()
.smush()
.justify(Justify::Center)
.build()
.expect("fluent chain build");
let _banner: Banner = figlet.render("X").expect("render");
}
#[test]
fn figlet_builder_render_terminal_returns_result_banner() {
let banner: Banner = FigletBuilder::new()
.font(Font::Standard)
.render("X")
.expect("terminal render");
let _lines: Vec<String> = banner.lines().collect();
}
#[test]
fn figlet_builder_render_returns_lazy_banner() {
let banner = FigletBuilder::new()
.font(Font::Standard)
.width(80)
.build()
.expect("build")
.render("X")
.expect("render");
let mut it = banner.lines();
let first = it.next();
assert!(first.is_some(), "lazy iterator must yield ≥1 row for 'X'");
}