vitest-auto-spy 4.4.0 → 4.5.1
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/angular.js +4 -1
- package/dist/bun-angular.js +4 -1
- package/dist/cli.js +1 -1
- package/dist/eslint-plugin.cjs +75 -0
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +2 -2
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/angular.js
CHANGED
|
@@ -2526,7 +2526,10 @@ function renderShallow(component, options = {}) {
|
|
|
2526
2526
|
imports: standalone ? [component, ...options.imports ?? []] : options.imports ?? [],
|
|
2527
2527
|
declarations: standalone ? [] : [component],
|
|
2528
2528
|
providers: options.providers ?? [],
|
|
2529
|
-
|
|
2529
|
+
// Only where it can do something. A standalone component carries its own dependency scope, so a
|
|
2530
|
+
// module-level schema never reaches its template — and `enableAngularDiagnostics({ deadSchemas })`
|
|
2531
|
+
// is right to report one, which made these two features of the same package cancel each other out.
|
|
2532
|
+
...standalone ? {} : { schemas: [NO_ERRORS_SCHEMA] }
|
|
2530
2533
|
});
|
|
2531
2534
|
TestBed.overrideComponent(component, { set: buildOverride(component, options) });
|
|
2532
2535
|
options.beforeCreate?.();
|
package/dist/bun-angular.js
CHANGED
|
@@ -314,7 +314,10 @@ function renderShallow(component, options = {}) {
|
|
|
314
314
|
imports: standalone ? [component, ...options.imports ?? []] : options.imports ?? [],
|
|
315
315
|
declarations: standalone ? [] : [component],
|
|
316
316
|
providers: options.providers ?? [],
|
|
317
|
-
|
|
317
|
+
// Only where it can do something. A standalone component carries its own dependency scope, so a
|
|
318
|
+
// module-level schema never reaches its template — and `enableAngularDiagnostics({ deadSchemas })`
|
|
319
|
+
// is right to report one, which made these two features of the same package cancel each other out.
|
|
320
|
+
...standalone ? {} : { schemas: [NO_ERRORS_SCHEMA] }
|
|
318
321
|
});
|
|
319
322
|
TestBed.overrideComponent(component, { set: buildOverride(component, options) });
|
|
320
323
|
options.beforeCreate?.();
|
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.5.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,55 @@ 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
|
+
var RENDER_MESSAGES = {
|
|
340
|
+
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.',
|
|
341
|
+
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."
|
|
342
|
+
};
|
|
343
|
+
function buildsDirectiveHarness(source) {
|
|
344
|
+
return source.includes("createDirectiveHost");
|
|
345
|
+
}
|
|
346
|
+
function readsRenderedTemplate(source) {
|
|
347
|
+
return TEMPLATE_READS.some((member) => source.includes(member));
|
|
348
|
+
}
|
|
349
|
+
function renderShallowSuggestion(context, node) {
|
|
350
|
+
const [component, ...extra] = node.arguments;
|
|
351
|
+
const state = bindingState(context.sourceCode.getScope(node), "renderShallow");
|
|
352
|
+
if (!component || extra.length > 0 || state === "taken") {
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
const replacement = `renderShallow(${context.sourceCode.getText(component)}).fixture`;
|
|
356
|
+
return {
|
|
357
|
+
desc: `Render without the children and the template: ${replacement}`,
|
|
358
|
+
fix: (fixer) => {
|
|
359
|
+
const edits = [fixer.replaceText(node, replacement)];
|
|
360
|
+
if (state === "free") {
|
|
361
|
+
edits.push(insertImport(fixer, `import { renderShallow } from '${PACKAGE}/angular';`));
|
|
362
|
+
}
|
|
363
|
+
return edits;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
319
368
|
// src/lib/eslint/floating-assertion.ts
|
|
320
369
|
var PROMISE_CALLBACK_METHODS = /* @__PURE__ */ new Set(["catch", "finally", "then"]);
|
|
321
370
|
function isPromiseCallback(fn) {
|
|
@@ -1609,6 +1658,30 @@ var noUnregisteredInjectSpy = defineRule({
|
|
|
1609
1658
|
};
|
|
1610
1659
|
}
|
|
1611
1660
|
});
|
|
1661
|
+
var preferRenderShallow = defineRule({
|
|
1662
|
+
anchor: "-a-components-children",
|
|
1663
|
+
description: "Render through renderShallow() when the spec never reads the rendered template",
|
|
1664
|
+
hasSuggestions: true,
|
|
1665
|
+
schema: [{ type: "object", properties: { templates: { enum: ["as-needed", "never"] } }, additionalProperties: false }],
|
|
1666
|
+
messages: RENDER_MESSAGES,
|
|
1667
|
+
create: (context) => ({
|
|
1668
|
+
'CallExpression[callee.object.name="TestBed"][callee.property.name="createComponent"]': (node) => {
|
|
1669
|
+
const source = context.sourceCode.getText();
|
|
1670
|
+
if (templatePolicy(context) === "as-needed" ? readsRenderedTemplate(source) : buildsDirectiveHarness(source)) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const suggestion = renderShallowSuggestion(context, node);
|
|
1674
|
+
context.report(
|
|
1675
|
+
suggestion ? { node, messageId: "preferRenderShallow", suggest: [suggestion] } : { node, messageId: "preferRenderShallow" }
|
|
1676
|
+
);
|
|
1677
|
+
},
|
|
1678
|
+
'Property[key.name="keepTemplate"][value.value=true]': (node) => {
|
|
1679
|
+
if (templatePolicy(context) === "never") {
|
|
1680
|
+
context.report({ node, messageId: "keepTemplate" });
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
})
|
|
1684
|
+
});
|
|
1612
1685
|
var rules = {
|
|
1613
1686
|
"prefer-provide-auto-spy": preferProvideAutoSpy,
|
|
1614
1687
|
"prefer-create-spy-from-class": preferCreateSpyFromClass,
|
|
@@ -1625,6 +1698,7 @@ var rules = {
|
|
|
1625
1698
|
"no-inject-before-override": noInjectBeforeOverride,
|
|
1626
1699
|
"no-import-time-spread": noImportTimeSpread,
|
|
1627
1700
|
"no-unregistered-inject-spy": noUnregisteredInjectSpy,
|
|
1701
|
+
"prefer-render-shallow": preferRenderShallow,
|
|
1628
1702
|
...jasmineRules
|
|
1629
1703
|
};
|
|
1630
1704
|
|
|
@@ -1646,6 +1720,7 @@ var recommendedRules = {
|
|
|
1646
1720
|
[`${PLUGIN_NAME}/no-inject-before-override`]: "error",
|
|
1647
1721
|
[`${PLUGIN_NAME}/no-import-time-spread`]: "error",
|
|
1648
1722
|
[`${PLUGIN_NAME}/no-unregistered-inject-spy`]: "error",
|
|
1723
|
+
[`${PLUGIN_NAME}/prefer-render-shallow`]: "error",
|
|
1649
1724
|
[`${PLUGIN_NAME}/jasmine-namespace-without-entry`]: "error",
|
|
1650
1725
|
[`${PLUGIN_NAME}/no-jasmine-globals`]: "error",
|
|
1651
1726
|
[`${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.1",
|
|
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",
|
|
@@ -78,7 +78,7 @@ describe('TaskService', () => {
|
|
|
78
78
|
|
|
79
79
|
One `configureTestingModule` per `describe` — reconfiguring per `it()` recompiles the module every
|
|
80
80
|
test. Use `mockReadonlyProp(component, 'selected', signal(true))` for the signals of the class under
|
|
81
|
-
test, `await stable(fixture)` before asserting zoneless state, `renderShallow` for components. For an
|
|
81
|
+
test, `await stable(fixture)` before asserting zoneless state, `renderShallow` for components — `prefer-render-shallow` reports the `TestBed.createComponent` in a file that reads no markup back, and the rewrite is a suggestion rather than a `--fix` because `renderShallow` configures the module itself. For an
|
|
82
82
|
`httpResource()`, `await expectRequest(url).flush(body)` from `vitest-auto-spy/angular-http` is the
|
|
83
83
|
whole dance — the request is issued by `flushEffects()`, not on creation, and the value is settled
|
|
84
84
|
before the promise resolves; by hand it is six steps and asserting early reads the resource's
|
|
@@ -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 —
|