WIP: lockstep yy/p cases where edit text and value disagree (failing)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Edward Langley
2026-09-22 21:02:48 -07:00
parent 40b8913397
commit 772c695d3e
@@ -1581,3 +1581,159 @@ fn opens_an_editor(client: &ClientState, key: KeyEvent) -> bool {
_ => false,
}
}
// Yank and paste (improvise-dsxq).
fn region_measure(region: &str, measure: &str) -> improvise_command::model::cell::CellKey {
improvise_command::model::cell::CellKey::new(vec![
("Region".to_string(), region.to_string()),
("_Measure".to_string(), measure.to_string()),
])
}
/// Four rows and four measures, the last a formula, holding values on which the
/// edit text and the value disagree. Evidence file.
fn yank_host() -> App {
use improvise_protocol::CellValue::{Number, Text};
let mut wb = Workbook::new("Test");
wb.add_category("Region").unwrap();
for item in ["West", "North", "South", "East"] {
wb.add_item("Region", item).unwrap();
}
for item in ["Units", "Revenue", "Cost"] {
wb.add_item("_Measure", item).unwrap();
}
wb.model.add_formula(
improvise_command::formula::parse_formula("Profit = Revenue - Cost", "_Measure").unwrap(),
);
wb.active_view_mut().set_axis("Region", Axis::Row);
wb.active_view_mut().set_axis("_Measure", Axis::Column);
for (region, measure, value) in [
("North", "Revenue", Number(1.0)),
("North", "Cost", Number(2.0)),
("South", "Revenue", Number(1.23456789)),
("South", "Cost", Text("007".into())),
("East", "Revenue", Number(10.123456)),
("East", "Cost", Number(0.5)),
] {
wb.model.set_cell(region_measure(region, measure), value);
}
let mut app = App::new(wb, None);
app.term_height = 4 + 8;
app.rebuild_layout();
app
}
/// One unmodified character on both ends: the host through its keymap, a twin
/// through exactly what the client composed.
fn press(app: &mut App, twin: &mut App, client: &mut ClientState, key: char) {
let event = KeyEvent::new(KeyCode::Char(key), KeyModifiers::NONE);
let KeyOutcome::Local(cmds) = resolve(client, event) else {
panic!("{key}: the client must answer this key locally");
};
for cmd in &cmds {
client.fold(cmd);
}
apply_on(twin, &cmds);
reduce_key(app, event);
}
fn seat(app: &mut App, client: &mut ClientState, at: (usize, usize)) {
app.cursor_mut().selected = at;
client.view.cursor_mut().selected = at;
}
/// `yy` then `p` copies the value the TUI copies, not its edit text re-parsed.
/// Never an integer: the one kind on which the two agree. Evidence file.
#[test]
fn yy_then_p_copies_the_value_the_host_copies() {
use improvise_protocol::CellValue::{Number, Text};
let cases = [
("a non-whole number", (2, 1), (1, 1), "Revenue", Number(1.23456789)),
("number-like text", (2, 2), (1, 2), "Cost", Text("007".into())),
("a formula's value", (3, 3), (1, 1), "Revenue", Number(10.123456 - 0.5)),
];
for (what, from, to, measure, expected) in cases {
let mut app = yank_host();
let mut twin = yank_host();
let mut client = client_ready_to_edit(&app, (4, 4));
seat(&mut app, &mut client, from);
press(&mut app, &mut twin, &mut client, 'y');
press(&mut app, &mut twin, &mut client, 'y');
assert_eq!(
app.view_state.yanked,
Some(expected.clone()),
"{what}: the host must yank the value itself, or the fixture proves nothing"
);
assert_eq!(
client.view.yanked, app.view_state.yanked,
"{what}: client and host yanked different values"
);
seat(&mut app, &mut client, to);
press(&mut app, &mut twin, &mut client, 'p');
assert_eq!(
app.model_state
.workbook
.model
.get_cell(&region_measure("North", measure)),
Some(&expected),
"{what}: the host's paste must have landed"
);
assert_eq!(
document_text(&twin),
document_text(&app),
"{what}: the client's paste wrote a different document"
);
}
}
/// A true aggregate: `Product` on no axis, so the cell sums two records and has
/// no stored value. Yank only: an all-aggregate grid has nowhere to paste.
#[test]
fn yy_over_an_aggregate_copies_the_sum_the_host_copies() {
let mut wb = Workbook::new("Test");
wb.add_category("Region").unwrap();
wb.add_item("Region", "North").unwrap();
wb.add_item("Region", "South").unwrap();
wb.add_category("Product").unwrap();
wb.add_item("Product", "Widget").unwrap();
wb.add_item("Product", "Gadget").unwrap();
wb.add_item("_Measure", "Revenue").unwrap();
wb.active_view_mut().set_axis("Region", Axis::Row);
wb.active_view_mut().set_axis("_Measure", Axis::Column);
wb.active_view_mut().set_axis("Product", Axis::None);
for (product, value) in [("Widget", 1.23456789), ("Gadget", 1.0)] {
wb.model.set_cell(
region_measure("South", "Revenue").with("Product", product),
improvise_protocol::CellValue::Number(value),
);
}
let mut app = App::new(wb, None);
app.term_height = 2 + 8;
app.rebuild_layout();
let mut client = client_of(&app, (2, 1));
app.cursor_mut().selected = (1, 0);
client.view.cursor_mut().selected = (1, 0);
for _ in 0..2 {
let event = KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE);
let KeyOutcome::Local(cmds) = resolve(&client, event) else {
panic!("y must resolve locally over an aggregate too");
};
for cmd in &cmds {
client.fold(cmd);
}
reduce_key(&mut app, event);
}
assert_eq!(
app.view_state.yanked,
Some(improvise_protocol::CellValue::Number(1.23456789 + 1.0)),
"the host yanks the sum, or the fixture is not an aggregate"
);
assert_eq!(
client.view.yanked, app.view_state.yanked,
"client and host yanked different values over an aggregate"
);
}