Files
gridcraft/crates/ui-egui/tests/it/native_print.rs
T
skylar taylor-barrick 41769f1d16 Migrate GridCraft to egui 0.37 (#2543)
* E37: migrate GridCraft to egui 0.37

Pin the reviewed fork and shared dependencies, adapt input and rendering to the
new frame lifecycle, and preserve bounded capture handling and font notices.
Add representative accessibility checks and meaningful control labels.

Validation: full repository CI passed all ten stages, with 2,249 tests passed,
zero failed and one existing timing test ignored. Native workflows and paired
performance qualification remain documented separately.

* egui037: record final published pins and local qualification

* M0.37: fix RTL clipping and preserve screenshot budgets

* test: share migrated GridCraft UI harness setup

* egui037: document migrated UI and regression contracts

* egui-037: align popup test fonts and avoid redundant tooltip probes
2026-10-25 14:55:00 -04:00

132 lines
6.0 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]
/// Opening settings and exporting do not spool; a configured native print submits its settings, and a spool error is reported.
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(), label: String::new(), 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]
/// Print settings stay visible and accessible in an 800x700 window; toggling Grayscale updates the settings and readiness.
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 = crate::common::prepare(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(), label: String::new(), 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 = crate::common::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]
/// Every malformed print-settings request errors and leaves the prior settings unchanged.
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(), label: String::new(), 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]
/// Two fast jobs each surface the same receipt exactly once.
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(), label: String::new(), 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());
}
}