Files
pdfcraft/crates/ui-egui/tests/it/layer_actions.rs
T
aiongg 9526363152 Build each crate's integration tests as one binary (#4087)
* Build each crate's integration tests as one binary

Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos,
crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and
the support modules move into tests/it/ too. Crates with a single test
file keep it. The test executables go from 150 to 52, and every crate
runs the same number of tests as before.

ui-egui perf keeps its own binary (own process): its frame-time budgets
would charge each measurement for the other tests running beside it.

What the move needs:
- include_str!/include_bytes! and #[path] paths gain one "../".
- ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file
  ones (content, document_links, editing, fill_sign, view), so one
  process creates one wgpu device at a time. The drop-line test in ui.rs
  and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and
  CRAFT_UI_DOCKING_DIR, which render without holding it, take it too.
- pdfcraft winit_wayland_scroll: the vendored winit source it compiles
  names crate::event, so that shim lives in main.rs.
- parity evidence names the new paths.

* Name the one-binary test paths in docs and comments

- READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs.
- The oracle README's example filters with --test it encryption::qpdf_agrees.
- pdfcraft's GPU lock comment points at the UI tests' shared lock in
  crates/ui-egui/tests/it/main.rs.
2026-10-22 11:28:58 -04:00

127 lines
5.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Set-layer-visibility actions (`SetOCGState`) on links and push buttons show and hide layers,
//! as the Layers panel does.
use egui::Pos2;
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use pdfcraft_ui_egui::PdfCraftApp;
/// One page with layers Red (on) and Green (off), a radio-button group, each drawing a band;
/// a "show-red" push button turns Red on and a link toggles Green.
fn fixture() -> Vec<u8> {
let content = "/OC /R BDC 1 0 0 rg 20 300 260 40 re f EMC /OC /G BDC 0 1 0 rg 20 240 260 40 re f EMC";
let objs = [
"<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [7 0 R] >> /OCProperties << /OCGs [5 0 R 6 0 R] /D << /OFF [6 0 R] /RBGroups [[5 0 R 6 0 R]] >> >> >>".to_string(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 400] /Contents 4 0 R /Resources << /Properties << /R 5 0 R /G 6 0 R >> >> /Annots [7 0 R 8 0 R] >>".into(),
format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()),
"<< /Type /OCG /Name (Red) >>".into(),
"<< /Type /OCG /Name (Green) >>".into(),
"<< /Type /Annot /Subtype /Widget /FT /Btn /Ff 65536 /T (show-red) /Rect [20 20 120 50] /P 3 0 R /A << /S /SetOCGState /State [/ON 5 0 R] >> >>".into(),
"<< /Type /Annot /Subtype /Link /Rect [180 20 280 50] /Border [0 0 0] /A << /S /SetOCGState /State [/Toggle 6 0 R] >> >>".into(),
];
let mut out = b"%PDF-1.7\n".to_vec();
let mut offs = Vec::new();
for (i, o) in objs.iter().enumerate() {
offs.push(out.len());
out.extend_from_slice(format!("{} 0 obj\n{o}\nendobj\n", i + 1).as_bytes());
}
let x = out.len();
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
for o in offs {
out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
}
out.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{x}\n%%EOF\n", objs.len() + 1).as_bytes());
out
}
fn harness(setup: impl FnOnce(&mut PdfCraftApp) + 'static) -> Harness<'static, PdfCraftApp> {
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
app.open_bytes("layers.pdf", None, fixture()).unwrap();
app.set_option("zoom", "150").unwrap();
setup(&mut app);
app
});
h.run_steps(6);
h
}
/// The screen position of a point given in PDF user space (y up) on the 300 × 400 page.
fn at(h: &Harness<'static, PdfCraftApp>, x: f32, y: f32) -> Pos2 {
let r = h.state().views[0].page_screen_rect(0).unwrap();
let k = r.width() / 300.0;
r.min + egui::vec2(x * k, (400.0 - y) * k)
}
fn click(h: &mut Harness<'static, PdfCraftApp>, p: Pos2) {
h.hover_at(p);
h.run_steps(1);
h.drag_at(p);
h.run_steps(1);
h.drop_at(p);
h.run_steps(3);
}
/// Whether Red and Green are shown.
fn shown(h: &Harness<'static, PdfCraftApp>) -> Vec<(String, bool)> {
let s = h.state();
s.session.get(s.views[0].id).unwrap().info.layers.iter().map(|l| (l.name.clone(), l.visible)).collect()
}
fn layers(red: bool, green: bool) -> Vec<(String, bool)> {
vec![("Red".into(), red), ("Green".into(), green)]
}
#[test]
fn links_set_layer_visibility() {
let mut h = harness(|_| {});
assert_eq!(shown(&h), layers(true, false));
let link = at(&h, 230.0, 35.0);
h.hover_at(link);
h.run_steps(2);
h.get_by_label("Set layer visibility");
// Turning Green on turns Red, its radio-button partner, off.
click(&mut h, link);
assert_eq!(shown(&h), layers(false, true));
// Toggling Green off turns nothing back on.
click(&mut h, link);
assert_eq!(shown(&h), layers(false, false));
let s = h.state();
assert!(!s.session.get(s.views[0].id).unwrap().dirty, "showing and hiding layers doesn't change the document");
}
#[test]
fn push_buttons_set_layer_visibility() {
// Both on, as the Layers panel can leave them.
let mut h = harness(|app| app.set_option("layer", "Green=on").unwrap());
assert_eq!(shown(&h), layers(true, true));
// Turning Red on (it already is) turns Green, its radio-button partner, off.
let button = at(&h, 70.0, 35.0);
click(&mut h, button);
assert_eq!(shown(&h), layers(true, false));
let s = h.state();
assert!(!s.session.get(s.views[0].id).unwrap().dirty, "showing and hiding layers doesn't change the document");
}
#[test]
fn layer_options_reset_to_initial_visibility() {
// Red off and Green on: the opposite of how the file opens.
let mut h = harness(|app| {
app.set_option("layer", "Red=off").unwrap();
app.set_option("layer", "Green=on").unwrap();
app.set_option("panel", "layers").unwrap();
});
assert_eq!(shown(&h), layers(false, true));
h.get_by_label("Layer options").click();
h.run_steps(2);
h.get_by_label("Reset to Initial Visibility").click();
h.run_steps(3);
assert_eq!(shown(&h), layers(true, false));
let s = h.state();
let doc = s.session.get(s.views[0].id).unwrap();
assert!(!doc.dirty, "showing and hiding layers doesn't change the document");
assert_eq!(doc.can_undo(), None, "nor is it an edit to undo");
}