Files
wanderbooks/crates/ledger-gui/tests/copy.rs
T
swaitsandClaude Opus 5.5 5bc0633cdd P2 slice 6: the viewer
`wanderbooks gui [BOOK]` finds the data root, reads settings from the
journal, the vault of the book, presence through ops/, Save a copy /
Open on every document, Save the W-9, the Export menu, and colour
schemes. One builder for the CPA package and the 1099 worksheet
(ledger-package), shared by the server and the viewer.

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

119 lines
3.8 KiB
Rust

//! Click to copy: values leave the viewer exactly.
//!
//! ```text
//! GOAL: what people retype into forms (the EIN, an amount, an entry
//! number) can be copied instead, and copies exactly.
//! SOURCE: DECISION (owner, 2026-10-14: click-to-copy on the company
//! page, amounts, and identifiers).
//! INVARIANTS:
//! 1. An amount copies as its plain decimal: fmt::plain of money,
//! accounting, or dollars text is the exact value, with "-" for a
//! negative and no "$", commas, or parentheses.
//! 2. Clicking a company value or an entry's amount puts exactly that
//! value on the clipboard.
//! ```
use egui::OutputCommand;
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use ledger_gui::nav::Route;
use ledger_gui::{App, Source, data, fmt};
use proptest::prelude::*;
use rust_decimal::Decimal;
/// 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);
}
}
proptest! {
/// INVARIANT 1: every display style copies back to the same value.
#[test]
fn an_amount_copies_as_its_exact_value(
mantissa in -10i128.pow(16)..10i128.pow(16),
scale in 0u32..8,
) {
let d = Decimal::from_i128_with_scale(mantissa, scale);
for shown in [fmt::money(d), fmt::accounting(d), fmt::dollars(d)] {
let plain = fmt::plain(&shown).expect("an amount");
prop_assert!(!plain.contains(['$', ',', '(', ')', '\u{2212}']), "{}", plain);
prop_assert_eq!(plain.parse::<Decimal>().unwrap(), d, "{} copied as {}", shown, plain);
}
}
}
fn copied(h: &Harness<'_, App>) -> Vec<String> {
h.output()
.platform_output
.commands
.iter()
.filter_map(|c| match c {
OutputCommand::CopyText(t) => Some(t.clone()),
_ => None,
})
.collect()
}
/// INVARIANT 2, on the demo books.
#[test]
fn clicking_a_value_copies_it() {
let dir = std::env::temp_dir().join(format!("ledger-gui-copy-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let _cleanup = TestDir(dir.clone());
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"),
today: Some(today),
};
let open = |route: Route| {
let books = data::load_once(&source).unwrap();
let mut h = Harness::builder()
.with_size(egui::vec2(1280.0, 900.0))
.build_eframe(|cc| App::with_books(&cc.egui_ctx, books, false, route));
h.run_steps(3);
h
};
// The company's EIN, as the demo journals it.
let ein = data::load_once(&source)
.unwrap()
.settings
.company
.profile
.iter()
.find(|(k, _)| k == "ein")
.map(|(_, v)| v.clone())
.expect("the demo has an EIN");
let mut h = open(Route::Company);
h.get_by_label(&ein).click();
h.step();
assert_eq!(copied(&h), [ein]);
// An entry's amount, as a plain decimal.
let books = data::load_once(&source).unwrap();
let e = books
.entries
.iter()
.find(|e| e.amount.is_some_and(|a| !a.is_zero()))
.unwrap();
let (id, amount) = (e.id, e.amount.unwrap());
drop(books);
let mut h = open(Route::Entry(id));
let shown = fmt::dollars(amount);
h.get_by_label(&shown).click();
h.step();
assert_eq!(copied(&h), [fmt::plain(&shown).unwrap()]);
assert_eq!(copied(&h)[0].parse::<Decimal>().unwrap(), amount);
// The entry number.
let mut h = open(Route::Entry(id));
h.get_by_label(&format!("Entry #{id}")).click();
h.step();
assert_eq!(copied(&h), [format!("#{id}")]);
}