mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
* CI: dev builds keep line tables only * CI: one integration-test binary per crate * CI: xtask runs tests with nextest, doctests apart, --keep-going on clippy and wasm cargo xtask test runs the workspace tests (--lib --bins --tests) with cargo-nextest when it is installed, else cargo test, then the doctests. cargo xtask ci uses the same two steps. * CI: docs name cargo xtask test * CI: agent guides name the moved fuzz test * CI: tests added on main join their crate's test binary
128 lines
5.5 KiB
Rust
128 lines
5.5 KiB
Rust
//! Native print onboarding preserves engine PDF generation and submits the selected settings.
|
|
use gridcraft_engine::Session;
|
|
use gridcraft_ui_egui::printing::{Duplex, Feedback, Printer};
|
|
use gridcraft_ui_egui::{Services, SheetApp};
|
|
use serde_json::json;
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
#[test]
|
|
fn native_print_uses_selected_settings_and_reports_spool_errors() {
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Services::default());
|
|
app.run("cell.set", json!({"cell":"A1", "input":"A sheet to print"})).unwrap();
|
|
let jobs = Rc::new(RefCell::new(Vec::new()));
|
|
let observed = jobs.clone();
|
|
app.services.print_native = Some(Box::new(move |bytes, settings| {
|
|
observed.borrow_mut().push((bytes, settings));
|
|
let (sender, receiver) = std::sync::mpsc::channel();
|
|
sender.send(Err("printer is offline".into())).unwrap();
|
|
receiver
|
|
}));
|
|
app.run_or_alert("file.print", json!({}));
|
|
assert!(app.backstage.is_some(), "a shortcut opens settings without submitting");
|
|
let pdf = app.run("file.print", json!({"sheets":"active"})).unwrap();
|
|
assert!(pdf["base64"].as_str().is_some(), "the engine still returns PDF bytes without submitting");
|
|
assert!(jobs.borrow().is_empty(), "opening settings/exporting does not spool");
|
|
app.printing.printers.push(Printer { name: "Chosen queue".into(), default: false });
|
|
let configured = app.run("ui.printSettings", json!({"printer":"Chosen queue", "copies":3,"duplex":"shortEdge","grayscale":true})).unwrap();
|
|
assert_eq!(configured["ready"], true);
|
|
assert_eq!(configured["printers"][0]["name"], "Chosen queue");
|
|
app.run("ui.printSubmit", json!({"sheets":"active"})).unwrap();
|
|
assert!(app.printing.busy());
|
|
let jobs = jobs.borrow();
|
|
assert_eq!(jobs.len(), 1);
|
|
assert!(jobs[0].0.starts_with(b"%PDF"));
|
|
assert_eq!(jobs[0].1.printer, "Chosen queue");
|
|
assert_eq!(jobs[0].1.copies, 3);
|
|
assert_eq!(jobs[0].1.duplex, Some(Duplex::ShortEdge));
|
|
assert!(jobs[0].1.grayscale);
|
|
app.poll_printing(&egui::Context::default());
|
|
assert_eq!(app.printing.message, Some(Feedback::Failed("printer is offline".into())));
|
|
assert!(app.message.as_ref().unwrap().1.contains("printer is offline"));
|
|
}
|
|
|
|
#[test]
|
|
fn print_settings_are_visible_in_a_narrow_window() {
|
|
use egui_kittest::kittest::{NodeT, Queryable};
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Services::default());
|
|
app.run("cell.set", json!({"cell":"A1", "input":"A sheet to print"})).unwrap();
|
|
app.services.printers = Some(Box::new(|| {
|
|
let (sender, receiver) = std::sync::mpsc::channel();
|
|
sender.send(vec![Printer { name: "Test queue".into(), default: true }]).unwrap();
|
|
receiver
|
|
}));
|
|
app.services.print_native = Some(Box::new(|_, _| {
|
|
let (sender, receiver) = std::sync::mpsc::channel();
|
|
sender.send(Ok("queued".into())).unwrap();
|
|
receiver
|
|
}));
|
|
app.run("ui.backstage", json!({"page":"print"})).unwrap();
|
|
let mut harness = egui_kittest::Harness::builder().with_size(egui::vec2(800.0, 700.0)).build_ui_state(
|
|
|ui, app: &mut SheetApp| {
|
|
app.logic(ui.ctx());
|
|
app.ui(ui);
|
|
},
|
|
app,
|
|
);
|
|
harness.run_steps(5);
|
|
assert!(!harness.get_by_label("Refresh printers").accesskit_node().is_disabled());
|
|
assert!(!harness.get_by_label("Grayscale").accesskit_node().is_disabled());
|
|
harness.get_by_label("Grayscale").click();
|
|
harness.run_steps(2);
|
|
assert!(harness.state().printing.settings.grayscale);
|
|
assert!(harness.state().printing.ready());
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_print_settings_are_rejected_without_partial_changes() {
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Services::default());
|
|
app.printing.printers.push(Printer { name: "Queue".into(), default: true });
|
|
let before = app.run("ui.printSettings", json!({})).unwrap();
|
|
for params in [
|
|
json!({"printer":"Missing"}),
|
|
json!({"copies":0}),
|
|
json!({"copies":1000}),
|
|
json!({"copies":1.5}),
|
|
json!({"copies":-1}),
|
|
json!({"copies":"3"}),
|
|
json!({"duplex":"maybe"}),
|
|
json!({"grayscale":"yes"}),
|
|
json!({"refresh":"yes"}),
|
|
json!({"copies":3,"duplex":"invalid"}),
|
|
json!([]),
|
|
] {
|
|
assert!(app.run("ui.printSettings", params.clone()).is_err(), "{params}");
|
|
assert_eq!(app.run("ui.printSettings", json!({})).unwrap(), before, "no partial update: {params}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn repeated_fast_jobs_each_surface_an_identical_receipt() {
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Services::default());
|
|
app.printing.printers.push(Printer { name: "Queue".into(), default: true });
|
|
app.run("ui.printSettings", json!({"printer":"Queue"})).unwrap();
|
|
app.services.print_native = Some(Box::new(|_, _| {
|
|
let (sender, receiver) = std::sync::mpsc::channel();
|
|
sender.send(Ok("same receipt".into())).unwrap();
|
|
receiver
|
|
}));
|
|
for _ in 0..2 {
|
|
app.toast = None;
|
|
app.run("ui.printSubmit", json!({})).unwrap();
|
|
// Completion may be consumed by the shared widget before the app's logic pass.
|
|
app.printing.poll(&egui::Context::default());
|
|
app.poll_printing(&egui::Context::default());
|
|
assert!(app.toast.as_ref().unwrap().0.contains("same receipt"));
|
|
app.toast = None;
|
|
app.poll_printing(&egui::Context::default());
|
|
assert!(app.toast.is_none());
|
|
}
|
|
}
|