check / check (push) Waiting to run
Company details (EIN, state number, address, every profile value), an entry's number and amount, an account's code and balance, a document's file name and SHA-256 copy on click: a pointer and copy icon on hover, a check for a moment after. Amounts copy as their exact plain decimal (fmt::plain: no "$", commas, or parentheses; "-" for negatives); where a click already drills down (report cells, register and transaction rows, general ledger lines), a right-click menu copies the amounts, the entry number, or the description. The sidebar's chain check copies the journal head and data folder. tests/copy.rs: every display style copies back to the exact value (property), and clicking the EIN, an entry's amount, and its number puts exactly that on the clipboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
107 lines
3.6 KiB
Rust
107 lines
3.6 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 (Stephen, 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;
|
|
|
|
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 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),
|
|
};
|
|
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 written in wanderbooks.toml.
|
|
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_zero()).unwrap();
|
|
let (id, amount) = (e.id, e.amount);
|
|
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}")]);
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|