web-testing-vitest-a84cad — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-testing-vitest-a84cad (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Vitest runs tests through the same transform pipeline and config the app is built with, so aliases, plugins and TypeScript work without a second toolchain.describe/it/expectfor structure,vi.fn()andvi.spyOn()for test doubles,vi.mock()for whole modules, andvi.useFakeTimers()for anything clock-driven. Vitest 4 is current stable (Vite 6+, Node 20+) and changed several mock defaults —vi.restoreAllMocks()no longer resets automocks, andpoolOptionsis gone.
Detailed Resources:
timers, assertions and snapshots
and mocking at the transport boundary
environment: "node", no setup file; followenvironment: "jsdom" (or happy-dom) and asetup file; follow examples/integration.md.
projects entry each, so asingle run covers all of them; see reference.md.
<critical_requirements>
Pass test options as the second argument — test("name", { timeout: 10_000 }, () => {}). The trailing-options form was removed after v2, and mixing an options object with a trailing timeout is rejected outright.
Return every export the file under test imports from a `vi.mock()` factory. The factory replaces the whole module, so anything it omits is undefined at import time; reach for importOriginal() to keep the rest, and remember the call is hoisted above every import in the file.
Reset mock state between tests — restoreMocks: true in config, or an explicit vi.resetAllMocks() in afterEach. In v4 vi.restoreAllMocks() touches only manual spies, so automocked modules keep their state without it.
Mock at the boundary the code crosses. A module mock binds the test to the import graph and breaks on any refactor that moves a function; intercepting HTTP leaves the import graph free and exercises serialisation.
</critical_requirements>
Auto-detection: Vitest, vitest.config, vi.fn, vi.mock, vi.spyOn, vi.hoisted, vi.mockObject, vi.useFakeTimers, vi.advanceTimersByTime, mockResolvedValue, mockRejectedValue, importOriginal, toMatchInlineSnapshot, expect.schemaMatching, defineConfig test block, projects, coverage provider
Applies to:
projects, coverageHandled elsewhere:
inside it is settled by whatever owns component testing
lifecycle they start and reset, not how they are written
<philosophy>
Vitest reads the project's own Vite config, so a test resolves imports, aliases and plugins exactly as the application does. That is the whole reason it needs so little configuration of its own — and the reason a failure is usually a config question ("which environment is this suite running in?") rather than a runner question.
</philosophy>
<decision_framework>
Which kind of test double:
vi.spyOn(obj, "method"), whichkeeps the original for restore.
vi.fn() with mockReturnValue / mockResolvedValue; no modulemachinery needed.
test covers serialisation and survives refactors.
vi.mock() forthe module, vi.useFakeTimers() for the clock, vi.stubGlobal() for a global.
</decision_framework>
<patterns>
Options are the second argument. test.each covers table-driven cases without a loop, and it.concurrent runs siblings in parallel inside one file.
describe("formatCurrency", () => {
it("formats the default currency", () => {
expect(formatCurrency(1234.56)).toBe("$1,234.56");
});
it("retries a known-flaky path", { retry: 2, timeout: 10_000 }, async () => {
await expect(fetchRate()).resolves.toBeGreaterThan(0);
});
it.each([
[0, "$0.00"],
[-1, "-$1.00"],
])("formats %d as %s", (input, expected) => {
expect(formatCurrency(input)).toBe(expected);
});
});Full code: examples/core.md
vi.fn() creates a standalone double; vi.spyOn() wraps an existing method and can be restored. Both record calls on .mock.
const onSave = vi.fn().mockResolvedValue({ id: "1" });
await submit({ onSave });
expect(onSave).toHaveBeenCalledWith({ title: "Draft" });
const spy = vi.spyOn(clock, "now").mockReturnValue(0);
stampEvent("saved");
expect(spy).toHaveBeenCalledTimes(1);
spy.mockRestore();Full code: examples/core.md
vi.mock() is hoisted above the imports, so its factory sees nothing from the file body unless the value comes from vi.hoisted(). importOriginal keeps the exports the test does not replace.
const { readConfig } = vi.hoisted(() => ({ readConfig: vi.fn() }));
vi.mock("./config", async (importOriginal) => ({
...(await importOriginal<typeof import("./config")>()),
readConfig,
}));
readConfig.mockReturnValue({ locale: "en-US" });Full code: examples/core.md
Install fake timers, advance them explicitly, and return to real timers afterwards so later suites are unaffected.
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
it("fires the debounced callback once", () => {
const onIdle = vi.fn();
watchIdle(onIdle, 300);
vi.advanceTimersByTime(300);
expect(onIdle).toHaveBeenCalledOnce();
});Full code: examples/core.md
toStrictEqual compares shape as well as value; resolves / rejects keep async assertions in one expression; inline snapshots keep the expected value beside the test.
await expect(loadUser("missing")).rejects.toThrowError(/not found/);
expect(toSummary(order)).toStrictEqual({ total: 118, items: 2 });
expect(parseArgs(["--json"]).json).toMatchInlineSnapshot(`true`);Full code: examples/core.md
The test block lives in the project's own config file. environment decides what globals a suite gets, setupFiles runs before each test file, and projects gives one run several of each.
import { defineConfig } from "vitest/config"; // not "vite" — that one has no `test` key
export default defineConfig({
test: {
environment: "node",
setupFiles: ["./tests/setup.ts"],
restoreMocks: true,
coverage: { provider: "v8", include: ["src/**/*.ts"] },
},
});Full code: examples/integration.md — lookup table in reference.md
</patterns>
<red_flags>
Breaks at runtime:
test("x", fn, { retry: 2 }) — removed after v2; put themin the second argument.
vi.mock() factory referencing a const from the file body — the call is hoisted, so thebinding is in its temporal dead zone; take the value from vi.hoisted().
await importOriginal() first.
default key — the factory's return shape is the module'sshape, so default: vi.fn() is required.
poolOptions in a v4 config — removed; maxWorkers and isolate are top-level.coverage.all in a v4 config — removed; coverage.include is now required for anything to bereported.
Surprising behaviour:
vi.restoreAllMocks() in v4 restores manual spies only; automocked modules needvi.resetAllMocks().
vi.fn().mock.invocationCallOrder starts at 1 in v4, where it started at 0 before.vi.fn().getMockName() answers "vi.fn()" rather than "spy", which changes snapshots thatcaptured it.
undefined instead of calling the original.useFakeTimers() with useRealTimers().
override answers the next one's request.
</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.