Files
pdfcraft/crates/ui-egui/tests/javascript.rs
T
Brandon ThomasandClaude Opus 5.5 d3db99818f Rename PrintCraft to PdfCraft (#174)
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:03:57 -04:00

136 lines
5.8 KiB
Rust

//! Acrobat JavaScript in the shell (egui_kittest): the console, Document JavaScripts, a button
//! script and Preferences ▸ JavaScript.
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use pdfcraft_ui_egui::{Dialog, PdfCraftApp};
/// A form with a text field and a push button whose Mouse Up script fills it in.
fn form() -> Vec<u8> {
let objs = [
"<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [4 0 R 5 0 R] /DA (/Helv 0 Tf 0 g) /DR << /Font << /Helv 6 0 R >> >> >> >>",
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 300 300] >>",
"<< /Type /Page /Parent 2 0 R /Annots [4 0 R 5 0 R] >>",
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (greeting) /Rect [20 250 280 270] /P 3 0 R /F 4 >>",
"<< /Type /Annot /Subtype /Widget /FT /Btn /Ff 65536 /T (hello) /Rect [20 200 120 230] /P 3 0 R /F 4 /A << /S /JavaScript /JS (getField\\('greeting'\\).value = 'Hello from JavaScript'; app.alert\\('Filled in'\\);) >> >>",
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>",
];
let mut out = b"%PDF-1.7\n".to_vec();
let mut offsets = Vec::new();
for (i, o) in objs.iter().enumerate() {
offsets.push(out.len());
out.extend_from_slice(format!("{} 0 obj\n{o}\nendobj\n", i + 1).as_bytes());
}
let xref = out.len();
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
for o in offsets {
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{xref}\n%%EOF\n", objs.len() + 1).as_bytes());
out
}
fn harness() -> Harness<'static, PdfCraftApp> {
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
let mut app = PdfCraftApp::new();
app.open_bytes("form.pdf", None, form()).unwrap();
app
});
h.run_steps(4);
h
}
fn value(h: &Harness<'static, PdfCraftApp>, name: &str) -> Vec<String> {
let app = h.state();
let id = app.active_ids().unwrap().1;
app.session.get(id).unwrap().form.iter().find(|f| f.name == name).unwrap().value.clone()
}
#[test]
fn console_runs_scripts_against_the_document() {
let mut h = harness();
assert!(h.state_mut().execute("tools.js_console"));
h.run_steps(2);
h.get_by_label("JavaScript Console");
h.state_mut().js_console.input = "getField('greeting').value = 'typed'; console.println(numFields); 6 * 7".into();
h.get_by_label("Run").click();
h.run_steps(2);
assert_eq!(value(&h, "greeting"), ["typed"]);
let log = h.state().js_console.log.clone();
assert!(log.contains(&"2".to_string()) && log.contains(&"42".to_string()), "{log:?}");
h.state_mut().js_console.input = "throw new Error('boom')".into();
h.run_steps(4);
h.get_by_label("Run").click();
h.run_steps(2);
assert!(h.state().js_console.log.last().unwrap().contains("boom"));
h.run_steps(4);
h.get_by_label("Clear").click();
h.run_steps(2);
assert!(h.state().js_console.log.is_empty(), "{:?} {:?}", h.state().js_console.log, h.state().dialog);
}
#[test]
fn button_scripts_run_and_alert() {
let mut h = harness();
let id = h.state().active_ids().unwrap().1;
h.state_mut().run_button_script(id, "hello", "getField('greeting').value = 'Hello from JavaScript'; app.alert('Filled in');");
h.run_steps(2);
assert_eq!(value(&h, "greeting"), ["Hello from JavaScript"]);
assert_eq!(h.state().toast.clone().unwrap().0, "Filled in");
}
#[test]
fn document_scripts_and_preferences() {
let mut h = harness();
assert!(h.state_mut().execute("tools.document_js"));
h.run_steps(2);
h.get_by_label("Document JavaScripts");
h.state_mut().doc_js.name = "init".into();
h.state_mut().doc_js.script = "function hi() { return 'hi'; }".into();
h.get_by_label("Save").click();
h.run_steps(2);
let id = h.state().active_ids().unwrap().1;
assert_eq!(h.state().session.get(id).unwrap().document_scripts()[0].0, "init");
h.state_mut().dialog = Some(Dialog::Preferences);
h.run_steps(2);
h.get_by_label("Enable Acrobat JavaScript").click();
h.run_steps(2);
assert!(!h.state().session.javascript());
let saved = h.state().persist();
let mut fresh = PdfCraftApp::new();
fresh.restore(&saved);
assert!(!fresh.session.javascript(), "the preference is remembered");
}
#[test]
fn merge_data_files_into_a_spreadsheet() {
let dir = std::env::temp_dir().join(format!("pdfcraft-merge-ui-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let out = dir.join("report.csv");
let mut app = PdfCraftApp::new();
app.save_override = Some(out.to_string_lossy().into_owned());
app.merge_data_files(vec![("form.pdf".into(), form())]);
assert_eq!(std::fs::read_to_string(&out).unwrap(), "greeting\n\n");
assert!(app.toast.clone().unwrap().0.starts_with("Merged 1 file"));
app.merge_data_files(vec![("x.fdf".into(), b"junk".to_vec())]);
assert!(app.toast.clone().unwrap().0.starts_with("x.fdf:"));
}
#[test]
fn prepare_a_form_detects_fields_on_a_paper_form() {
let paper = pdfcraft_engine::Session::new().create_from_text("t", "Name: ____________________\n\nPhone: ____________________").unwrap().to_vec();
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
let mut app = PdfCraftApp::new();
app.open_bytes("paper.pdf", None, paper.clone()).unwrap();
app
});
h.run_steps(4);
assert!(h.state_mut().execute("form.prepare"));
h.run_steps(3);
let app = h.state();
let id = app.active_ids().unwrap().1;
let names: Vec<String> = app.session.get(id).unwrap().form.iter().map(|f| f.name.clone()).collect();
assert_eq!(names, ["Name", "Phone"]);
assert_eq!(app.toast.clone().unwrap().0, "Detected 2 form fields");
}