From 8192e576cf40a2b7dc097bebde670fd2b6409665 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Fri, 22 May 2026 04:05:15 +0200 Subject: [PATCH] B6: add a WASM binding test suite The WASM binding had no tests; CI only checked that artefacts existed. Adds a wasm-bindgen-test suite covering SMA reference values, EMA batch==streaming equivalence, RSI pure-uptrend behaviour, fallible constructors returning JsError, and the unequal-length batch guards from B3. Wires wasm-pack test --node into the CI wasm job. The suite type-checks on the host; it executes under wasm-pack in CI (the local environment is a non-rustup Rust install without the wasm32 target). --- .github/workflows/ci.yml | 3 ++ bindings/wasm/Cargo.toml | 3 ++ bindings/wasm/src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6638d9bb..55520d49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,6 +112,9 @@ jobs: - name: Build WASM package run: wasm-pack build bindings/wasm --target web --release --features panic-hook + - name: Run WASM tests + run: wasm-pack test --node bindings/wasm + - name: Verify generated artefacts run: | test -f bindings/wasm/pkg/wickra_wasm.js diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 00beec02..121c299d 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -34,6 +34,9 @@ console_error_panic_hook = { version = "0.1", optional = true } default = [] panic-hook = ["dep:console_error_panic_hook"] +[dev-dependencies] +wasm-bindgen-test = "0.3" + [package.metadata.wasm-pack.profile.release.wasm-bindgen] debug-js-glue = false demangle-name-section = true diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index c49087ff..9355dfe3 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -649,3 +649,69 @@ impl WasmAroon { Ok(Float64Array::from(out.as_slice())) } } + +#[cfg(test)] +mod tests { + use super::*; + use wasm_bindgen_test::wasm_bindgen_test; + + #[wasm_bindgen_test] + fn sma_batch_reference_values() { + // SMA(3) of [2, 4, 6, 8, 10] -> [NaN, NaN, 4, 6, 8]. + let mut sma = WasmSma::new(3).ok().expect("valid period"); + let out = sma.batch(&[2.0, 4.0, 6.0, 8.0, 10.0]); + assert!(out.get_index(0).is_nan()); + assert!(out.get_index(1).is_nan()); + assert_eq!(out.get_index(2), 4.0); + assert_eq!(out.get_index(3), 6.0); + assert_eq!(out.get_index(4), 8.0); + } + + #[wasm_bindgen_test] + fn ema_batch_equals_streaming() { + let prices: Vec = (1..=60) + .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0) + .collect(); + let batch = WasmEma::new(14).ok().expect("valid").batch(&prices); + let mut ema = WasmEma::new(14).ok().expect("valid"); + for (i, &p) in prices.iter().enumerate() { + let b = batch.get_index(i as u32); + match ema.update(p) { + Some(v) => assert!((v - b).abs() < 1e-9, "streaming != batch at {i}"), + None => assert!(b.is_nan(), "expected NaN during warmup at {i}"), + } + } + } + + #[wasm_bindgen_test] + fn rsi_pure_uptrend_yields_100() { + let prices: Vec = (1..=20).map(f64::from).collect(); + let out = WasmRsi::new(14).ok().expect("valid").batch(&prices); + for i in 14..prices.len() { + assert_eq!(out.get_index(i as u32), 100.0); + } + } + + #[wasm_bindgen_test] + fn invalid_constructors_return_err() { + assert!(WasmSma::new(0).is_err()); + assert!(WasmMacd::new(0, 0, 0).is_err()); + assert!(WasmMacd::new(26, 12, 9).is_err()); + assert!(WasmBb::new(20, -1.0).is_err()); + assert!(WasmPsar::new(0.30, 0.02, 0.20).is_err()); + } + + #[wasm_bindgen_test] + fn batch_rejects_unequal_lengths() { + let mut atr = WasmAtr::new(14).ok().expect("valid"); + assert!(atr.batch(&[1.0, 2.0], &[1.0], &[1.0, 2.0]).is_err()); + let mut stoch = WasmStoch::new(14, 3).ok().expect("valid"); + assert!(stoch + .batch(&[1.0, 2.0, 3.0], &[1.0], &[1.0, 2.0, 3.0]) + .is_err()); + let mut mfi = WasmMfi::new(14).ok().expect("valid"); + assert!(mfi + .batch(&[1.0, 2.0], &[1.0, 2.0], &[1.0, 2.0], &[1.0]) + .is_err()); + } +}