Files
wanderbooks/crates/ledger-gui/tests/breadcrumbs.rs
T
swaitsandClaude Opus 5.5 a833229d87 tests: every test dir is removed when its test ends, pass or panic
Many test helpers removed their temp dir only before the test, or only
on pass, so runs left them behind: 117,000 filled /tmp (RAM here) and
stopped a mutation run. Each now holds a guard that removes the dir on
drop, unwind included; servers started on a test dir hold its guard
until they stop. A full cargo test --workspace leaves 0 dirs (859
before). just sweep-test-tmp stays for runs killed mid-test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-17 20:47:44 -06:00

120 lines
4.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The breadcrumb links back up: every crumb but the page you're on goes
//! where it names.
//!
//! ```text
//! GOAL: from Parties › Stephen Waits, clicking "Parties" goes back to the
//! directory; deeper trails link each level.
//! SOURCE: DECISION (Stephen, 2026-10-16: "I should be able to click
//! parties and it takes me back there ... when it goes deeper").
//! INVARIANTS: palette::crumbs 1-3, and
//! 4. Clicking a crumb goes to its route (a history step, so Back
//! returns).
//! ```
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use ledger_gui::nav::{ReportKind, Route, Section};
use ledger_gui::palette::{crumbs, describe};
use ledger_gui::{App, Source, data};
/// A test's dir, removed when the test ends, pass or panic.
struct TestDir(std::path::PathBuf);
impl Drop for TestDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
/// The demo books, kept while the guard lives.
fn demo(tag: &str) -> (TestDir, Source) {
let dir = std::env::temp_dir().join(format!("ledger-gui-crumbs-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let today = chrono::NaiveDate::from_ymd_opt(2026, 10, 12).unwrap();
ledger_gui::demo::build(&dir, today).unwrap();
let source = Source {
data_dir: dir.join("books-data"),
books_toml: Some(dir.join("wanderbooks.toml")),
today: Some(today),
};
(TestDir(dir), source)
}
/// INVARIANTS 1-3 on every kind of page in the demo books.
#[test]
fn crumbs_link_every_level_but_the_last() {
let (_dir, source) = demo("unit");
let b = data::load_once(&source).unwrap();
let party = b.ledger.parties().next().unwrap().id;
let rec = b.reconciliations.first().expect("the demo reconciles");
let entry = b.entries.first().unwrap().id;
let mut routes = vec![
Route::Party(party),
Route::Reconciliation(rec.id),
Route::Entry(entry),
Route::Reports(ReportKind::ProfitAndLoss),
Route::Reports(ReportKind::BalanceSheet),
];
routes.extend(Section::ALL.iter().map(|s| s.home()));
for r in &routes {
let c = crumbs(Some(&b), r);
assert_eq!(c[0].0, r.section().title(), "{r:?}");
assert!(c.last().unwrap().1.is_none(), "{r:?}: the page you're on");
for (_, to) in &c {
assert_ne!(to.as_ref(), Some(r), "{r:?} links to itself");
}
// Every crumb above is a link, except one naming this very page
// (Reports on Profit and loss, the Reports section's home).
for (i, (_, to)) in c[..c.len() - 1].iter().enumerate() {
let home_here = i == 0 && r.section().home() == *r;
assert_eq!(to.is_some(), !home_here, "{r:?}: crumb {i}");
}
let here = describe(&b, r);
if !here.is_empty() {
assert_eq!(c.last().unwrap().0, here);
}
}
assert_eq!(
crumbs(Some(&b), &Route::Party(party)),
[
("Parties".to_string(), Some(Route::Parties)),
(b.party_label(party), None),
]
);
// A reconciliation sits under its account.
let account = Route::Account(rec.account_id);
let c = crumbs(Some(&b), &Route::Reconciliation(rec.id));
assert_eq!(c.len(), 3);
assert_eq!(c[0].1, Some(Route::Accounts));
assert_eq!(c[1], (describe(&b, &account), Some(account)));
// A section's own page is one crumb, not a link.
assert_eq!(
crumbs(Some(&b), &Route::Parties),
[("Parties".to_string(), None)]
);
// P&L is the Reports section's home, so "Reports" there isn't a link.
assert_eq!(
crumbs(Some(&b), &Route::Reports(ReportKind::ProfitAndLoss))[0].1,
None
);
}
/// INVARIANT 4, through the top bar.
#[test]
fn clicking_a_crumb_goes_there() {
let (_dir, source) = demo("click");
let b = data::load_once(&source).unwrap();
let rec = b.reconciliations.first().unwrap().clone();
let account = describe(&b, &Route::Account(rec.account_id));
let mut h = Harness::builder()
.with_size(egui::vec2(1280.0, 900.0))
.build_eframe(|cc| App::with_books(&cc.egui_ctx, b, false, Route::Reconciliation(rec.id)));
h.run_steps(3);
h.get_all_by_label(&account).next().unwrap().click();
h.run_steps(3);
assert_eq!(h.state().route(), &Route::Account(rec.account_id));
h.get_by_label("Accounts").click();
h.run_steps(3);
assert_eq!(h.state().route(), &Route::Accounts);
}