gridcraft/crates/ui-egui/tests/clipboard.rs

119 lines
5.0 KiB
Rust

//! Copy/cut route rich tables to the host without a later egui text write replacing them.
use std::cell::RefCell;
use std::rc::Rc;
use egui::{Event, OutputCommand};
use gridcraft_engine::Session;
use gridcraft_ui_egui::{Services, SheetApp};
use serde_json::json;
fn harness(services: Services) -> egui_kittest::Harness<'static, SheetApp> {
let mut session = Session::new();
session.new_workbook();
session.execute("range.setValues", json!({"range": "A1", "values": [["Name", "Score"], ["Ada", 42]]})).unwrap();
session.execute("selection.set", json!({"range": "A1:B2"})).unwrap();
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SheetApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
SheetApp::new(session, services),
);
h.run_steps(4);
h
}
#[test]
fn copy_and_cut_publish_html_and_text_without_overwriting_rich_clipboard() {
for event in [Event::Copy, Event::Cut] {
let calls = Rc::new(RefCell::new(Vec::new()));
let received = calls.clone();
let mut h = harness(Services {
copy_html: Some(Box::new(move |html, text| {
received.borrow_mut().push((html.to_string(), text.to_string()));
Ok(())
})),
..Default::default()
});
h.input_mut().events.push(event.clone());
h.step();
let calls = calls.borrow();
assert_eq!(calls.len(), 1);
assert!(calls[0].0.contains("<table"));
assert!(calls[0].0.contains("Ada"));
assert_eq!(calls[0].1, "Name\tScore\nAda\t42\n");
assert!(!h.output().platform_output.commands.iter().any(|c| matches!(c, OutputCommand::CopyText(_))));
assert_eq!(h.state().session.clipboard.as_ref().map(|c| c.cut), Some(matches!(event, Event::Cut)));
}
}
#[test]
fn missing_or_failed_rich_clipboard_falls_back_to_plain_text() {
for fail in [false, true] {
let mut services = Services::default();
if fail {
services.copy_html = Some(Box::new(|_, _| Err("clipboard unavailable".into())));
}
let mut h = harness(services);
h.input_mut().events.push(Event::Copy);
h.step();
assert!(
h.output().platform_output.commands.iter().any(|c| { matches!(c, OutputCommand::CopyText(text) if text == "Name\tScore\nAda\t42\n") })
);
}
}
#[test]
fn programmatic_copy_does_not_write_to_the_system_clipboard() {
let calls = Rc::new(RefCell::new(0));
let received = calls.clone();
let mut h = harness(Services {
copy_html: Some(Box::new(move |_, _| {
*received.borrow_mut() += 1;
Ok(())
})),
..Default::default()
});
let copied = h.state_mut().run("edit.copy", json!({})).unwrap();
assert!(copied.get("html").is_none(), "HTML is only built when requested");
let copied = h.state_mut().run("edit.copy", json!({"html": true})).unwrap();
assert!(copied["html"].is_string());
assert_eq!(*calls.borrow(), 0);
assert!(!h.output().platform_output.commands.iter().any(|c| matches!(c, OutputCommand::CopyText(_))));
}
#[test]
fn pasting_reads_the_clipboard_html_for_the_numbers_behind_the_text() {
// #257: currency text from another spreadsheet stays a number when the host offers the HTML.
let mut h = harness(Services {
paste_html: Some(Box::new(|| Some(r#"<table><tr><td x:num="10000">Rp10,000.00</td></tr></table>"#.to_string()))),
..Default::default()
});
h.state_mut().run("selection.set", json!({"cell": "D5"})).unwrap();
h.input_mut().events.push(Event::Paste("Rp10,000.00".into()));
h.run_steps(2);
let value = h.state().session.active().and_then(|d| d.wb.active().map(|s| s.value(gridcraft_engine::core::CellRef::new(4, 3))));
assert_eq!(value, Some(gridcraft_engine::core::Value::number(10000.0)));
}
#[test]
fn copying_multiple_areas_copies_lined_up_areas_and_explains_the_rest() {
// Issue #348: lined-up areas copy as one block; others get Excel's message instead of nothing.
let mut h = harness(Services::default());
h.state_mut().session.execute("selection.set", json!({"range": "B1:B2,A1:A2"})).unwrap();
h.input_mut().events.push(Event::Copy);
h.step();
assert!(h.output().platform_output.commands.iter().any(|c| matches!(c, OutputCommand::CopyText(text) if text == "Name\tScore\nAda\t42\n")));
assert!(h.state().message.is_none());
assert_eq!(h.state().session.clipboard.as_ref().map(|c| c.areas.len()), Some(2));
h.state_mut().session.execute("selection.set", json!({"range": "A1,B2"})).unwrap();
h.input_mut().events.push(Event::Copy);
h.step();
assert!(!h.output().platform_output.commands.iter().any(|c| matches!(c, OutputCommand::CopyText(_))));
assert_eq!(h.state().message.as_ref().map(|(_, text)| text.as_str()), Some("This action won't work on multiple selections."));
assert_eq!(h.state().session.clipboard.as_ref().map(|c| c.areas.len()), Some(2), "the earlier copy stays");
}