From 3ef63cb4e7124688e7850a19d1bd46c6ac503659 Mon Sep 17 00:00:00 2001 From: skylar taylor-barrick Date: Tue, 13 Oct 2026 23:14:03 -0600 Subject: [PATCH] M2: retain open regressions and deterministic test fixtures --- crates/automation/tests/batch.rs | 12 ++++- crates/print/src/tests.rs | 3 +- crates/ui-egui/src/open_worker_tests.rs | 62 +++++++++++++++++++++++++ crates/ui-egui/tests/document_links.rs | 3 ++ 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/crates/automation/tests/batch.rs b/crates/automation/tests/batch.rs index 20a335917..6c1c55162 100644 --- a/crates/automation/tests/batch.rs +++ b/crates/automation/tests/batch.rs @@ -10,7 +10,17 @@ fn result(auto: &mut Automation, args: Value) -> Value { } fn pdf() -> Vec { - Session::new().create_from_text("Original", "Original text").unwrap().as_ref().clone() + // Reuse one synthetic document so byte comparisons cannot straddle the + // writer's modification-time clock. Each caller gets an independent buffer. + static PDF: std::sync::OnceLock> = std::sync::OnceLock::new(); + PDF.get_or_init(|| Session::new().create_from_text("Original", "Original text").unwrap().as_ref().clone()).clone() +} + +#[test] +fn pdf_fixture_remains_identical_across_metadata_clock_tick() { + let before = pdf(); + std::thread::sleep(std::time::Duration::from_millis(1100)); + assert_eq!(before, pdf()); } #[test] diff --git a/crates/print/src/tests.rs b/crates/print/src/tests.rs index ee21c2706..be0d641ac 100644 --- a/crates/print/src/tests.rs +++ b/crates/print/src/tests.rs @@ -506,7 +506,8 @@ mod cups_options { let Some(record) = std::env::var_os(RECORD) else { return }; let args: Vec = std::env::args().skip_while(|a| a != "--").skip(1).collect(); std::fs::write(std::path::Path::new(&record).join("args"), args.join("\n")).unwrap(); - println!("{}", std::env::var(ANSWER).unwrap()); + // Serial libtest prints its test label without a newline before this helper's stdout. + println!("\n{}", std::env::var(ANSWER).unwrap()); } fn command(record: &std::path::Path, answer: &str) -> std::process::Command { diff --git a/crates/ui-egui/src/open_worker_tests.rs b/crates/ui-egui/src/open_worker_tests.rs index 306d8e9ac..7da567dc8 100644 --- a/crates/ui-egui/src/open_worker_tests.rs +++ b/crates/ui-egui/src/open_worker_tests.rs @@ -406,3 +406,65 @@ fn a_startup_document_waits_for_an_active_document_to_prepare() { assert_eq!(harness.state().active, Some(0), "the chosen document keeps focus"); let _ = std::fs::remove_file(path); } + +#[test] +#[allow(clippy::expect_used, clippy::unwrap_used)] +fn inline_tool_open_keeps_its_command_for_pdf_conversion_and_password_retry() { + for (name, bytes, encrypted) in + [("plain.pdf", fixture(1), false), ("converted.txt", b"Text to export".to_vec(), false), ("locked.pdf", encrypted_tool_fixture(), true)] + { + let path = std::env::temp_dir().join(format!("pdfcraft-inline-tool-{}-{name}", std::process::id())); + std::fs::write(&path, bytes).expect("write selected file"); + let mut app = PdfCraftApp::new(); + app.set_option("language", "en").expect("language preference"); + assert!(app.ctx.is_none(), "this exercises the no-context inline path"); + app.open_path_then(&path.to_string_lossy(), Some("export.text")); + if encrypted { + assert!(app.dialog.is_none()); + assert_eq!(app.password_prompt.as_ref().expect("password prompt").then, Some("export.text")); + app.submit_password(Some("wrong".into())); + assert!(app.password_prompt.as_ref().expect("wrong-password prompt").error.is_some()); + assert!(app.dialog.is_none()); + app.submit_password(Some("tool-password".into())); + } + assert_eq!(app.views.len(), 1); + assert_eq!(app.active, Some(0)); + assert!(matches!(app.dialog, Some(crate::Dialog::Export(_))), "the inline {name} retains its export command"); + let _ = std::fs::remove_file(path); + } +} + +#[test] +#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)] +fn startup_files_wait_for_absolute_link_consent_and_preparation() { + let (mut h, _) = harness(); + let source = h.state().views[0].id; + let path = std::env::temp_dir().join(format!("pdfcraft-open-consented-startup-{}.pdf", std::process::id())); + std::fs::write(&path, fixture(2)).expect("write linked PDF"); + h.state_mut().open_remote_link(source, path.to_string_lossy().into_owned(), pdfcraft_render::RemoteDestination::Default); + h.state().startup_inbox.lock().expect("startup queue").push(("startup.pdf".into(), fixture(1))); + h.step(); + assert!(h.state().pending_remote_link.is_some()); + assert_eq!(h.state().startup_inbox.lock().expect("startup queue").len(), 1); + assert_eq!(h.state().views.len(), 1, "startup cannot replace the consent interaction"); + + let (gate, started, release, finished) = open_worker::prepare_gate(); + h.state_mut().open_prepare_gate = Some(gate); + h.state_mut().resolve_pending_remote_link(true); + started.recv_timeout(Duration::from_secs(5)).expect("worker reached prepare barrier"); + h.step(); + let queued = h.state().startup_inbox.lock().expect("startup queue").len(); + let visible = h.state().views.len(); + // Always release before asserting: a failure must not strand the worker at the barrier. + release.send(()).expect("release prepare barrier"); + finished.recv_timeout(Duration::from_secs(5)).expect("worker finished preparation"); + assert_eq!(queued, 1, "startup bytes wait while the approved linked PDF prepares"); + assert_eq!(visible, 1); + h.step(); + let app = h.state(); + assert_eq!(app.views.len(), 3); + assert_eq!(app.session.get(app.views[1].id).expect("linked document").path.as_deref(), path.to_str()); + assert_eq!(app.session.get(app.views[2].id).expect("startup document").name, "startup.pdf"); + assert_eq!(app.active, Some(1), "the user's approved navigation keeps focus"); + let _ = std::fs::remove_file(path); +} diff --git a/crates/ui-egui/tests/document_links.rs b/crates/ui-egui/tests/document_links.rs index c9bb64a3d..2d6346c83 100644 --- a/crates/ui-egui/tests/document_links.rs +++ b/crates/ui-egui/tests/document_links.rs @@ -235,6 +235,9 @@ fn a_gotor_link_to_a_directory_or_device_is_refused_without_reading_it() { for target in targets { let mut h = remote_harness(Some(dir.join("source.pdf").to_string_lossy().into_owned()), target, "[0 /Fit]"); click_remote_link(&mut h); + support::wait_until(&mut h, "non-regular remote-file refusal", |app| { + app.toast.as_ref().is_some_and(|(message, _)| message.contains("not a regular file")) + }); assert_eq!(h.state().views.len(), 1, "{target} opens no tab"); let toast = &h.state().toast.as_ref().unwrap().0; assert!(toast.contains("not a regular file"), "{target}: {toast}");