From qa-unit-tests-js
Configures and runs AVA - concurrent-by-default JS/TS test framework with isolated test files (each file runs in its own Node process), no globals (explicit `import test from 'ava'`), async-first API, snapshot support, and TypeScript via `@ava/typescript`. Use when the user wants minimal-API parallel-by-default tests, or works with libraries (vs apps) where per-file isolation prevents test interference.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-unit-tests-js:ava-testsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [github.com/avajs/ava][ava-gh]:
Per github.com/avajs/ava:
AVA's distinguishing properties:
import test from 'ava' - explicit, easier to
type, no dependency-injection hacks.done
callback dance.t.is/t.deepEqual etc.
produce diff-rich failure output.t.snapshot() built-in.The per-file process isolation eliminates entire test-interference classes that Jest/Mocha allow.
For React component testing, prefer vitest-tests
or jest-tests (ecosystem density).
Per ava-gh:
npm init ava
# or manually:
npm install --save-dev ava
// test/sum.test.js
import test from 'ava';
import { sum } from '../src/sum.js';
test('adds 1 + 2 to equal 3', t => {
t.is(sum(1, 2), 3);
});
test('async addition', async t => {
const result = await Promise.resolve(sum(2, 3));
t.is(result, 5);
});
t is the AVA-injected test context; assertion methods live on it
(t.is, t.deepEqual, t.truthy, t.snapshot, t.throws, etc.).
No global expect / assert - explicit per test.
Config lives in package.json ava key OR ava.config.js:
// package.json
{
"ava": {
"files": ["test/**/*.test.js"],
"extensions": ["js", "ts"],
"require": ["ts-node/register"],
"timeout": "30s",
"concurrency": 4,
"failFast": false,
"verbose": true,
"snapshotDir": "test/snapshots"
}
}
For TypeScript with the @ava/typescript adapter:
npm install --save-dev @ava/typescript typescript
{
"ava": {
"typescript": {
"rewritePaths": { "src/": "build/" },
"compile": "tsc"
}
}
}
test.serial(...) to force serial execution within a file:test.serial('this runs first', t => { /* ... */ });
test.serial('this runs second', t => { /* ... */ });
test('this runs concurrently with the next', t => { /* ... */ });
test('this runs concurrently with the previous', t => { /* ... */ });
For tests that share file-level state (rare, often a smell), serial is necessary.
test.before(async t => {
// runs once before all tests in file
});
test.after(async t => {
// runs once after all tests
});
test.beforeEach(async t => {
// runs before each test
});
test.afterEach(async t => {
// runs after each test
});
// Hooks can pass context to tests via t.context:
test.beforeEach(t => {
t.context.user = createUser();
});
test('uses fixture', t => {
t.is(t.context.user.id, 1);
});
Common matchers (full list at github.com/avajs/ava/blob/main/docs/03-assertions.md):
| Matcher | Use |
|---|---|
t.is(actual, expected) | Strict equality (===) |
t.not(actual, expected) | Strict inequality |
t.deepEqual(a, b) | Deep value equality |
t.notDeepEqual(a, b) | Inverse |
t.truthy(value) / t.falsy(value) | Boolean coercion |
t.true(value) / t.false(value) | Strict boolean |
t.throws(() => fn()) | Sync throw |
t.throwsAsync(async () => fn()) | Async throw |
t.regex(string, regex) | Regex match |
t.snapshot(value) | Snapshot test |
t.like(actual, partial) | Partial structural match |
test('snapshot user profile', t => {
const profile = renderProfile(user);
t.snapshot(profile);
});
Snapshots stored under test/snapshots/ per config (Step 3).
Update via npx ava --update-snapshots (or -u).
test.only('runs only this', t => { /* ... */ });
test.skip('skipped', t => { /* ... */ });
test.failing('expected to fail (TODO marker)', t => {
t.is(1, 2); // PASSES the test (because it's marked failing)
});
test.todo('write this test'); // marker; doesn't run
test.failing is AVA-distinctive - encodes "this test is expected
to fail" so the suite signals when the underlying bug is fixed.
- run: npm ci
- run: npx ava --tap | npx tap-junit > junit.xml
# Or built-in TAP reporter for any TAP consumer:
- run: npx ava --reporter=tap > test-results.tap
AVA emits TAP by default; for JUnit XML, pipe through tap-junit or
similar.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Use serial tests by default | Defeats AVA's parallelism advantage | Default concurrent; serial only when needed (Step 4) |
| Share state across test files | File-level process isolation breaks the assumption | Per-file fixture setup (Step 5) |
Use t.true(myArr.includes(x)) instead of t.true(myArr.includes(x)) with assertion-specific matcher | Generic boolean assertion gives poor failure message | Use specific matchers (Step 6) |
Commit test.only | Suite runs only the .only tests | Lint rule ava/no-only-test |
Use done callback pattern | AVA doesn't have one; tests hang | Use async/await or return a promise |
@ava/babel + jsdom; less
ergonomic than Jest/Vitest.jest-tests,
vitest-tests,
mocha-tests,
jasmine-tests - sister toolstest-code-conventions - cross-plugin: test code hygienenpx claudepluginhub testland/qa --plugin qa-unit-tests-jsProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.