wasm-actor-bridge 0.1.0

Typed, zero-copy Web Worker bridge for Rust/WASM actor systems
Documentation
[Back to README]../README.md | [Examples]../EXAMPLES.md

# Testing Without a Browser

`wasm-actor-bridge` is designed so that all actor *logic* can be tested with standard
`cargo test` — no browser, no wasm-bindgen-test harness, no headless Chrome needed.

---

## How It Works

`Context<Evt>` has two implementations:

- **WASM** — sends responses via `postMessage` back to the supervisor.
- **Native** — buffers responses in a `Vec` behind an `Arc<Mutex<...>>` that stays
  accessible from `clone()`d copies after the handler completes.

This means you can unit-test every `WorkerActor::handle` path on native Rust.

---

## Minimal Test Skeleton

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use wasm_actor_bridge::{Context, CancellationToken};

    // A test helper to run one command and collect responses.
    async fn run(cmd: MyCmd, bytes: Option<Vec<u8>>) -> Vec<MyEvt> {
        let mut actor = MyActor::default();
        actor.init(MyInit::default());

        let ctx = Context::<MyEvt>::new(bytes);
        let (token, _guard) = CancellationToken::new();
        actor.handle(cmd, ctx.clone(), token).await;
        ctx.take_responses().into_iter().map(|r| r.payload).collect()
    }

    #[tokio::test]
    async fn ping_returns_pong() {
        let responses = run(MyCmd::Ping, None).await;
        assert_eq!(responses, vec![MyEvt::Pong]);
    }

    #[tokio::test]
    async fn cancelled_before_start_returns_nothing() {
        let mut actor = MyActor::default();
        actor.init(MyInit::default());

        let ctx = Context::<MyEvt>::new(None);
        let (token, guard) = CancellationToken::new();
        drop(guard); // cancel immediately, before handle runs

        actor.handle(MyCmd::Ping, ctx.clone(), token).await;
        assert_eq!(ctx.response_count(), 0);
    }
}
```

---

## Testing Binary Payloads

```rust
#[tokio::test]
async fn upload_stores_bytes() {
    let payload = b"hello world".to_vec();
    let responses = run(MyCmd::Upload { name: "test.txt".into() }, Some(payload)).await;
    assert!(matches!(responses[0], MyEvt::Uploaded { .. }));
}
```

Pass `Some(bytes)` as the second argument to `Context::new` to simulate a binary transfer.
Inside the actor, `ctx.bytes()` will return those bytes.

---

## When You Do Need a Browser

Some things cannot be tested natively:

- `ArrayBuffer` zero-copy transfer semantics
- `Worker::postMessage` timing and backpressure
- `EventStream` ordering under real browser scheduling

For those, use [`wasm-bindgen-test`](https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/index.html)
with a headless browser target:

```rust
#[wasm_bindgen_test]
async fn worker_lifecycle_in_browser() {
    let (handle, _events) = SupervisorBuilder::<Cmd, Evt, Init>::new("/pkg/worker.js")
        .init(Init)
        .build()
        .unwrap();
    handle.send(Cmd::Ping).unwrap();
    // assert via events...
}
```

---

## CI Strategy

In CI, run the native tests on all platforms and a WASM compilation check:

```yaml
- run: cargo test                                       # native logic tests
- run: cargo build --target wasm32-unknown-unknown      # WASM compile check
```

Full browser tests are optional — they require a headless Chrome runner and add
considerable CI cost.