/** * "Did the migration lose a test?" — the only check that answers it. * * Counters do not. Under `isolate: false` a file can lose a whole suite (an exported spec file * imported by its neighbour loses its own `describe`) and, in the same run, a flaky test elsewhere * can start passing. Totals match, the run looks identical, and a suite is silently gone. The * question is about **which** tests ran, so the answer has to be the symmetric difference of two * sets of names. * * This is the shape of every runner migration — Jest to Vitest, Karma to Jest, mocha to node:test. * Both runners write a JSON report in the same "Jest format" (`--reporter=json`), and that is all * this needs: no runner is imported here, and neither report has to come from Vitest. */ /** One test in a JSON report. Only the fields every runner writes are read. */ interface ReportedTest { fullName?: string; title?: string; status?: string; } /** One file in a JSON report. */ interface ReportedFile { name?: string; assertionResults?: ReportedTest[]; } /** A JSON report, as `--reporter=json` writes it. */ interface TestRunReport { testResults?: ReportedFile[]; } /** What one run contained, keyed by `file::full name`. */ interface TestRunSummary { /** `file::full test name` for every test the report mentions, whatever its status. */ names: Set; /** * How many times each of those names ran. * * Two `it('handles error')` in one file — the ordinary result of a copy-paste — are one name, so * a set alone answered "nothing was lost" for a migration that dropped one of them. The * multiplicity is the honest answer, and it sits beside `names` rather than replacing it so * nothing reading the set has to change. */ counts: Map; files: number; passed: number; failed: string[]; skipped: number; } /** How two runs differ. */ interface TestRunComparison { baseline: TestRunSummary; current: TestRunSummary; /** * In the baseline, gone now — the answer to "did I lose anything?". * * A name that ran fewer times than before, rather than not at all, is listed as * `name (×2 → ×1)`: one of two same-named tests disappearing is a loss like any other. */ missing: string[]; /** Not in the baseline — a rename shows up here *and* in `missing`. */ added: string[]; } /** * Read one JSON report into the set of names it ran. * * @param report The parsed report. * @param root A path fragment to cut everything before, so two runs from different checkouts (CI * and a laptop) compare as equal. */ declare function summarizeTestRun(report: TestRunReport, root?: string): TestRunSummary; /** * Compare two JSON reports by the set of test names. * * ```ts * const diff = compareTestRuns(JSON.parse(before), JSON.parse(after), '/my-repo/'); * * expect(diff.missing).toEqual([]); * ``` * * A renamed test appears in both `missing` and `added`, which is the honest answer: from the * outside a rename and a deletion-plus-addition are the same event, and only a person can tell them * apart. */ declare function compareTestRuns(baseline: TestRunReport, current: TestRunReport, root?: string): TestRunComparison; /** Render a {@link compareTestRuns} result for a terminal or a CI log. */ declare function formatTestRunComparison(comparison: TestRunComparison): string; /** * Describe how two arrays of records disagree, field by field — or `undefined` when they match. * * ```ts * const sent = analytics.send.mock.calls.map(([event]) => event); * * expect(diffByField(sent, expectedEvents)).toBeUndefined(); * // AssertionError: expected '9 of 9 elements differ. * // `event_timestamp` differs in all 9: actual 1 everywhere, expected 2, 3, 4, 5, 6, 7, …' to be undefined * ``` * * "Everywhere" against a run of values is the tell worth recognising: under fake timers every * `Date.now()` inside one test answers the same, so a test about *order* or *duration* needs * `useCountingClock()` rather than a frozen one. * * Equality is this library's own stable serialization — the same one `calledWith` matches arguments * with — so key order does not count as a difference, and `Date`, `Map`, `Set` and circular * references are all comparable. */ declare function diffByField(actual: readonly unknown[], expected: readonly unknown[]): string | undefined; /** * Explain what a double is configured to answer and what it was actually asked. * * With no `method`, every spied member that the double exposes is reported; with one, only that * member. The result is a report to print — `console.log(explainSpy(users))` — not something to * assert on. * * @example * ```ts * const users = createSpyFromClass(UserService); * users.load.calledWith(1).resolveWith('ok'); * * await users.load(2); * * console.log(explainSpy(users, 'load')); * // [vitest-auto-spy] explainSpy * // * // load — 1 call, 1 configured, none matched * // configured: * // #1 calledWith(1) * // calls: * // #1 load(2) -> no configured arguments matched; the default value was used * ``` * * @param spy A double, or a single function spy off one. * @param method Restrict the report to one member. * @returns A human-readable report; never throws, whatever it was handed. */ declare function explainSpy(spy: object, method?: string): string; export { type TestRunComparison, type TestRunReport, type TestRunSummary, compareTestRuns, diffByField, explainSpy, formatTestRunComparison, summarizeTestRun };