CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m4s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m37s
CI/CD / Publish immutable image (pull_request) Canceled after 0s
CI/CD / test_and_check_app (pull_request) Canceled after 1m38s
111 lines
6.0 KiB
Rust
111 lines
6.0 KiB
Rust
#[cfg(target_os = "macos")]
|
|
#[tauri::command]
|
|
fn updater_smoke_menu(app: tauri::AppHandle) -> Result<(), String> {
|
|
let submenu = app.menu().unwrap().get("app-menu").unwrap();
|
|
let item = submenu.as_submenu().unwrap().get("install-update").unwrap();
|
|
let item = item.as_menuitem().unwrap();
|
|
for (available, busy, expected) in [
|
|
(false, false, false), // No update.
|
|
(true, false, true), // Available update, also after a retryable failure.
|
|
(true, true, false), // Download or installation in progress.
|
|
(true, false, true), // Retry after a failed download.
|
|
(false, true, false), // A new check clears the previous update.
|
|
(false, false, false), // Up to date.
|
|
] {
|
|
kanbangit_native::refresh_update_menu_for_smoke_test(&app, available, busy);
|
|
if item.is_enabled().map_err(|error| error.to_string())? != expected {
|
|
return Err(format!(
|
|
"Native install menu has wrong state: available={available}, busy={busy}"
|
|
));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
#[tauri::command]
|
|
fn updater_smoke_report(app: tauri::AppHandle, passed: bool, reason: String) {
|
|
use tauri::Manager;
|
|
println!("Updater WebView test: passed={passed}, {reason}");
|
|
app.state::<std::sync::Arc<std::sync::atomic::AtomicBool>>()
|
|
.store(passed, std::sync::atomic::Ordering::SeqCst);
|
|
app.exit(if passed { 0 } else { 1 });
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn main() {
|
|
use std::sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc,
|
|
};
|
|
use tauri::webview::PageLoadEvent;
|
|
let passed = Arc::new(AtomicBool::new(false));
|
|
let config: serde_json::Value =
|
|
serde_json::from_str(include_str!("../tauri.conf.json")).unwrap();
|
|
let expected_installed = format!(
|
|
"Installed version {} (build {})",
|
|
config["version"].as_str().unwrap(),
|
|
config["bundle"]["macOS"]["bundleVersion"].as_str().unwrap()
|
|
);
|
|
|
|
let plugin = tauri::plugin::Builder::<tauri::Wry>::new("updater-smoke")
|
|
.js_init_script(format!("window.expectedInstalled = {};", serde_json::to_string(&expected_installed).unwrap()))
|
|
.on_page_load(|window, payload| {
|
|
if window.label() != "updates" || payload.event() != PageLoadEvent::Finished { return; }
|
|
window.eval(r#"
|
|
(async () => {
|
|
try {
|
|
for (let i = 0; i < 100 && !window.updateStatus; i++) await new Promise(r => setTimeout(r, 50));
|
|
if (!window.updateStatus) throw new Error('Update UI did not load');
|
|
const $ = id => document.getElementById(id);
|
|
for (let i = 0; i < 100 && !$('installed').textContent; i++) await new Promise(r => setTimeout(r, 50));
|
|
if ($('installed').textContent !== window.expectedInstalled) throw new Error('Actual installed version/build failed: ' + $('installed').textContent);
|
|
await window.__TAURI_INTERNALS__.invoke('updater_smoke_menu');
|
|
const state = { phase: 'current', currentVersion: '0.1.2', build: '2', version: null, notes: null, downloaded: 0, total: null, error: null, preferences: { automatic: true, intervalHours: 6 } };
|
|
window.updateStatus(state);
|
|
if (!$('installed').textContent.includes('0.1.2 (build 2)') || !$('install').hidden || $('check').disabled) throw new Error('Current version state failed');
|
|
Object.assign(state, { phase: 'available', version: '0.1.3', notes: '<script>unsafe()</script>\nRelease notes' });
|
|
window.updateStatus(state);
|
|
if ($('install').hidden || $('release-notes').querySelector('script') || !$('release-notes').textContent.includes('<script>')) throw new Error('Available state or text escaping failed');
|
|
$('install').click();
|
|
if ($('install-confirmation').hidden || !$('install-confirmation').textContent.includes('Unsaved inputs')) throw new Error('Missing restart confirmation');
|
|
$('cancel-install').click();
|
|
if (!$('install-confirmation').hidden) throw new Error('Cancel failed');
|
|
Object.assign(state, { phase: 'downloading', downloaded: 500, total: 1000 }); window.updateStatus(state);
|
|
if (!$('check').disabled || !$('install').hidden || $('download-progress').value !== 500) throw new Error('Progress state failed');
|
|
Object.assign(state, { phase: 'error', error: 'signatureError' }); window.updateStatus(state);
|
|
if ($('install').hidden || !$('update-error').textContent.includes('Nothing was installed')) throw new Error('Retry failed');
|
|
state.preferences.automatic = false; window.updateStatus(state);
|
|
if ($('automatic').checked || !$('interval').disabled) throw new Error('Opt out failed');
|
|
await window.__TAURI_INTERNALS__.invoke('updater_smoke_report', { passed: true, reason: '' });
|
|
} catch (error) {
|
|
await window.__TAURI_INTERNALS__.invoke('updater_smoke_report', { passed: false, reason: error.message });
|
|
}
|
|
})();
|
|
"#).unwrap();
|
|
})
|
|
.build();
|
|
let mut context = kanbangit_native::context();
|
|
context.config_mut().identifier = format!("de.kanbangit.smoke.updates.{}", std::process::id());
|
|
let app = kanbangit_native::builder()
|
|
.manage(passed.clone())
|
|
.invoke_handler(tauri::generate_handler![
|
|
updater_smoke_report,
|
|
updater_smoke_menu
|
|
])
|
|
.plugin(plugin)
|
|
.build(context)
|
|
.expect("App must start");
|
|
let handle = app.handle().clone();
|
|
std::thread::spawn(move || {
|
|
std::thread::sleep(std::time::Duration::from_secs(30));
|
|
handle.exit(1);
|
|
});
|
|
app.run_return(|_, _| {});
|
|
assert!(passed.load(Ordering::SeqCst), "Updater UI test must pass");
|
|
}
|
|
#[cfg(not(target_os = "macos"))]
|
|
fn main() {
|
|
panic!("Requires macOS");
|
|
}
|