vanilla-test
v2.1.3
Published
Zero-build, Web-standard JavaScript testing for Node and browsers
Maintainers
Readme
vanilla-test
Website · JavaScript README · Rust README · Testing · Benchmarks · Changelog
Minimal testing in the language you already ship. JavaScript uses one native ES module in Node.js and browsers. Rust uses one dependency-free, unsafe-free implementation natively and compiles the same Rust unit tests into browser WebAssembly.
Choose your language
| Language | Native path | Browser path | Package | Language documentation |
| --- | --- | --- | --- | --- |
| JavaScript | Node.js 22.12+ native ESM | Standards-only browser modules | npm | Node + browser |
| Rust | Rust 1.85+ and Cargo | The same Rust unit-test harness compiled to wasm32-unknown-unknown | crates.io | Native + browser WASM |
JavaScript is the right lane when the application is JavaScript and needs identical source in Node and a real browser. Rust is the right lane when the application and tests should stay Rust across native and browser targets. The APIs preserve the same sequential lifecycle and first-decision rule, but each implementation stays idiomatic to its language rather than wrapping the other runtime.
Install
JavaScript
npm install vanilla-testRust
cargo add vanilla-testCheck the terminal for a successful exit. JavaScript and Rust package versions share the same major and minor line; patches may advance independently.
One lifecycle, native implementations
JavaScript
import VanillaTest from 'vanilla-test';
const test = new VanillaTest();
test.expects('addition preserves the total');
try {
test.compare(1 + 2, 3);
test.pass();
} catch (error) {
console.error(error);
test.fail();
}
test.done();
const result = test.report();The same module can run unchanged in Node and the browser. Host adapters decide how to display the immutable result or map it to an exit status.
Rust
use vanilla_test::VanillaTest;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut test = VanillaTest::new();
test.expects("addition preserves the total")?;
if 1 + 2 == 3 { test.pass()?; } else { test.fail()?; }
test.done()?;
let result = test.report()?;
assert!(result.ok, "{}", result.report);
Ok(())
}Native Rust uses ordinary results and the type system. The browser lane compiles the existing Rust unit tests—there is no copied browser suite:
rustup target add wasm32-unknown-unknown
cargo install vanilla-test --version 2.1.0
cargo vanilla-test --browserThe Cargo subcommand builds the current package's existing library tests and emits dist/vanilla-test/ with the WASM harness and static loader. That page checks its visible browser UI, canonical wasm32-unknown-unknown target label, artifact response, WASM MIME type, streaming instantiation, and main() export before calling the generated Rust harness once. Every check appears on-page and in DevTools. Pass --page tests/browser.html to use a compatible site page whose marked visible-text checks run alongside the Rust harness. A zero harness status passes; a panic or nonzero status fails. Native cargo test prints the individual Rust names and assertion diagnostics because the bare browser target has no stdout. Run and inspect the current Rust browser example.
Why Vanilla Test
Vanilla is a control-surface choice: keep fewer test-specific layers between source and evidence.
- One language end to end. Choose JavaScript for Node + browser or Rust for native + browser WASM.
- One test inventory per language. Runtime adapters execute the same source instead of maintaining browser copies.
- Native execution. Node and Chrome run the JavaScript module directly; Cargo and the browser run Rust and WebAssembly directly.
- No build framework. JavaScript needs no bundle or transpiler. The dependency-free Cargo subcommand uses Cargo's standard target support with no wasm framework or generated glue.
- Small contracts. One active test, exact unique descriptions, first decision wins, undecided completion fails, and reporting seals the suite.
- Auditable evidence. CI publishes runtime-specific tests, native V8 coverage for JavaScript, built-in LLVM source coverage for Rust, package smoke checks, a real-Chrome Rust WASM run, and reproducible benchmarks.
The JavaScript package deliberately keeps its runtime validation and ANSI rendering dependencies focused. The Rust crate has zero dependencies and forbids unsafe code.
Benchmarks
Benchmarks are separate by language and protocol. They describe observed work on one recorded machine; they are not a claim that unlike runtimes or workloads are interchangeable.
| Language/runtime | Verified workload | Median | Throughput | | --- | --- | ---: | ---: | | JavaScript · Node 24.18 | 1,000,000 passing cases across 1,000 suites, lifecycle + report | 2,589.836 ms | 386,125 cases/s | | JavaScript · Chrome 151 | The same 1,000,000-case browser workload | 2,074.900 ms | 481,951 cases/s | | Rust 1.85 · native optimized | 100,000 uniquely named passing cases, lifecycle timed and report validated | 13.278 ms | 7,531,368 cases/s |
The JavaScript figures are five-sample medians from the published native-pipeline dataset, including exact runtime, memory, source, and machine provenance. The Rust figure is a five-sample median after one warmup on the same Intel Core Ultra 9 275HX machine; its raw samples and provenance record the timed boundary and post-timer report validation.
Reproduce them independently:
npm run benchmark
cargo bench --bench lifecycle -- 100000 5See benchmark methodology and raw JavaScript samples, the raw Rust record, and the benchmark site. Results should be compared within a lane and protocol, not used as a cross-language contest.
Test and support evidence
| Lane | Current verified support | CI evidence | | --- | --- | --- | | JavaScript · Node | 45 shared-core cases; 41 tooling cases; packed npm install/import/CLI smoke | Node 22.12 and Node 24 jobs | | JavaScript · browser | The same 45 shared-core cases in real Chrome; independent 100% native V8 gates | Chrome coverage job and live browser page | | Rust · native | 6 library tests + 3 browser-build CLI tests + 1 doctest; LLVM region/line/function coverage; formatting; warning denial; package verification | Rust 1.85 job and Rust coverage report | | Rust · browser | Visible browser/site-delivery checks plus one aggregate check that executes the same 6 Rust unit tests as a zero-import WASM harness in real Chrome | Rust browser artifact, on-page result list, DevTools output, and live browser example |
JavaScript coverage stays split by Node and Chrome. Rust coverage reports LLVM source regions, executable lines, and functions from the native unit-test run; browser WebAssembly remains a separate compatibility gate.
The tables are a readable snapshot. The linked CI run, generated status, coverage reports, and browser pages remain the source of truth for a specific commit.
Multi-language releases
One repository release represents the complete supported language set.
- Major versions always align across packages.
- Minor versions generally align and cannot move behind another language's release line.
- Patch versions may be language-specific.
- A GitHub release uses a bare numeric tag and title and attaches the exact current npm
.tgzand Rust.crate. - A release is not published until every supported language artifact is available.
- Adding a language means adding its README, documentation lane, CI/package gate, and release artifact to this same contract.
Example: GitHub release 2.1.3 may contain JavaScript/npm 2.1.3 and Rust/crates.io 2.1.0 because their shared 2.1 line is aligned.
Development
npm test
npm run coverage
cargo fmt --check
cargo test
cargo run --bin cargo-vanilla-test -- --browser
cargo packageLanguage-specific contracts live in JAVASCRIPT.md and RUST.md. Shared release history lives in CHANGELOG.md.
License
MIT. See licence.
