Files
KanbanGit/native/src-tauri/tests/macos_smoke.rs
T
Twyco 32a6756419
CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m17s
CI/CD / test_and_check_app (pull_request) Successful in 6m24s
CI/CD / Publish immutable image (pull_request) Skipped
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m27s
feat(KAN-69): open saved native workspace directly in dashboard
2026-09-28 21:00:46 +02:00

172 lines
9.9 KiB
Rust

#[cfg(target_os = "macos")]
fn main() {
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc,
};
use tauri::{webview::PageLoadEvent, Manager};
let passed = Arc::new(AtomicBool::new(false));
let stage = Arc::new(AtomicUsize::new(0));
let page_stage = stage.clone();
let result_passed = passed.clone();
let plugin = tauri::plugin::Builder::<tauri::Wry>::new("macos-smoke")
.setup(|app, _| {
let directory = app.path().app_config_dir()?;
std::fs::create_dir_all(&directory)?;
std::fs::write(directory.join("active-server"), "https://127.0.0.1:9/\n0123456789abcdef0123456789abcdef")?;
Ok(())
})
.js_init_script(r#"
if (location.protocol === 'tauri:') {
window.__selectionFlashed = false;
new MutationObserver(() => {
const selection = document.querySelector('#workspace-settings');
const phase = document.querySelector('#connection-status')?.dataset.state;
if (selection && !selection.hidden && phase !== 'connectionError') window.__selectionFlashed = true;
}).observe(document, { subtree: true, attributes: true, childList: true });
}
"#)
.js_init_script("window.addEventListener('error', event => { location.href = 'tauri://localhost/__native_smoke_result?passed=false&error=' + encodeURIComponent(event.message || event.target.src); }, true);")
.on_page_load(move |webview, payload| {
eprintln!("Smoke load: {} {:?} {}", webview.label(), payload.event(), payload.url());
if payload.event() != PageLoadEvent::Finished {
return;
}
if webview.label() == "launcher" {
webview.eval(r#"
(async () => {
const wait = async predicate => {
for (let attempt = 0; attempt < 220; attempt++) {
if (predicate()) return;
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error('Timed out waiting for local state');
};
try {
await wait(() => document.querySelector('#connection-status')?.dataset.state === 'connectionError');
if (window.__selectionFlashed) throw new Error('Workspace selection flashed during saved startup');
if (document.querySelector('#server-url').value !== 'https://127.0.0.1:9/') throw new Error('Saved workspace was discarded');
const input = document.querySelector('#server-url');
const form = document.querySelector('#server-form');
const status = document.querySelector('#connection-status');
input.value = 'http://example.com';
form.requestSubmit();
if (input.getAttribute('aria-invalid') !== 'true' || !status.textContent.includes('HTTPS')) throw new Error('Invalid address accepted');
for (const [theme, expected] of [['dark', 'rgb(15, 23, 42)'], ['light', 'rgb(247, 249, 251)']]) {
const appearance = document.querySelector('#appearance');
appearance.value = theme;
appearance.dispatchEvent(new Event('change'));
if (getComputedStyle(document.documentElement).backgroundColor !== expected) throw new Error('Theme failed');
}
input.value = 'https://127.0.0.1:9/';
form.requestSubmit();
await wait(() => status.dataset.state === 'connectionError');
if (input.value !== 'https://127.0.0.1:9/' || document.querySelector('.open-workspace').disabled) throw new Error('Offline recovery failed');
input.value = 'https://kanbangit.de/';
form.requestSubmit();
if (document.querySelector('#switch-confirmation').hidden) throw new Error('Missing switch confirmation');
document.querySelector('#cancel-switch').click();
if (!document.querySelector('#switch-confirmation').hidden) throw new Error('Cancel failed');
form.requestSubmit();
document.querySelector('#confirm-switch').click();
} catch (error) {
location.href = 'tauri://localhost/__native_smoke_result?passed=false&reason=' + encodeURIComponent(error.message);
}
})();
"#).unwrap();
} else if webview.label().starts_with("workspace-") && payload.url().host_str() == Some("kanbangit.de") {
let current_stage = page_stage.fetch_add(1, Ordering::SeqCst);
if current_stage == 0 {
webview.eval(r#"
(async () => {
for (let attempt = 0; attempt < 100; attempt++) {
if (document.querySelector('#app')?.innerText.trim().length > 100) break;
await new Promise(resolve => setTimeout(resolve, 100));
}
const rendered = document.querySelector('#app')?.innerText.trim().length > 100;
let denied = true;
for (const cmd of ['plugin:path|resolve_directory', 'plugin:window|get_all_windows']) {
try { await window.__TAURI_INTERNALS__.invoke(cmd); denied = false; }
catch (error) { denied = denied && String(error).includes('not allowed'); }
}
const input = document.createElement('input');
input.id = 'kan52-unsaved'; input.value = 'unsaved input'; document.body.append(input);
document.cookie = 'kan52_fixture=isolated; Path=/; Secure; SameSite=Lax';
localStorage.setItem('kan52_fixture', 'isolated');
const ok = rendered && denied && typeof window.__TAURI__ === 'undefined' && location.pathname === '/login' && history.length === 1;
location.href = '/__native_smoke_result?stage=loaded&passed=' + ok;
})();
"#).unwrap();
} else {
webview.eval(r#"
const isolated = !document.cookie.includes('kan52_fixture') && localStorage.getItem('kan52_fixture') === null;
location.href = '/__native_smoke_result?stage=isolated&passed=' + isolated;
"#).unwrap();
}
}
})
.on_navigation(move |webview, url| {
eprintln!("Smoke navigation: {} {}", webview.label(), url);
if url.path() != "/__native_smoke_result" {
return true;
}
let success = url.query_pairs().any(|(key, value)| key == "passed" && value == "true");
let loaded = url.query_pairs().any(|(key, value)| key == "stage" && value == "loaded");
println!("macOS WebView smoke test: {}", url.query().unwrap_or_default());
if success && loaded {
let app = webview.app_handle().clone();
let label = webview.label().to_string();
std::thread::spawn(move || {
let window = app.get_webview_window(&label).unwrap();
// A blocked cross-origin navigation must leave the existing input intact.
window.navigate(tauri::Url::parse("https://kanbangit.de:9/unreachable").unwrap()).unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
window.eval(r#"
if (document.querySelector('#kan52-unsaved')?.value !== 'unsaved input') {
location.href = '/__native_smoke_result?passed=false&stage=preservation';
}
"#).unwrap();
let launcher = app.get_webview_window("launcher").unwrap();
launcher.show().unwrap();
launcher.eval(r#"
document.querySelector('#server-url').value = 'https://kanbangit.de/login/';
document.querySelector('#server-form').requestSubmit();
document.querySelector('#confirm-switch').click();
"#).unwrap();
});
} else {
result_passed.store(success, Ordering::SeqCst);
webview.app_handle().exit(if success { 0 } else { 1 });
}
false
})
.build();
let mut context = kanbangit_native::context();
context.config_mut().identifier = format!("de.kanbangit.smoke.kan52.{}", std::process::id());
let app = kanbangit_native::builder()
.plugin(plugin)
.build(context)
.expect("The macOS application must start");
let config_dir = app.path().app_config_dir().unwrap();
let handle = app.handle().clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(60));
eprintln!("macOS smoke test timed out");
handle.exit(1);
});
app.run_return(|_, _| {});
let _ = std::fs::remove_dir_all(config_dir);
assert!(
passed.load(Ordering::SeqCst),
"The native smoke test must complete successfully"
);
}
#[cfg(not(target_os = "macos"))]
fn main() {
panic!("Run this smoke test on macOS with an active graphical session");
}