mod support;
use predicates::prelude::*;
use support::{lx, lx_no_colour};
#[test]
fn help_flag() {
lx().arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("personality"));
}
#[test]
fn help_short_flag() {
lx().arg("-?")
.assert()
.success()
.stdout(predicate::str::contains("--oneline"));
}
#[test]
fn version_flag() {
lx().arg("--version")
.assert()
.success()
.stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn version_short_flag() {
lx().arg("-v")
.assert()
.success()
.stdout(predicate::str::contains("lx"));
}
#[test]
fn unknown_short_flag() {
lx().arg("-Y")
.assert()
.failure()
.stderr(predicate::str::contains("error"));
}
#[test]
fn uppercase_f_groups_dirs_first() {
lx().arg("-F").assert().success();
}
#[test]
fn unknown_long_flag() {
lx().arg("--ternary")
.assert()
.failure()
.stderr(predicate::str::contains("error"));
}
#[test]
fn invalid_sort_value() {
lx().arg("--sort=colour")
.assert()
.failure()
.stderr(predicate::str::contains("invalid value"));
}
#[test]
fn removed_time_flag_is_rejected() {
lx().arg("--time=modified").assert().failure().stderr(
predicate::str::contains("unexpected argument")
.or(predicate::str::contains("unrecognized")),
);
}
#[test]
fn invalid_colour_value() {
lx().arg("--colour=upstream")
.assert()
.failure()
.stderr(predicate::str::contains("invalid value"));
}
#[test]
fn unknown_time_style_errors() {
lx_no_colour()
.args(["--time-style=24-hour", "-l", "Cargo.toml"])
.assert()
.failure()
.code(2)
.stderr(predicate::str::contains(
"invalid value '24-hour' for '--time-style",
))
.stderr(predicate::str::contains(
"[possible values: default, iso, long-iso, full-iso, relative, +FORMAT]",
));
}
#[test]
fn invalid_level_not_a_number() {
lx().arg("--level=abc")
.assert()
.failure()
.stderr(predicate::str::contains("invalid value"));
}
#[test]
fn time_tier_compounds() {
lx_no_colour()
.args(["-ltt", "Cargo.toml"])
.assert()
.success();
}
#[test]
fn tree_all_all_error() {
lx().args(["-Taa"])
.assert()
.failure()
.stderr(predicate::str::contains("--tree"));
}
#[test]
fn exit_code_success() {
lx().arg(".").assert().success();
}
#[test]
fn exit_code_options_error() {
lx().arg("--sort=nope").assert().code(2); }
#[test]
fn nonexistent_path_still_exits() {
lx().arg("/nonexistent/path/that/does/not/exist")
.assert()
.failure();
}
#[test]
fn list_current_directory() {
lx_no_colour()
.arg(".")
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn list_specific_file() {
lx_no_colour()
.arg("Cargo.toml")
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn oneline_view() {
lx_no_colour()
.args(["-1", "Cargo.toml", "Cargo.lock"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"))
.stdout(predicate::str::contains("Cargo.lock"));
}
#[test]
fn long_view() {
lx_no_colour()
.args(["-l", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn long_view_with_header() {
lx_no_colour()
.args(["-lh", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Permissions"))
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn grid_view() {
lx_no_colour()
.args(["-G", "."])
.env("COLUMNS", "80")
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn tree_view() {
lx_no_colour()
.args(["-T", "--level=1", "src"])
.assert()
.success()
.stdout(predicate::str::contains("main.rs"));
}
#[test]
fn recurse_view() {
lx_no_colour()
.args(["-R", "--level=1", "src"])
.assert()
.success()
.stdout(predicate::str::contains("main.rs"));
}
#[test]
fn long_grid_view() {
lx_no_colour()
.args(["-lG", "."])
.env("COLUMNS", "200")
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn sort_by_name() {
lx_no_colour()
.args(["-1", "--sort=name", "."])
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn sort_by_size() {
lx_no_colour()
.args(["-1", "--sort=size", "."])
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn reverse_sort() {
lx_no_colour()
.args(["-1r", "--sort=name", "."])
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn ignore_glob() {
lx_no_colour()
.args(["-1", "-I=*.lock", "."])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.lock").not());
}
#[test]
fn only_dirs() {
lx_no_colour()
.args(["-1D", "."])
.assert()
.success()
.stdout(predicate::str::contains("src"))
.stdout(predicate::str::contains("Cargo.toml").not());
}
#[test]
fn only_dirs_filters_cli_arguments() {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
std::fs::write(root.join("a_file.txt"), "x").unwrap();
std::fs::create_dir(root.join("a_dir")).unwrap();
lx_no_colour()
.current_dir(root)
.args(["-1dD", "a_file.txt", "a_dir"])
.assert()
.success()
.stdout(predicate::str::contains("a_dir"))
.stdout(predicate::str::contains("a_file.txt").not());
}
#[test]
fn only_files_filters_cli_arguments() {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
std::fs::write(root.join("a_file.txt"), "x").unwrap();
std::fs::create_dir(root.join("a_dir")).unwrap();
lx_no_colour()
.current_dir(root)
.args(["-1df", "a_file.txt", "a_dir"])
.assert()
.success()
.stdout(predicate::str::contains("a_file.txt"))
.stdout(predicate::str::contains("a_dir").not());
}
#[test]
fn binary_sizes() {
lx_no_colour()
.args(["-lb", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn byte_sizes() {
lx_no_colour()
.args(["-lB", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn uid_and_gid_columns() {
lx_no_colour()
.args(["-l", "--uid", "--gid", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn group_column() {
lx_no_colour()
.args(["-lg", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn inode_column() {
lx_no_colour()
.args(["-li", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn no_permissions() {
lx_no_colour()
.args(["-l", "--no-permissions", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn no_filesize() {
lx_no_colour()
.args(["-l", "--no-filesize", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn no_user() {
lx_no_colour()
.args(["-l", "--no-user", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn no_time() {
lx_no_colour()
.args(["-l", "--no-time", "Cargo.toml"])
.assert()
.success()
.stdout(predicate::str::contains("Cargo.toml"));
}
#[test]
fn colour_always() {
lx().args(["--colour=always", "-1", "Cargo.toml"])
.assert()
.success();
}
#[test]
fn colour_never() {
lx().args(["--colour=never", "-1", "Cargo.toml"])
.assert()
.success();
}
#[test]
fn colour_auto() {
lx().args(["--colour=auto", "-1", "Cargo.toml"])
.assert()
.success();
}
#[test]
fn no_color_env() {
lx().args(["-1", "Cargo.toml"])
.env("NO_COLOR", "1")
.assert()
.success();
}
#[test]
fn completions_bash() {
lx().arg("--completions=bash")
.assert()
.success()
.stdout(predicate::str::contains("_lx"));
}
#[test]
fn completions_zsh() {
lx().arg("--completions=zsh")
.assert()
.success()
.stdout(predicate::str::contains("#compdef lx"));
}
#[test]
fn completions_fish() {
lx().arg("--completions=fish")
.assert()
.success()
.stdout(predicate::str::contains("complete -c lx"));
}
#[test]
fn columns_env_controls_width() {
lx_no_colour()
.args(["-G", "."])
.env("COLUMNS", "40")
.assert()
.success();
}
#[test]
fn invalid_columns_env() {
lx_no_colour()
.args(["-G", "."])
.env("COLUMNS", "abc")
.assert()
.failure()
.stderr(predicate::str::contains("COLUMNS"));
}
#[test]
fn invalid_lx_grid_rows_env() {
lx_no_colour()
.args(["-lG", "."])
.env("LX_GRID_ROWS", "not-a-number")
.assert()
.failure()
.stderr(predicate::str::contains("LX_GRID_ROWS"));
}
#[test]
fn binary_without_long_is_fine() {
lx_no_colour().args(["--binary", "."]).assert().success();
}
#[test]
fn header_without_long_is_fine() {
lx_no_colour().args(["--header", "."]).assert().success();
}
#[test]
fn level_without_recurse_is_fine() {
lx_no_colour().args(["--level=3", "."]).assert().success();
}
#[test]
fn recurse_with_level_and_positional_path() {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
std::fs::create_dir_all(root.join("a/aa/aaa")).unwrap();
std::fs::write(root.join("a/aa/aaa/leaf"), b"").unwrap();
let assert = lx_no_colour()
.args(["-RL2", root.to_str().unwrap()])
.assert()
.success();
let stdout = String::from_utf8_lossy(&assert.get_output().stdout).to_string();
assert!(stdout.contains("aa"), "depth-2 should reach `aa`: {stdout}");
assert!(
!stdout.contains("aaa"),
"depth-2 should NOT reach `aaa`: {stdout}"
);
let assert = lx_no_colour()
.args(["-RL3", root.to_str().unwrap()])
.assert()
.success();
let stdout = String::from_utf8_lossy(&assert.get_output().stdout).to_string();
assert!(
stdout.contains("aaa"),
"depth-3 should reach `aaa`: {stdout}"
);
}
#[cfg(unix)]
#[test]
fn tree_follows_symlinked_positional_dir() {
let dir = tempfile::tempdir().expect("tempdir");
let target = dir.path().join("target");
let link = dir.path().join("link");
std::fs::create_dir(&target).unwrap();
std::fs::write(target.join("leaf"), b"").unwrap();
std::os::unix::fs::symlink(&target, &link).unwrap();
lx_no_colour()
.args(["-T", link.to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("leaf"));
}
#[test]
fn columns_env_ignored_when_not_tty() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("a"), b"").unwrap();
std::fs::write(dir.path().join("b"), b"").unwrap();
let assert = lx_no_colour()
.env("COLUMNS", "120")
.arg(dir.path())
.assert()
.success();
let stdout = String::from_utf8_lossy(&assert.get_output().stdout);
assert_eq!(
stdout.lines().count(),
2,
"piped output should be one entry per line, got: {stdout:?}"
);
}
#[test]
fn explicit_width_forces_grid_on_pipe() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("a"), b"").unwrap();
std::fs::write(dir.path().join("b"), b"").unwrap();
let assert = lx_no_colour()
.args(["--width=120"])
.arg(dir.path())
.assert()
.success();
let stdout = String::from_utf8_lossy(&assert.get_output().stdout);
assert_eq!(
stdout.lines().count(),
1,
"--width should force a grid even on a pipe, got: {stdout:?}"
);
}