mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
Add opt-in browser download helpers that own detached links and object URLs, clean up failed or abandoned preparation, and register delayed URL cleanup before issuing a click. Applications retain filename, MIME type, pacing, and user-activation policy. Include browser lifecycle and failure-path tests, public API documentation, and Sonar test-source classification. All 32 PR checks passed; targeted WASM Clippy and formatting passed in local validation.
99 lines
3.2 KiB
Rust
99 lines
3.2 KiB
Rust
#![cfg(all(target_arch = "wasm32", feature = "browser"))]
|
|
use craft_platform::browser::{DownloadOptions, PreparedDownload, download_bytes};
|
|
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
|
|
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
|
|
wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
#[wasm_bindgen(module = "/tests/browser_harness.js")]
|
|
extern "C" {
|
|
fn setup(mode: &str);
|
|
fn restore();
|
|
fn created_count() -> u32;
|
|
fn revoked_count() -> u32;
|
|
fn clicked_count() -> u32;
|
|
fn anchor_count() -> u32;
|
|
fn last_name() -> String;
|
|
fn last_mime() -> String;
|
|
fn timer_before_click() -> bool;
|
|
fn wait_for_cleanup() -> js_sys::Promise;
|
|
}
|
|
struct Restore;
|
|
impl Drop for Restore {
|
|
fn drop(&mut self) {
|
|
restore();
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn abandoning_a_prepared_download_releases_its_url() -> Result<(), JsValue> {
|
|
setup("");
|
|
let _restore = Restore;
|
|
let prepared = PreparedDownload::bytes("export.txt", "text/plain", b"content")?;
|
|
assert_eq!(created_count(), 1);
|
|
drop(prepared);
|
|
assert_eq!(revoked_count(), 1);
|
|
assert_eq!(clicked_count(), 0);
|
|
assert_eq!(anchor_count(), 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn failed_attachment_and_timer_do_not_click_or_leak() {
|
|
for mode in ["attach-error", "timer-error"] {
|
|
setup(mode);
|
|
let _restore = Restore;
|
|
assert!(download_bytes("export.txt", "text/plain", b"content", Default::default()).is_err());
|
|
assert_eq!(created_count(), 1);
|
|
assert_eq!(revoked_count(), 1);
|
|
assert_eq!(clicked_count(), 0);
|
|
assert_eq!(anchor_count(), 0);
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
async fn successful_download_releases_its_url_after_the_click() -> Result<(), JsValue> {
|
|
setup("");
|
|
let _restore = Restore;
|
|
download_bytes("export.txt", "text/plain", b"content", DownloadOptions { revoke_after_ms: 5 })?;
|
|
assert_eq!(clicked_count(), 1);
|
|
assert_eq!(last_name(), "export.txt");
|
|
assert_eq!(last_mime(), "text/plain");
|
|
assert!(timer_before_click());
|
|
assert_eq!(revoked_count(), 0);
|
|
assert_eq!(anchor_count(), 0);
|
|
wasm_bindgen_futures::JsFuture::from(wait_for_cleanup()).await?;
|
|
assert_eq!(revoked_count(), 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn invalid_lifetime_cleans_a_prepared_download() {
|
|
setup("");
|
|
let _restore = Restore;
|
|
assert!(download_bytes("export.txt", "text/plain", b"content", DownloadOptions { revoke_after_ms: 0 }).is_err());
|
|
assert_eq!(revoked_count(), 1);
|
|
assert_eq!(clicked_count(), 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn failed_setup_does_not_allocate_a_url_or_click() {
|
|
for mode in ["element-error", "style-error"] {
|
|
setup(mode);
|
|
let _restore = Restore;
|
|
assert!(download_bytes("export.txt", "text/plain", b"content", Default::default()).is_err());
|
|
assert_eq!(created_count(), 0);
|
|
assert_eq!(revoked_count(), 0);
|
|
assert_eq!(clicked_count(), 0);
|
|
assert_eq!(anchor_count(), 0);
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn empty_name_is_rejected_before_allocating_resources() {
|
|
setup("");
|
|
let _restore = Restore;
|
|
assert!(download_bytes(" ", "text/plain", b"content", Default::default()).is_err());
|
|
assert_eq!(created_count(), 0);
|
|
assert_eq!(clicked_count(), 0);
|
|
}
|