mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 22:41:47 -06:00
* feat: startup update prompt modal and direct self-updating Help ▸ Check for updates and the check at start run through craft-update's Updater, which replaces the old GitHub fetcher and update source. The check at start is opt-in for the desktop app (test harnesses never reach the network), an install holds back while documents are unsaved, and a finished update closes through the app's quit path. craft-update stays out of the wasm build. Ref: storytold/craft-libs#319 * test(updates): the prompt's buttons are DOWNLOAD and LATER * build: pin craft-update to craft-libs main (db19c37) * fix(updates): run the Windows update helper before argument parsing * fix(updates): point the parity evidence at the current update tests
121 lines
4.6 KiB
Rust
121 lines
4.6 KiB
Rust
//! Help ▸ Check for updates (issue #28), with stand-in release sources (no network).
|
|
|
|
use std::sync::Arc;
|
|
|
|
use egui_kittest::Harness;
|
|
use egui_kittest::kittest::Queryable;
|
|
use pdfcraft_ui_egui::PdfCraftApp;
|
|
use pdfcraft_ui_egui::updates::UpdateSource;
|
|
|
|
fn release(tag: &str) -> craft_update::Release {
|
|
craft_update::Release {
|
|
tag: tag.to_string(),
|
|
version: craft_update::parse_version(tag),
|
|
html_url: format!("https://github.com/storytold/pdfcraft/releases/tag/{tag}"),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// `Ok(Some(tag))` is a newer release, `Ok(None)` is up to date.
|
|
fn source(answer: Result<Option<&str>, &str>) -> UpdateSource {
|
|
let answer = answer.map(|tag| tag.map(release)).map_err(str::to_string);
|
|
Arc::new(move || answer.clone())
|
|
}
|
|
|
|
fn harness(answer: Result<Option<&str>, &str>) -> Harness<'static, PdfCraftApp> {
|
|
Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_eframe(move |_cc| {
|
|
let mut app = PdfCraftApp::new();
|
|
app.set_option("language", "en").unwrap();
|
|
app.set_update_source(source(answer));
|
|
app
|
|
})
|
|
}
|
|
|
|
/// Run frames until the background check has reported. By what the dialog shows, with the
|
|
/// same 30-second deadline as `support::wait_until`, not for a fixed second: under load the
|
|
/// check's thread can take longer (#1277).
|
|
fn settle(h: &mut Harness<'static, PdfCraftApp>) {
|
|
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
|
|
loop {
|
|
h.run_steps(2);
|
|
if h.query_by_label_contains("Checking for a newer version").is_none() {
|
|
break;
|
|
}
|
|
assert!(std::time::Instant::now() < deadline, "timed out waiting for the update check");
|
|
std::thread::sleep(std::time::Duration::from_millis(5));
|
|
}
|
|
h.run_steps(2);
|
|
}
|
|
|
|
#[test]
|
|
fn a_newer_release_is_offered_for_download() {
|
|
let mut h = harness(Ok(Some("v99.0.0")));
|
|
h.state_mut().execute("help.check_updates");
|
|
settle(&mut h);
|
|
h.get_by_label_contains("PdfCraft 99.0.0 is available");
|
|
h.get_by_label("DOWNLOAD");
|
|
h.get_by_label("LATER").click();
|
|
h.run_steps(3);
|
|
assert!(h.query_by_label_contains("is available").is_none(), "Later closes the dialog");
|
|
}
|
|
|
|
#[test]
|
|
fn an_up_to_date_or_failed_check_says_so() {
|
|
let mut h = harness(Ok(None));
|
|
h.state_mut().execute("help.check_updates");
|
|
settle(&mut h);
|
|
h.get_by_label_contains("is up to date");
|
|
assert!(h.query_by_label("Download").is_none());
|
|
|
|
let mut h = harness(Err("couldn't reach GitHub"));
|
|
h.state_mut().execute("help.check_updates");
|
|
settle(&mut h);
|
|
h.get_by_label_contains("Couldn't check for updates: couldn't reach GitHub");
|
|
}
|
|
|
|
#[test]
|
|
fn nothing_is_asked_until_the_user_checks() {
|
|
// The check at start is opt-in (the desktop app turns it on): the source is only called from the command.
|
|
let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
|
let counted = calls.clone();
|
|
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_eframe(move |_cc| {
|
|
let mut app = PdfCraftApp::new();
|
|
app.set_option("language", "en").unwrap();
|
|
// Settings from a build that had the startup option are ignored.
|
|
app.restore(r#"{"check_updates_at_start": true}"#);
|
|
let counted = counted.clone();
|
|
app.set_update_source(Arc::new(move || {
|
|
counted.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
Ok(Some(release("v99.0.0")))
|
|
}));
|
|
app
|
|
});
|
|
settle(&mut h);
|
|
assert_eq!(calls.load(std::sync::atomic::Ordering::SeqCst), 0);
|
|
assert!(h.query_by_label_contains("is available").is_none());
|
|
h.state_mut().execute("help.check_updates");
|
|
settle(&mut h);
|
|
assert_eq!(calls.load(std::sync::atomic::Ordering::SeqCst), 1);
|
|
h.get_by_label_contains("PdfCraft 99.0.0 is available");
|
|
}
|
|
|
|
#[test]
|
|
fn the_check_at_start_offers_the_prompt_and_later_dismisses_it() {
|
|
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_eframe(|_cc| {
|
|
let mut app = PdfCraftApp::new();
|
|
app.set_option("language", "en").unwrap();
|
|
app.set_update_source(source(Ok(Some("v99.0.0"))));
|
|
app.set_startup_update_check(true);
|
|
app
|
|
});
|
|
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
|
|
while h.query_by_label_contains("is available").is_none() {
|
|
h.run_steps(2);
|
|
assert!(std::time::Instant::now() < deadline, "timed out waiting for the startup prompt");
|
|
std::thread::sleep(std::time::Duration::from_millis(5));
|
|
}
|
|
h.get_by_label("LATER").click();
|
|
h.run_steps(3);
|
|
assert!(h.query_by_label_contains("is available").is_none(), "Later closes the prompt");
|
|
}
|