use assert_cmd::Command;
use predicates::prelude::*;
fn cal() -> Command {
Command::cargo_bin("cal").unwrap()
}
#[test]
fn january_2024_header() {
cal()
.args(["1", "2024"])
.assert()
.success()
.stdout(predicate::str::contains("January 2024"));
}
#[test]
fn january_2024_day_layout() {
let output = cal().args(["1", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Su Mo Tu We Th Fr Sa"));
let lines: Vec<&str> = stdout.lines().collect();
let first_week = lines.iter().find(|l| l.trim().starts_with('1')).unwrap();
assert!(
first_week.starts_with(" 1"),
"Expected first week to start with blank Sunday then 1, got: '{first_week}'"
);
}
#[test]
fn january_2024_has_31_days() {
cal()
.args(["1", "2024"])
.assert()
.success()
.stdout(predicate::str::contains("31"));
}
#[test]
fn february_2024_leap_year() {
cal()
.args(["2", "2024"])
.assert()
.success()
.stdout(predicate::str::contains("February 2024"))
.stdout(predicate::str::contains("29"));
}
#[test]
fn february_2023_not_leap_year() {
let output = cal().args(["2", "2023"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("February 2023"));
assert!(stdout.contains("28"));
let has_29_as_day = stdout.lines().any(|line| {
if line.contains("February")
|| line.contains("Su")
|| line.contains("Mo Tu")
{
return false;
}
line.split_whitespace().any(|tok| tok == "29")
});
assert!(!has_29_as_day, "February 2023 should not contain day 29");
}
#[test]
fn full_year_2024() {
let output = cal().args(["-y", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("2024"));
assert!(stdout.contains("January"));
assert!(stdout.contains("February"));
assert!(stdout.contains("March"));
assert!(stdout.contains("April"));
assert!(stdout.contains("May"));
assert!(stdout.contains("June"));
assert!(stdout.contains("July"));
assert!(stdout.contains("August"));
assert!(stdout.contains("September"));
assert!(stdout.contains("October"));
assert!(stdout.contains("November"));
assert!(stdout.contains("December"));
}
#[test]
fn year_only_shows_full_year() {
let output = cal().args(["2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("January"));
assert!(stdout.contains("December"));
}
#[test]
fn monday_first() {
cal()
.args(["-m", "1", "2024"])
.assert()
.success()
.stdout(predicate::str::contains("Mo Tu We Th Fr Sa Su"));
}
#[test]
fn monday_first_day_alignment() {
let output = cal().args(["-m", "1", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.lines().collect();
let first_week = lines.iter().find(|l| l.trim().starts_with('1')).unwrap();
assert!(
first_week.starts_with(" 1"),
"Expected first week to start with ' 1' (Monday column), got: '{first_week}'"
);
}
#[test]
fn sunday_first_explicit() {
cal()
.args(["-s", "1", "2024"])
.assert()
.success()
.stdout(predicate::str::contains("Su Mo Tu We Th Fr Sa"));
}
#[test]
fn three_month_view() {
let output = cal().args(["-3", "6", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("May 2024"));
assert!(stdout.contains("June 2024"));
assert!(stdout.contains("July 2024"));
}
#[test]
fn three_month_january_wraps() {
let output = cal().args(["-3", "1", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("December 2023"));
assert!(stdout.contains("January 2024"));
assert!(stdout.contains("February 2024"));
}
#[test]
fn three_month_december_wraps() {
let output = cal().args(["-3", "12", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("November 2024"));
assert!(stdout.contains("December 2024"));
assert!(stdout.contains("January 2025"));
}
#[test]
fn julian_flag() {
let output = cal().args(["-j", "1", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("January 2024"));
assert!(
stdout.contains("31"),
"January should contain ordinal day 31"
);
}
#[test]
fn julian_february_ordinals() {
let output = cal().args(["-j", "2", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("32"), "Feb 1 should be ordinal day 32");
assert!(stdout.contains("60"), "Feb 29 should be ordinal day 60");
}
#[test]
fn n_months_flag() {
let output = cal().args(["-n", "4", "3", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("March 2024"));
assert!(stdout.contains("April 2024"));
assert!(stdout.contains("May 2024"));
assert!(stdout.contains("June 2024"));
}
#[test]
fn month_name_argument() {
cal()
.args(["jan", "2024"])
.assert()
.success()
.stdout(predicate::str::contains("January 2024"));
}
#[test]
fn month_name_full() {
cal()
.args(["january", "2024"])
.assert()
.success()
.stdout(predicate::str::contains("January 2024"));
}
#[test]
fn invalid_month_fails() {
cal().args(["13", "2024"]).assert().failure();
}
#[test]
fn too_many_args_fails() {
cal().args(["1", "2", "3", "4"]).assert().failure();
}
#[test]
fn september_1752_renders() {
cal()
.args(["9", "1752"])
.assert()
.success()
.stdout(predicate::str::contains("September 1752"));
}
#[test]
fn day_alignment_march_2024() {
let output = cal().args(["3", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.lines().collect();
let first_week = lines.iter().find(|l| l.trim().starts_with('1')).unwrap();
assert!(
first_week.contains(" 1 2"),
"March 2024 first week should have 1 (Fri) and 2 (Sat) together, got: '{first_week}'"
);
}
#[test]
fn columns_flag() {
let output = cal().args(["-c", "4", "-y", "2024"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.lines().collect();
let has_four_months_row = lines
.iter()
.any(|l| l.contains("January") && l.contains("April"));
assert!(
has_four_months_row,
"With 4 columns, January and April should be on the same row"
);
}