Merge pull request #12384 from dotCooCoo/chore/craft-update-updater-fixes

Updates: the updater reports installing, ready, installed and failed, and installs at exit
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-08 22:12:53 -05:00
commit 95c9e465df
8 files changed
+181 -51

No files matched your search

Generated
+14 -27
View File
@@ -1406,17 +1406,17 @@ dependencies = [
[[package]]
name = "craft-docpath"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
[[package]]
name = "craft-fonts-build"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
[[package]]
name = "craft-i18n"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"fixed_decimal",
"icu_decimal",
@@ -1439,7 +1439,7 @@ dependencies = [
[[package]]
name = "craft-jpeg"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"thiserror 2.0.21",
]
@@ -1457,7 +1457,7 @@ dependencies = [
[[package]]
name = "craft-pdf"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"bytemuck",
"hayro",
@@ -1469,7 +1469,7 @@ dependencies = [
[[package]]
name = "craft-print"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"craft-winprint-gdi",
"log",
@@ -1483,7 +1483,7 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"craft-fonts-build",
"craft-i18n",
@@ -1504,7 +1504,7 @@ dependencies = [
[[package]]
name = "craft-update"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"chrono",
"dunce",
@@ -1530,7 +1530,7 @@ dependencies = [
[[package]]
name = "craft-winprint-gdi"
version = "0.1.0"
source = "git+https://github.com/storytold/winprint.rs?rev=417ac3c4d14d3da5485a99c7d5ce8f05664ec312#417ac3c4d14d3da5485a99c7d5ce8f05664ec312"
source = "git+https://github.com/storytold/winprint.rs?rev=64e7afd4d1483512385012c340ecaaaf0f3beb7f#64e7afd4d1483512385012c340ecaaaf0f3beb7f"
dependencies = [
"windows-sys 0.61.2",
]
@@ -2964,7 +2964,7 @@ dependencies = [
[[package]]
name = "hayro"
version = "0.7.1"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"bytemuck",
"hayro-interpret",
@@ -2994,7 +2994,7 @@ dependencies = [
[[package]]
name = "hayro-interpret"
version = "0.7.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"bitflags 2.13.2",
"hayro-cmap",
@@ -3012,7 +3012,7 @@ dependencies = [
[[package]]
name = "hayro-jbig2"
version = "0.3.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"fearless_simd",
"hayro-ccitt",
@@ -3036,7 +3036,7 @@ checksum = "bc2d80da85f31c3fdc0366431f44cfd645a9da1e242ed1f4a61f213d9765660f"
[[package]]
name = "hayro-syntax"
version = "0.7.2"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"flate2",
"hayro-ccitt",
@@ -9473,7 +9473,7 @@ dependencies = [
[[package]]
name = "winprint"
version = "0.2.1"
source = "git+https://github.com/storytold/winprint.rs?rev=5f80b9baf6b58937c401656634e42d33f00ddf49#5f80b9baf6b58937c401656634e42d33f00ddf49"
source = "git+https://github.com/storytold/winprint.rs?rev=64e7afd4d1483512385012c340ecaaaf0f3beb7f#64e7afd4d1483512385012c340ecaaaf0f3beb7f"
dependencies = [
"flate2",
"fmt-derive",
@@ -10006,7 +10006,6 @@ dependencies = [
"nix",
"typed-path",
"windows-sys 0.61.2",
"zopfli",
]
[[package]]
@@ -10021,18 +10020,6 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zopfli"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]
[[package]]
name = "zune-core"
version = "0.4.12"
+10 -10
View File
@@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0"
rust-version = "1.95"
[workspace.dependencies]
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["serde"] }
craft-ui = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", features = ["serde"] }
# internal
photocraft-geom = { path = "crates/geom" }
photocraft-color = { path = "crates/color" }
@@ -39,13 +39,13 @@ photocraft-tablet = { path = "crates/tablet" }
photocraft-ipp = { path = "crates/ipp" }
# external
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["raster"] }
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
craft-jpeg = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
craft-print = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
craft-pdf = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", features = ["raster"] }
craft-docpath = { version = "0.1.0", git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
craft-jpeg = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
craft-pcraft-format = { git = "https://github.com/storytold/craft-libs", rev = "b8f7d93b00dfb8f172938f3d2d796ae62dcfe123" }
# craft-update: app-specific pretend versions apply to startup and manual checks.
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] }
craft-update = { git = "https://github.com/dotCooCoo/craft-libs", version = "=0.1.0", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", features = ["egui"] }
thiserror = "2"
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
@@ -205,7 +205,7 @@ egui_demo_lib = { git = "https://github.com/storytold/egui.git", rev = "6d518fc7
egui_inspection = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
winit = { git = "https://github.com/storytold/winit.git", rev = "a517b3846ee8457d84ee4975110bfda6797c44dc" }
# craft-pdf's vendored Hayro trees: consumers patch the same craft-libs revision (crates/pdf README).
hayro = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
hayro = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
hayro-syntax = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
hayro-interpret = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
hayro-jbig2 = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
+1
View File
@@ -763,6 +763,7 @@ pub fn native(automation: Option<photocraft_automation::AuthorizedWorkspace>) ->
open_url: Some(Box::new(|url: &str| open::that(url).map_err(|e| e.to_string()))),
beep: Some(platform_beep),
check_updates: Some(crate::updates::service()),
last_update: Some(crate::updates::last_update),
clipboard_set_image: Some({
let clip = clip.clone();
guarded_clipboard_set(move |w: u32, h: u32, px: &[u8]| {
+3 -1
View File
@@ -218,7 +218,9 @@ fn open_filters_keep_literal_extensions_on_windows_and_macos() {
#[test]
fn native_services_provide_update_transport() {
assert!(native(None).check_updates.is_some());
let services = native(None);
assert!(services.check_updates.is_some());
assert!(services.last_update.is_some());
}
#[test]
+13
View File
@@ -20,6 +20,19 @@ pub fn service() -> CheckFn {
})
}
/// What the last self-update came to, once: `craft-update` removes its records as it reads them.
pub fn last_update() -> Option<Result<String, String>> {
use craft_update::self_update::{Outcome, current_version, take_outcome};
let app = &photocraft_ui_egui::UPDATE_APP;
let paths = craft_update::Paths::for_app(app).ok()?;
let version = photocraft_engine::build_info::VERSION;
let running = current_version(app, version).map_or_else(|| version.to_string(), |v| v.to_string());
match take_outcome(&paths, &running)? {
Outcome::Installed(installed) => Some(Ok(installed.to_string())),
Outcome::Failed(reason) => Some(Err(reason)),
}
}
fn check() -> CheckResult {
let http = craft_update::Http::new("PhotoCraft/Update")?;
let found = craft_update::check_for_update(&photocraft_ui_egui::UPDATE_APP, &http, photocraft_engine::build_info::VERSION)?;
+2 -2
View File
@@ -263,8 +263,8 @@ pub use services::{
AdoptAutosaveFn, AppendTextFn, AutomationCommandFn, AutomationOpenPcraftFn, AutomationReadFn, AutomationWriteFn, AutosaveFn, AutosaveResultsFn, CapsLockFn,
CaptionStyleFn, ClipboardGetFn, ClipboardGetSvgFn, ClipboardSetFn, ClipboardUnreadableFn, CursorPosFn, DiscardAutosaveFn, EncodePngFn, ExportFn,
ExportSettings, ExternalSvgEditor, ExternalSvgSession, ExternalVectorSave, ExternalVectorSource, FileStamp, FileStampFn, FormatChoices, ImportFn, Inbox,
LoadTextFn, OpenExternalSvgFn, OpenUrlFn, OsEventsFn, PenEndFn, PhotoshopShortcutsFn, PlaceInbox, QuitFn, RecoverFn, Recoverable, RightButtonDownFn,
SameFileFn, SaveFileFn, SaveTextFn, Services, SystemThemeFn, TabletPollFn, WindowFrameFn, WriteFn,
LastUpdateFn, LoadTextFn, OpenExternalSvgFn, OpenUrlFn, OsEventsFn, PenEndFn, PhotoshopShortcutsFn, PlaceInbox, QuitFn, RecoverFn, Recoverable,
RightButtonDownFn, SameFileFn, SaveFileFn, SaveTextFn, Services, SystemThemeFn, TabletPollFn, WindowFrameFn, WriteFn,
};
pub use text::bidi_text;
pub use text::cjk_fonts;
+7
View File
@@ -429,6 +429,10 @@ pub type PenEndFn = Box<dyn Fn() -> Option<bool> + Send + Sync>;
/// whether the app window is focused (to re-assert WinTab overlap priority on activation).
pub type TabletPollFn = Box<dyn FnMut(bool)>;
/// What the last self-update came to: `Ok` with the version it installed, `Err` with the reason it
/// could not be installed, `None` when there is nothing to report ([`Services::last_update`]).
pub type LastUpdateFn = fn() -> Option<Result<String, String>>;
/// Platform services injected by the app binary (file dialogs, codecs), keeping this crate free of
/// I/O dependencies.
#[derive(Default)]
@@ -486,6 +490,9 @@ pub struct Services {
pub alert: Option<fn()>,
/// Starts a background check for published releases (desktop, outside Flatpak).
pub check_updates: Option<photocraft_engine::updates_cmds::CheckFn>,
/// What the last self-update came to, read once at the first frame: `Ok` with the version it
/// installed, `Err` with the reason it could not be installed (desktop).
pub last_update: Option<LastUpdateFn>,
/// Files delivered asynchronously (web drag-and-drop): drained every frame and opened as
/// documents.
pub inbox: Option<Inbox>,
+131 -11
View File
@@ -11,6 +11,12 @@ pub(crate) struct Runtime {
/// The startup prompt and the self-update behind it (desktop).
#[cfg(not(target_arch = "wasm32"))]
updater: craft_update::updater::Updater,
/// What the last self-update came to, shown in the result window until it is closed.
#[cfg(not(target_arch = "wasm32"))]
last_update: craft_update::updater::Status,
/// The check running was asked for with Help ▸ Check for Updates.
#[cfg(not(target_arch = "wasm32"))]
requested: bool,
}
impl Default for Runtime {
@@ -20,6 +26,10 @@ impl Default for Runtime {
notified_version: None,
#[cfg(not(target_arch = "wasm32"))]
updater: craft_update::updater::Updater::new(&crate::UPDATE_APP, photocraft_engine::build_info::VERSION),
#[cfg(not(target_arch = "wasm32"))]
last_update: craft_update::updater::Status::Idle,
#[cfg(not(target_arch = "wasm32"))]
requested: false,
}
}
}
@@ -51,11 +61,33 @@ pub(crate) fn check(app: &mut PhotocraftApp, _ctx: &egui::Context, params: Value
app.updates_rt.startup_checked = true;
// A release the user dismissed is offered again by a manual check.
app.updates_rt.notified_version = None;
// The window shows this check from now on.
#[cfg(not(target_arch = "wasm32"))]
{
app.updates_rt.requested = true;
app.updates_rt.updater.dismiss_status();
app.updates_rt.last_update = craft_update::updater::Status::Idle;
// A check made before the first frame leaves the last self-update's records for the
// next start.
app.services.last_update = None;
}
}
result
}
pub(crate) fn tick(app: &mut PhotocraftApp, ctx: &egui::Context) {
#[cfg(not(target_arch = "wasm32"))]
if let Some(read) = app.services.last_update.take() {
use craft_update::updater::Status as Shown;
app.updates_rt.last_update = match read() {
Some(Ok(version)) => Shown::Updated(version),
Some(Err(reason)) => Shown::InstallFailed(reason),
None => Shown::Idle,
};
if app.updates_rt.last_update != Shown::Idle {
app.ui.updates_open = true;
}
}
// `updates.status` also polls (automation may consume the result before this frame).
app.session.updates.poll();
if let Status::Available { release } = &app.session.updates.status
@@ -64,7 +96,19 @@ pub(crate) fn tick(app: &mut PhotocraftApp, ctx: &egui::Context) {
app.updates_rt.notified_version = Some(release.version.clone());
app.ui.updates_open = true;
#[cfg(not(target_arch = "wasm32"))]
app.updates_rt.updater.offer(release_of(release));
{
let updater = &mut app.updates_rt.updater;
updater.offer(release_of(release));
// While an update installs or waits for the app to close, or while the launcher keeps
// this version, no prompt opens: a manual check reports that state in the result window.
if std::mem::take(&mut app.updates_rt.requested) && !updater.prompt_open() {
updater.check_now(ctx);
}
}
}
#[cfg(not(target_arch = "wasm32"))]
if !matches!(app.session.updates.status, Status::Checking) {
app.updates_rt.requested = false;
}
if app.session.updates.check.is_none() {
return;
@@ -89,7 +133,7 @@ pub(crate) fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
{
let was_open = app.updates_rt.updater.prompt_open();
app.updates_rt.updater.tick(ctx);
// The restart helper is scheduled before the app closes, so no update starts over unsaved work.
// An update starts, and a ready one asks the app to close, only while no work is unsaved.
let clean = !app.has_unsaved_work();
match app.updates_rt.updater.show_if(ctx, clean) {
craft_update::updater::Action::None => {}
@@ -98,8 +142,9 @@ pub(crate) fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
// relaunches once the process exits.
craft_update::updater::Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
}
// The last self-update's outcome, hidden while the prompt showed, is still shown after it.
if was_open && !app.updates_rt.updater.prompt_open() {
app.ui.updates_open = false;
app.ui.updates_open = app.updates_rt.last_update != craft_update::updater::Status::Idle;
}
}
if let Some(url) = download {
@@ -108,15 +153,37 @@ pub(crate) fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
if app.updates_rt.updater.prompt_open() {
return;
}
if app.ui.updates_open {
let dialog = craft_update::CheckResultDialog::new(&crate::UPDATE_APP, photocraft_engine::build_info::VERSION);
match dialog.show(ctx, &check_status(&app.session.updates.status)) {
craft_update::CheckResultResponse::None => {}
craft_update::CheckResultResponse::Close => app.ui.updates_open = false,
craft_update::CheckResultResponse::CheckAgain => {
let _ = check(app, ctx, serde_json::json!({}));
}
let status = shown_status(app);
let dialog = craft_update::CheckResultDialog::new(&crate::UPDATE_APP, photocraft_engine::build_info::VERSION);
match dialog.show(ctx, &status) {
craft_update::CheckResultResponse::None => {}
craft_update::CheckResultResponse::Close => {
app.ui.updates_open = false;
app.updates_rt.updater.dismiss_status();
app.updates_rt.last_update = craft_update::updater::Status::Idle;
}
craft_update::CheckResultResponse::CheckAgain => {
let _ = check(app, ctx, serde_json::json!({}));
}
craft_update::CheckResultResponse::RestartToUpdate => app.updates_rt.updater.restart_to_update(),
}
}
/// What the open result window shows: the updater's answer to a manual check (an update installing
/// or ready, or a version the launcher keeps), else the last self-update's outcome, else the
/// engine's check.
#[cfg(not(target_arch = "wasm32"))]
fn shown_status(app: &PhotocraftApp) -> craft_update::updater::Status {
use craft_update::updater::Status as Shown;
let updater = app.updates_rt.updater.status();
if !app.ui.updates_open {
Shown::Idle
} else if *updater != Shown::Idle {
updater.clone()
} else if app.updates_rt.last_update != Shown::Idle {
app.updates_rt.last_update.clone()
} else {
check_status(&app.session.updates.status)
}
}
@@ -270,6 +337,59 @@ mod tests {
assert!(app.updates_rt.updater.prompt_open());
}
#[cfg(not(target_arch = "wasm32"))]
fn press_escape(app: &mut PhotocraftApp, ctx: &egui::Context) {
let escape = egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE };
let mut output = ctx.run_ui(egui::RawInput { events: vec![escape], ..Default::default() }, |ui| show(app, ui.ctx()));
output.textures_delta.clear();
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn the_last_update_opens_the_result_window_once_until_it_is_closed() {
use craft_update::updater::Status as Shown;
let (mut app, _, _) = app();
app.services.last_update = Some(|| Some(Ok("99.0.0".into())));
let ctx = egui::Context::default();
at(&mut app, &ctx, 0.0);
assert!(app.ui.updates_open);
assert_eq!(shown_status(&app), Shown::Updated("99.0.0".into()));
assert!(app.services.last_update.is_none(), "read once");
at(&mut app, &ctx, 1.0);
assert_eq!(shown_status(&app), Shown::Updated("99.0.0".into()), "shown until closed");
press_escape(&mut app, &ctx);
assert!(!app.ui.updates_open);
assert_eq!(app.updates_rt.last_update, Shown::Idle);
assert_eq!(shown_status(&app), Shown::Idle);
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn a_failed_update_is_reported_until_a_manual_check_replaces_it() {
use craft_update::updater::Status as Shown;
let (mut app, calls, _) = app();
app.services.last_update = Some(|| Some(Err("the installer exited with code 1603".into())));
app.session.edit_prefs(|p| p.general.automatically_check_updates = false);
let ctx = egui::Context::default();
at(&mut app, &ctx, 0.0);
assert_eq!(shown_status(&app), Shown::InstallFailed("the installer exited with code 1603".into()));
menus::invoke(&mut app, &ctx, "help.checkForUpdates", serde_json::json!({})).unwrap();
assert_eq!(*calls.lock().unwrap(), 1);
assert_eq!(shown_status(&app), Shown::Checking, "the window shows the new check");
let (mut early, _, _) = self::app();
early.services.last_update = Some(|| Some(Err("unread".into())));
menus::invoke(&mut early, &ctx, "help.checkForUpdates", serde_json::json!({})).unwrap();
at(&mut early, &ctx, 0.0);
assert_eq!(shown_status(&early), Shown::Checking, "a check before the first frame is not replaced by the last update");
assert!(early.services.last_update.is_none());
let (mut quiet, _, _) = self::app();
quiet.services.last_update = Some(|| None);
at(&mut quiet, &ctx, 0.0);
assert!(!quiet.ui.updates_open, "nothing to report opens nothing");
}
#[test]
fn a_manual_check_that_fails_to_start_opens_the_status_window() {
let mut app =