P3: rename accounts, one set of period defaults, tax categories warn or refuse (defects 15, 16, 8)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
books.rename_account; account names trimmed, bounded, refused when a sibling's or TIN-shaped; books.show refuses what its target ignores. A report asked for without dates covers this fiscal year so far, everywhere (one rule for tools, descriptions and the viewer; the IIF and CPA package keep their own dates). policy.require_tax_category warns or refuses; a Form 4797 gain is shown apart on every return. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
69 files changed
+8063
-570
No files matched your search
@@ -118,3 +118,194 @@ fn show_requests_navigate_the_viewer() {
|
||||
});
|
||||
assert_eq!(h.state_mut().route(), &Route::Transactions);
|
||||
}
|
||||
|
||||
/// The demo books (calendar fiscal year) in a viewer, at `today`.
|
||||
fn demo(
|
||||
tag: &str,
|
||||
today: chrono::NaiveDate,
|
||||
) -> (TestDir, Harness<'static, App>, ledger_gui::books::Books) {
|
||||
demo_sized(tag, today, 920.0)
|
||||
}
|
||||
|
||||
/// [`demo`] in a window `height` tall (the register's date row sits below
|
||||
/// an account's cards: tall enough to click it).
|
||||
fn demo_sized(
|
||||
tag: &str,
|
||||
today: chrono::NaiveDate,
|
||||
height: f32,
|
||||
) -> (TestDir, Harness<'static, App>, ledger_gui::books::Books) {
|
||||
let dir = std::env::temp_dir().join(format!("ledger-gui-show-{tag}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let guard = TestDir(dir.clone());
|
||||
ledger_gui::demo::build(&dir, today).unwrap();
|
||||
let source = Source {
|
||||
data_dir: dir.join("books-data"),
|
||||
today: Some(today),
|
||||
};
|
||||
let books = data::load_once(&source).unwrap();
|
||||
let copy = data::load_once(&source).unwrap();
|
||||
let h = Harness::builder()
|
||||
.with_size(egui::vec2(1440.0, height))
|
||||
.build_eframe(move |cc| App::with_books(&cc.egui_ctx, books, true, Route::Overview));
|
||||
(guard, h, copy)
|
||||
}
|
||||
|
||||
fn day(y: i32, m: u32, d: u32) -> chrono::NaiveDate {
|
||||
chrono::NaiveDate::from_ymd_opt(y, m, d).unwrap()
|
||||
}
|
||||
|
||||
/// Defect 15, repros 1.1.1-1.1.2 (rules.md B-30): a target the viewer
|
||||
/// can't honour is ignored whole, never swapped or guessed: a start after
|
||||
/// the end (an earlier writer's, or a hand-edited ops/show.jsonl), text
|
||||
/// that isn't a date, an unknown split or comparison. The page, the
|
||||
/// report's range and split, and the register's range stay as they were.
|
||||
#[test]
|
||||
fn show_never_swaps_an_inverted_target_defect_15() {
|
||||
use ledger_gui::pages::accounts::RegisterRange;
|
||||
use ledger_gui::pages::reports::{Columns, Compare, Range};
|
||||
let (_dir, mut h, _) = demo("inverted", day(2026, 10, 20));
|
||||
h.state_mut().state_mut().reports.range = Range::LastMonth;
|
||||
h.state_mut().state_mut().reports.columns = Columns::Quarter;
|
||||
h.state_mut().state_mut().accounts.range = RegisterRange::All;
|
||||
let report =
|
||||
|start: Option<&str>, end: Option<&str>, columns: Option<&str>, compare: Option<&str>| {
|
||||
ViewerTarget::Report {
|
||||
report: "profit_and_loss".into(),
|
||||
start: start.map(Into::into),
|
||||
end: end.map(Into::into),
|
||||
columns: columns.map(Into::into),
|
||||
compare: compare.map(Into::into),
|
||||
}
|
||||
};
|
||||
for target in [
|
||||
report(Some("2026-12-31"), Some("2026-01-01"), None, None),
|
||||
report(Some("2027-01-01"), None, None, None),
|
||||
report(Some("January"), None, None, None),
|
||||
report(None, Some("2026-13-01"), None, None),
|
||||
report(None, None, Some("weekly"), None),
|
||||
report(None, None, None, Some("prior_decade")),
|
||||
ViewerTarget::Account {
|
||||
code: "1000".into(),
|
||||
start: Some("2026-12-31".into()),
|
||||
end: Some("2026-01-01".into()),
|
||||
},
|
||||
ViewerTarget::Account {
|
||||
code: "1000".into(),
|
||||
start: Some("2027-01-01".into()),
|
||||
end: None,
|
||||
},
|
||||
] {
|
||||
h.state_mut().show(target.clone());
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state_mut().route(), &Route::Overview, "{target:?}");
|
||||
let st = h.state_mut().state_mut();
|
||||
assert_eq!(st.reports.range, Range::LastMonth, "{target:?}");
|
||||
assert_eq!(st.reports.columns, Columns::Quarter, "{target:?}");
|
||||
assert_eq!(st.reports.compare, Compare::None, "{target:?}");
|
||||
assert_eq!(st.accounts.range, RegisterRange::All, "{target:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Defect 16, repro 1.1.4 (rules.md B-30): a report target with only an
|
||||
/// end covers that end's fiscal year to it (2025-01-01 to 2025-06-30, not
|
||||
/// the swapped 2025-06-30 to 2026-01-01); with no dates, the default (the
|
||||
/// fiscal year to date, named "Year to date"); dates that are a preset's
|
||||
/// show under its name; an account target with no dates opens the
|
||||
/// register on the default.
|
||||
#[test]
|
||||
fn show_report_with_only_end_uses_that_years_start_defect_16() {
|
||||
use ledger_gui::pages::accounts::RegisterRange;
|
||||
use ledger_gui::pages::reports::Range;
|
||||
let today = day(2026, 10, 20);
|
||||
let (_dir, mut h, books) = demo("only-end", today);
|
||||
let report = |start: Option<&str>, end: Option<&str>| ViewerTarget::Report {
|
||||
report: "profit_and_loss".into(),
|
||||
start: start.map(Into::into),
|
||||
end: end.map(Into::into),
|
||||
columns: None,
|
||||
compare: None,
|
||||
};
|
||||
h.state_mut().show(report(None, Some("2025-06-30")));
|
||||
h.run_steps(2);
|
||||
assert_eq!(
|
||||
h.state_mut().state_mut().reports.range,
|
||||
Range::Custom(day(2025, 1, 1), day(2025, 6, 30))
|
||||
);
|
||||
h.state_mut().state_mut().reports.range = Range::LastMonth;
|
||||
h.state_mut().show(report(None, None));
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state_mut().state_mut().reports.range, Range::YearToDate);
|
||||
assert_eq!(Range::YearToDate.dates(&books), (day(2026, 1, 1), today));
|
||||
// What books.show sends for no dates: the same, by name.
|
||||
h.state_mut()
|
||||
.show(report(Some("2026-01-01"), Some("2026-10-20")));
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state_mut().state_mut().reports.range, Range::YearToDate);
|
||||
h.state_mut()
|
||||
.show(report(Some("2026-09-01"), Some("2026-09-30")));
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state_mut().state_mut().reports.range, Range::LastMonth);
|
||||
// Only a start: through today.
|
||||
h.state_mut().show(report(Some("2026-03-15"), None));
|
||||
h.run_steps(2);
|
||||
assert_eq!(
|
||||
h.state_mut().state_mut().reports.range,
|
||||
Range::Custom(day(2026, 3, 15), today)
|
||||
);
|
||||
// An account with no dates (an earlier writer's): the default.
|
||||
h.state_mut().state_mut().accounts.range = RegisterRange::All;
|
||||
h.state_mut().show(ViewerTarget::Account {
|
||||
code: "1000".into(),
|
||||
start: None,
|
||||
end: None,
|
||||
});
|
||||
h.run_steps(2);
|
||||
assert_eq!(
|
||||
h.state_mut().state_mut().accounts.range,
|
||||
RegisterRange::Default
|
||||
);
|
||||
assert_eq!(
|
||||
RegisterRange::Default.dates(&books),
|
||||
(day(2026, 1, 1), today)
|
||||
);
|
||||
}
|
||||
|
||||
/// Settled in design (P3, defect 16): an account's register opens on the
|
||||
/// fiscal year to date with "Show all dates" one click away: after
|
||||
/// books.show of an account (which sends the resolved dates), the button
|
||||
/// shows every date, the books' first entry through today; from there,
|
||||
/// "Fiscal year to date" goes back.
|
||||
#[test]
|
||||
fn show_all_dates_is_one_click_from_a_shown_register() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use ledger_gui::pages::accounts::RegisterRange;
|
||||
let today = day(2026, 10, 20);
|
||||
let (_dir, mut h, books) = demo_sized("all-dates", today, 3000.0);
|
||||
let id = books.ledger.chart().find_by_code("1000").unwrap().id;
|
||||
h.state_mut().show(ViewerTarget::Account {
|
||||
code: "1000".into(),
|
||||
start: Some("2026-01-01".into()),
|
||||
end: Some("2026-10-20".into()),
|
||||
});
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state_mut().route(), &Route::Account(id));
|
||||
assert_eq!(
|
||||
h.state_mut().state_mut().accounts.range,
|
||||
RegisterRange::Dates(day(2026, 1, 1), today)
|
||||
);
|
||||
h.get_by_label("Show all dates").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state_mut().state_mut().accounts.range, RegisterRange::All);
|
||||
let first = books.kpis.first_entry.unwrap();
|
||||
assert_eq!(RegisterRange::All.dates(&books), (first.min(today), today));
|
||||
assert!(
|
||||
first < day(2026, 1, 1),
|
||||
"the demo's books began before this year"
|
||||
);
|
||||
h.get_by_label("Fiscal year to date").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(
|
||||
h.state_mut().state_mut().accounts.range,
|
||||
RegisterRange::Default
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user