I got tired of test frameworks that require config files, plugins, and 50MB of dependencies just to run assert.equal. So I built QUnitX.
The pitch is simple: one test file, three runtimes, no changes.
import { module, test } from 'qunitx';
module('Math', () => {
test('addition', (assert) => {
assert.equal(2 + 2, 4);
});
});
node --test math-test.js
deno test math-test.js
qunitx math-test.js # browser, headless
Why QUnit?
I know the reaction: "QUnit? That jQuery thing from 2008?" — yes, exactly. That's the point. It's been solving real edge cases for 16 years that Jest/Vitest are still catching up to. assert.deepEqual correctly handles circular references, typed arrays, Maps, Sets, prototype chains. assert.step / assert.verifySteps catches missing async callbacks that other frameworks silently swallow. assert.expect(n) fails if the wrong number of assertions ran — invaluable when async code paths are involved.
What QUnitX actually does:
- Wraps Node's built-in node:test runner with the QUnit lifecycle (no Jest, no Vitest, nothing extra)
- Wraps Deno's native BDD runner the same way
- Browser path is a thin re-export of QUnit itself — full browser UI with filterable, shareable test URLs
TypeScript works out of the box (node --import=tsx/esm --test). Coverage via npx c8. Watch mode via --watch. Zero runtime dependencies.
The browser demo is what I'm most proud of — the QUnit UI lets you filter to any test and share the URL, so your colleague sees the exact same filtered view. We've been using this in production for years and it never gets old.
izelnakri•1h ago