vitest-auto-spy 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -0
- package/README.md +5 -4
- package/dist/cli.js +1 -1
- package/dist/eslint-plugin.cjs +70 -0
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +1 -1
package/AGENTS.md
CHANGED
|
@@ -2328,6 +2328,7 @@ export default [
|
|
|
2328
2328
|
| `no-inject-before-override` | `error` | — | `TestBed.inject()` in a hook, in a suite that still calls `override*` |
|
|
2329
2329
|
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
|
|
2330
2330
|
| `no-unregistered-inject-spy` | `error` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
|
|
2331
|
+
| `prefer-render-shallow` | `error` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
|
|
2331
2332
|
| `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compat layer nowhere — option: `{ setupModules: […] }` |
|
|
2332
2333
|
| `no-jasmine-globals` | `error` | — | `jasmine.*`, bare `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` |
|
|
2333
2334
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
76
76
|
- 🧩 Module mocks that prove they applied — `assertMocked`, `moduleNamespace`, for a `vi.mock()` a bundler quietly ignored
|
|
77
77
|
- 🧾 Fixtures without casts — deep-partial `createMock`, `createFixture` / `createFixtureFactory`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`
|
|
78
78
|
- 🚚 A migration you can verify — `vitest-auto-spy/diagnostics`: `compareTestRuns` on the two JSON reports, `summarizeTestRun` / `formatTestRunComparison` to read the answer, `diffByField` for the assertion the reporter collapses, `explainSpy` for a double that answered something you did not configure
|
|
79
|
-
- 📏 Lint rules and one-line test-run hygiene —
|
|
79
|
+
- 📏 Lint rules and one-line test-run hygiene — twenty rules in `vitest-auto-spy/eslint-plugin` (three `--fix`, eight suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()`
|
|
80
80
|
- 🩺 [Editor diagnostics](#editor-diagnostics--webstorm--vs-code) — the same anti-patterns underlined while you type: native ESLint inspections in **WebStorm** and the other JetBrains IDEs, the ESLint extension in **VS Code**, no extra plugin either way
|
|
81
81
|
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-perf-codemod-and-init) — suite-level defects **that never fail a run**: a `tsconfig` `include` matching no file, a production module importing a spec, a `@jest-environment` pragma the runner never reads, config left behind for a runner that is gone. Read-only, no config, exits 1 in CI
|
|
82
82
|
- ⏱️ [`npx vitest-auto-spy perf`](#perf--where-the-cpu-time-actually-goes) — where a suite's CPU time actually goes, phase by phase, and which spec files to act on: the ones that reach no DOM and could run under `node`, the ones that import a barrel. Runs Vitest once with a reporter this package ships, reads `TestModule.diagnostic()`, names files, states the rule behind each finding
|
|
@@ -2961,6 +2961,7 @@ export can never be.
|
|
|
2961
2961
|
| `no-inject-before-override` | `error` | — | `TestBed.inject()` in a hook, in a suite that still calls `override*` |
|
|
2962
2962
|
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
|
|
2963
2963
|
| `no-unregistered-inject-spy` | `error` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
|
|
2964
|
+
| `prefer-render-shallow` | `error` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
|
|
2964
2965
|
| `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compatibility layer nowhere |
|
|
2965
2966
|
| `no-jasmine-globals` | `error` | — | `jasmine.*`, `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` — none of them exist under Vitest |
|
|
2966
2967
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()`, which is a no-op here → take the copy at call time |
|
|
@@ -2970,7 +2971,7 @@ Every message ends with a link to the matching [recipe](#how-to-mock): a rule th
|
|
|
2970
2971
|
"don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
|
|
2971
2972
|
are versioned together and stop being re-written in every project that installs the package.
|
|
2972
2973
|
|
|
2973
|
-
**Three of the
|
|
2974
|
+
**Three of the twenty fix on their own, eight offer suggestions**, and the split is not about how hard
|
|
2974
2975
|
the rewrite is. `no-mocked-for-spy` touches a _declaration_: get it wrong and the file stops
|
|
2975
2976
|
compiling, which is the loudest, cheapest failure there is — so `--fix` rewrites the type, adds
|
|
2976
2977
|
`import type { Spy } from 'vitest-auto-spy'` and drops the `Mocked` import once nothing else uses
|
|
@@ -3074,7 +3075,7 @@ package's own — it needs its ESLint integration switched on.
|
|
|
3074
3075
|
### WebStorm and the other JetBrains IDEs
|
|
3075
3076
|
|
|
3076
3077
|
No plugin to install: WebStorm, IntelliJ IDEA Ultimate, PhpStorm, PyCharm Professional and RubyMine
|
|
3077
|
-
all run ESLint natively, so the
|
|
3078
|
+
all run ESLint natively, so the twenty rules appear inline, in the **Problems** tool window, and
|
|
3078
3079
|
under **Code → Inspect Code** for the whole project.
|
|
3079
3080
|
|
|
3080
3081
|
```js
|
|
@@ -3091,7 +3092,7 @@ has supported flat config since 2023.3); scope the block to spec files yourself;
|
|
|
3091
3092
|
the fixes and suggestions live.
|
|
3092
3093
|
|
|
3093
3094
|
A native JetBrains plugin is **not** planned — it would duplicate an integration the IDE already has
|
|
3094
|
-
and then keep a second copy of
|
|
3095
|
+
and then keep a second copy of twenty rules, in Kotlin, in step with the TypeScript ones.
|
|
3095
3096
|
|
|
3096
3097
|
### VS Code, Cursor, Windsurf, VSCodium
|
|
3097
3098
|
|
package/dist/cli.js
CHANGED
|
@@ -2356,7 +2356,7 @@ function checkForeignPragma(graph) {
|
|
|
2356
2356
|
}
|
|
2357
2357
|
|
|
2358
2358
|
// src/cli/checks/export-map.generated.ts
|
|
2359
|
-
var EXPORT_MAP_VERSION = "4.
|
|
2359
|
+
var EXPORT_MAP_VERSION = "4.4.0";
|
|
2360
2360
|
var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rxjs vitest-auto-spy/console vitest-auto-spy/dom-stubs vitest-auto-spy/diagnostics vitest-auto-spy/jasmine vitest-auto-spy/jasmine-compat vitest-auto-spy/observer-spy vitest-auto-spy/angular vitest-auto-spy/angular-http vitest-auto-spy/nestjs vitest-auto-spy/react vitest-auto-spy/vue vitest-auto-spy/svelte vitest-auto-spy/setup vitest-auto-spy/zone vitest-auto-spy/eslint-plugin";
|
|
2361
2361
|
var EXPORTED_BY = {
|
|
2362
2362
|
AccessorImplementations: "0 1 2 3 11 14 15 16",
|
package/dist/eslint-plugin.cjs
CHANGED
|
@@ -316,6 +316,48 @@ function defineRule(options) {
|
|
|
316
316
|
};
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
// src/lib/eslint/dom-reads.ts
|
|
320
|
+
var TEMPLATE_READS = [
|
|
321
|
+
"nativeElement",
|
|
322
|
+
"debugElement",
|
|
323
|
+
"elementRef",
|
|
324
|
+
"querySelector",
|
|
325
|
+
"getComputedStyle",
|
|
326
|
+
"triggerEventHandler",
|
|
327
|
+
"innerHTML",
|
|
328
|
+
"innerText",
|
|
329
|
+
"textContent",
|
|
330
|
+
"getAttribute",
|
|
331
|
+
"classList",
|
|
332
|
+
"shadowRoot",
|
|
333
|
+
"By.css",
|
|
334
|
+
"By.directive"
|
|
335
|
+
];
|
|
336
|
+
function templatePolicy(context) {
|
|
337
|
+
return Reflect.get(Object(context.options[0]), "templates") === "never" ? "never" : "as-needed";
|
|
338
|
+
}
|
|
339
|
+
function readsRenderedTemplate(source) {
|
|
340
|
+
return TEMPLATE_READS.some((member) => source.includes(member));
|
|
341
|
+
}
|
|
342
|
+
function renderShallowSuggestion(context, node) {
|
|
343
|
+
const [component, ...extra] = node.arguments;
|
|
344
|
+
const state = bindingState(context.sourceCode.getScope(node), "renderShallow");
|
|
345
|
+
if (!component || extra.length > 0 || state === "taken") {
|
|
346
|
+
return void 0;
|
|
347
|
+
}
|
|
348
|
+
const replacement = `renderShallow(${context.sourceCode.getText(component)}).fixture`;
|
|
349
|
+
return {
|
|
350
|
+
desc: `Render without the children and the template: ${replacement}`,
|
|
351
|
+
fix: (fixer) => {
|
|
352
|
+
const edits = [fixer.replaceText(node, replacement)];
|
|
353
|
+
if (state === "free") {
|
|
354
|
+
edits.push(insertImport(fixer, `import { renderShallow } from '${PACKAGE}/angular';`));
|
|
355
|
+
}
|
|
356
|
+
return edits;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
319
361
|
// src/lib/eslint/floating-assertion.ts
|
|
320
362
|
var PROMISE_CALLBACK_METHODS = /* @__PURE__ */ new Set(["catch", "finally", "then"]);
|
|
321
363
|
function isPromiseCallback(fn) {
|
|
@@ -1609,6 +1651,32 @@ var noUnregisteredInjectSpy = defineRule({
|
|
|
1609
1651
|
};
|
|
1610
1652
|
}
|
|
1611
1653
|
});
|
|
1654
|
+
var preferRenderShallow = defineRule({
|
|
1655
|
+
anchor: "-a-components-children",
|
|
1656
|
+
description: "Render through renderShallow() when the spec never reads the rendered template",
|
|
1657
|
+
hasSuggestions: true,
|
|
1658
|
+
schema: [{ type: "object", properties: { templates: { enum: ["as-needed", "never"] } }, additionalProperties: false }],
|
|
1659
|
+
messages: {
|
|
1660
|
+
keepTemplate: '`keepTemplate: true` puts the real template back, and this project set `{ templates: "never" }`. Drop it \u2014 or, when the component genuinely reads its own template through `viewChild` or content projection, silence this line, because the alternative is a spec that cannot reach the component at all.',
|
|
1661
|
+
preferRenderShallow: "`TestBed.createComponent` pays for compiling the template and instantiating the whole child subtree, and nothing in this file reads either \u2014 no `nativeElement`, no `debugElement`, no `By.css`, no `querySelector`. `renderShallow(X)` brings the same component up through the same `TestBed` with the children dropped and the template blank, and leaves inputs, signals, lifecycle hooks and DI exactly where they were; `fixture` is still a real `ComponentFixture`. What it buys is measured in `bench-angular/`: **0.24\xD7** the per-test cycle at 100 children and **0.05\xD7** at 400 \u2014 but at **zero** children the two are level (measurements straddle 1.0, and it is the noisiest row in the benchmark), so a leaf component gains nothing and this report is worth ignoring there. Reach for `{ keepTemplate: true }` when the component reads its own template through `viewChild` or content projection, which this rule cannot see from the spec."
|
|
1662
|
+
},
|
|
1663
|
+
create: (context) => ({
|
|
1664
|
+
'CallExpression[callee.object.name="TestBed"][callee.property.name="createComponent"]': (node) => {
|
|
1665
|
+
if (templatePolicy(context) === "as-needed" && readsRenderedTemplate(context.sourceCode.getText())) {
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
const suggestion = renderShallowSuggestion(context, node);
|
|
1669
|
+
context.report(
|
|
1670
|
+
suggestion ? { node, messageId: "preferRenderShallow", suggest: [suggestion] } : { node, messageId: "preferRenderShallow" }
|
|
1671
|
+
);
|
|
1672
|
+
},
|
|
1673
|
+
'Property[key.name="keepTemplate"][value.value=true]': (node) => {
|
|
1674
|
+
if (templatePolicy(context) === "never") {
|
|
1675
|
+
context.report({ node, messageId: "keepTemplate" });
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
})
|
|
1679
|
+
});
|
|
1612
1680
|
var rules = {
|
|
1613
1681
|
"prefer-provide-auto-spy": preferProvideAutoSpy,
|
|
1614
1682
|
"prefer-create-spy-from-class": preferCreateSpyFromClass,
|
|
@@ -1625,6 +1693,7 @@ var rules = {
|
|
|
1625
1693
|
"no-inject-before-override": noInjectBeforeOverride,
|
|
1626
1694
|
"no-import-time-spread": noImportTimeSpread,
|
|
1627
1695
|
"no-unregistered-inject-spy": noUnregisteredInjectSpy,
|
|
1696
|
+
"prefer-render-shallow": preferRenderShallow,
|
|
1628
1697
|
...jasmineRules
|
|
1629
1698
|
};
|
|
1630
1699
|
|
|
@@ -1646,6 +1715,7 @@ var recommendedRules = {
|
|
|
1646
1715
|
[`${PLUGIN_NAME}/no-inject-before-override`]: "error",
|
|
1647
1716
|
[`${PLUGIN_NAME}/no-import-time-spread`]: "error",
|
|
1648
1717
|
[`${PLUGIN_NAME}/no-unregistered-inject-spy`]: "error",
|
|
1718
|
+
[`${PLUGIN_NAME}/prefer-render-shallow`]: "error",
|
|
1649
1719
|
[`${PLUGIN_NAME}/jasmine-namespace-without-entry`]: "error",
|
|
1650
1720
|
[`${PLUGIN_NAME}/no-jasmine-globals`]: "error",
|
|
1651
1721
|
[`${PLUGIN_NAME}/no-save-arguments-by-value`]: "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-auto-spy",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun and node:test, with Angular/NestJS/React/Vue/Svelte recipes. Drop-in replacement for jest-auto-spies and jasmine-auto-spies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auto-mock",
|
|
@@ -323,7 +323,7 @@ npx vitest-auto-spy codemod --verify # after a migration: anything the transfor
|
|
|
323
323
|
Most of this library's guarantees are type-level, so a green run that does not type-check is not
|
|
324
324
|
done. Report failures with their output rather than describing them as passing.
|
|
325
325
|
|
|
326
|
-
**After any `eslint --fix` over specs, run `npx tsc --noEmit`.** The
|
|
326
|
+
**After any `eslint --fix` over specs, run `npx tsc --noEmit`.** The twenty rules in
|
|
327
327
|
`vitest-auto-spy/eslint-plugin` are lint, not typecheck: `no-mocked-for-spy` rewrites a declaration
|
|
328
328
|
to `Spy<T>` and cannot see what the name is assigned two lines below, so a clean lint pass is not
|
|
329
329
|
evidence that the types still hold. Where it cannot prove the rename it downgrades to a suggestion —
|