vitest-auto-spy 3.9.0 → 3.11.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 +2 -1
- package/README.md +26 -9
- package/dist/eslint-plugin.cjs +98 -2
- package/dist/eslint-plugin.js +98 -2
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +1 -1
package/AGENTS.md
CHANGED
|
@@ -1886,8 +1886,9 @@ export default [{ files: ['**/*.spec.ts'], ...autoSpy.configs.recommended }];
|
|
|
1886
1886
|
| `no-overridden-provider` | `error` | suggest | two providers for one token in one array → the earlier one never runs; the exact duplicate can be deleted |
|
|
1887
1887
|
| `no-inject-before-override` | `warn` | — | `TestBed.inject()` in a hook, in a suite that still calls `override*` |
|
|
1888
1888
|
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
|
|
1889
|
+
| `no-unregistered-inject-spy` | `warn` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
|
|
1889
1890
|
|
|
1890
|
-
|
|
1891
|
+
Fourteen rules; two fix on their own, six offer suggestions. `no-mocked-for-spy` only ever touches a
|
|
1891
1892
|
**type position**, where a wrong rewrite is a compile error rather than a test that quietly changed
|
|
1892
1893
|
meaning — so `--fix` renames the type, adds `import type { Spy } from 'vitest-auto-spy'` and drops
|
|
1893
1894
|
the orphaned `Mocked` import. Every type position, not only a `let`: a factory's return type, a
|
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
60
60
|
- 🧩 Module mocks that prove they applied — `assertMocked`, `moduleNamespace`, for a `vi.mock()` a bundler quietly ignored
|
|
61
61
|
- 🧾 Fixtures without casts — deep-partial `createMock`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`
|
|
62
62
|
- 🚚 A migration you can verify — `compareTestRuns` on the two JSON reports, `diffByField` for the assertion the reporter collapses
|
|
63
|
-
- 📏 Lint rules and one-line test-run hygiene —
|
|
63
|
+
- 📏 Lint rules and one-line test-run hygiene — fourteen rules in `vitest-auto-spy/eslint-plugin` (two `--fix`, six suggestions), `setupAutoSpy()`
|
|
64
64
|
- 🩺 [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
|
|
65
65
|
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-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
|
|
66
66
|
- 🚚 [`npx vitest-auto-spy codemod`](#codemod--migrating-a-suite-off-jest-auto-spies) — seven transforms that move a suite off `jest-auto-spies` and Jest, dry-run by default, with a `--verify` pass that also checks a file somebody edited by hand
|
|
@@ -2207,12 +2207,13 @@ export can never be.
|
|
|
2207
2207
|
| `no-overridden-provider` | `error` | suggest | two providers for one token in one array → the earlier one never runs; the exact duplicate can be deleted |
|
|
2208
2208
|
| `no-inject-before-override` | `warn` | — | `TestBed.inject()` in a hook, in a suite that still calls `override*` |
|
|
2209
2209
|
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
|
|
2210
|
+
| `no-unregistered-inject-spy` | `warn` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
|
|
2210
2211
|
|
|
2211
2212
|
Every message ends with a link to the matching [recipe](#how-to-mock): a rule that only says
|
|
2212
2213
|
"don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
|
|
2213
2214
|
are versioned together and stop being re-written in every project that installs the package.
|
|
2214
2215
|
|
|
2215
|
-
**Two of the
|
|
2216
|
+
**Two of the fourteen fix on their own, six offer suggestions**, and the split is not about how hard
|
|
2216
2217
|
the rewrite is. `no-mocked-for-spy` touches a _declaration_: get it wrong and the file stops
|
|
2217
2218
|
compiling, which is the loudest, cheapest failure there is — so `--fix` rewrites the type, adds
|
|
2218
2219
|
`import type { Spy } from 'vitest-auto-spy'` and drops the `Mocked` import once nothing else uses
|
|
@@ -2270,6 +2271,20 @@ seven sites in an 8 673-file workspace, which is small enough to flag at the cur
|
|
|
2270
2271
|
and an instance field are deliberately not reported — they run later than the module does — while a
|
|
2271
2272
|
`static` field is.
|
|
2272
2273
|
|
|
2274
|
+
The fourteenth, `no-unregistered-inject-spy`, catches `injectSpy(X)` for a token nothing in the file
|
|
2275
|
+
registered as an auto-spy. What comes back is whatever Angular DI already had — the real service, or
|
|
2276
|
+
an object an imported testing module put there — and nothing complains, because `injectSpy` is
|
|
2277
|
+
declared to return a `Spy<T>`: the helpers are present for `tsc` and absent at run time, so the first
|
|
2278
|
+
`.mockReturnValue(…)` or `.calledWith(…)` throws on a real method. The library warns about exactly
|
|
2279
|
+
this at run time, and that warning is why the rule exists rather than why it is redundant — it does
|
|
2280
|
+
not fail the run, it scrolls past in a suite of a thousand files, and it arrives only for the tests
|
|
2281
|
+
that executed the line; in one consumer monorepo dozens of spec files print it on every CI run and it
|
|
2282
|
+
has never been acted on. Reading it at the cursor needs no types, only the file's own registrations,
|
|
2283
|
+
and the rule stays quiet unless it can read all of them: it needs at least one `provideAutoSpy`, and
|
|
2284
|
+
a spread, an unknown provider factory, `createWithAutoSpies`, `renderShallow` or
|
|
2285
|
+
`TestBed.overrideProvider` silences the file. A token handed a plain `useValue` is left to
|
|
2286
|
+
`prefer-provide-auto-spy` — two rules on one line only teach people to disable both.
|
|
2287
|
+
|
|
2273
2288
|
## Editor diagnostics — WebStorm & VS Code
|
|
2274
2289
|
|
|
2275
2290
|
The rules above are worth more while the cursor is still on the line than they are in CI, because
|
|
@@ -2279,7 +2294,7 @@ package's own — it needs its ESLint integration switched on.
|
|
|
2279
2294
|
### WebStorm and the other JetBrains IDEs
|
|
2280
2295
|
|
|
2281
2296
|
No plugin to install: WebStorm, IntelliJ IDEA Ultimate, PhpStorm, PyCharm Professional and RubyMine
|
|
2282
|
-
all run ESLint natively, so the
|
|
2297
|
+
all run ESLint natively, so the fourteen rules appear inline, in the **Problems** tool window, and
|
|
2283
2298
|
under **Code → Inspect Code** for the whole project.
|
|
2284
2299
|
|
|
2285
2300
|
```js
|
|
@@ -2296,7 +2311,7 @@ has supported flat config since 2023.3); scope the block to spec files yourself;
|
|
|
2296
2311
|
the fixes and suggestions live.
|
|
2297
2312
|
|
|
2298
2313
|
A native JetBrains plugin is **not** planned — it would duplicate an integration the IDE already has
|
|
2299
|
-
and then keep a second copy of
|
|
2314
|
+
and then keep a second copy of fourteen rules, in Kotlin, in step with the TypeScript ones.
|
|
2300
2315
|
|
|
2301
2316
|
### VS Code, Cursor, Windsurf, VSCodium
|
|
2302
2317
|
|
|
@@ -2462,11 +2477,13 @@ Releases are automated: merging a PR into `master` bumps the version from the
|
|
|
2462
2477
|
Conventional Commit types and publishes to npm — see
|
|
2463
2478
|
[CONTRIBUTING.md → Releasing](./CONTRIBUTING.md#releasing).
|
|
2464
2479
|
|
|
2465
|
-
`auto-release.yml` is the **only**
|
|
2466
|
-
with npm **Trusted Publishing (OIDC)** — no npm token
|
|
2467
|
-
A hand-pushed `v*` tag runs `release.yml`, which now only creates the
|
|
2468
|
-
publish. The `vitest-auto-spies` alias
|
|
2469
|
-
|
|
2480
|
+
`auto-release.yml` is the **only** entry point that publishes either package, and it authenticates
|
|
2481
|
+
with npm **Trusted Publishing (OIDC)** — no npm token anywhere in the repository, and provenance
|
|
2482
|
+
attached by the registry. A hand-pushed `v*` tag runs `release.yml`, which now only creates the
|
|
2483
|
+
GitHub Release; it does not publish. The `vitest-auto-spies` alias is a second npm package with its
|
|
2484
|
+
own publish (`publish-alias.yml`), but it is `workflow_call`-only and reached through
|
|
2485
|
+
`auto-release.yml`, because npm validates the workflow that *entered* the run, not the reusable one
|
|
2486
|
+
that runs `npm publish`.
|
|
2470
2487
|
|
|
2471
2488
|
If this package saved you time, a ⭐ on [GitHub](https://github.com/ASDAlexey/vitest-auto-spy)
|
|
2472
2489
|
helps others find it.
|
package/dist/eslint-plugin.cjs
CHANGED
|
@@ -708,6 +708,77 @@ function breaksAnOverride(injection) {
|
|
|
708
708
|
return countInSubtree(suite, (node) => isMemberCall(node, TEST_BED, OVERRIDES) && !runsFirst(node, injection, hook), true) > 0;
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
+
// src/lib/eslint/unregistered-spy.ts
|
|
712
|
+
var PROVIDE_AUTO_SPY = "provideAutoSpy";
|
|
713
|
+
var INJECT_SPY = "injectSpy";
|
|
714
|
+
var AUTO_SPY_FACTORIES = /* @__PURE__ */ new Set(["createAutoMock", "createSpyFromClass", "createMock", "mockDeep"]);
|
|
715
|
+
var OPAQUE_HELPERS = /* @__PURE__ */ new Set(["createWithAutoSpies", "renderShallow"]);
|
|
716
|
+
function emptyRegistrations() {
|
|
717
|
+
return { autoSpies: /* @__PURE__ */ new Set(), opaqueTokens: /* @__PURE__ */ new Set(), injections: [], sawProvideAutoSpy: false, opaque: false };
|
|
718
|
+
}
|
|
719
|
+
function calleeName(node) {
|
|
720
|
+
return isIdentifier(node.callee) ? node.callee.name : void 0;
|
|
721
|
+
}
|
|
722
|
+
function isOverrideProvider(node) {
|
|
723
|
+
const { callee } = node;
|
|
724
|
+
return isMemberExpression(callee) && isIdentifier(callee.object) && callee.object.name === "TestBed" && isIdentifier(callee.property) && callee.property.name === "overrideProvider";
|
|
725
|
+
}
|
|
726
|
+
function readCall(context, node, tally) {
|
|
727
|
+
const name = calleeName(node);
|
|
728
|
+
const [first] = node.arguments;
|
|
729
|
+
if (isOverrideProvider(node) || name !== void 0 && OPAQUE_HELPERS.has(name)) {
|
|
730
|
+
tally.opaque = true;
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (first === void 0) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (name === PROVIDE_AUTO_SPY) {
|
|
737
|
+
tally.sawProvideAutoSpy = true;
|
|
738
|
+
tally.autoSpies.add(context.sourceCode.getText(first));
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (name === INJECT_SPY) {
|
|
742
|
+
tally.injections.push({ node, token: context.sourceCode.getText(first) });
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function isAutoSpyValue(value) {
|
|
746
|
+
return isCallExpression(value) && isIdentifier(value.callee) && AUTO_SPY_FACTORIES.has(value.callee.name);
|
|
747
|
+
}
|
|
748
|
+
function readProviderObject(context, element, tally) {
|
|
749
|
+
const provide = findProperty(element, "provide");
|
|
750
|
+
if (provide === void 0) {
|
|
751
|
+
tally.opaque = true;
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const token = context.sourceCode.getText(propertyValue(provide));
|
|
755
|
+
const useValue = findProperty(element, "useValue");
|
|
756
|
+
if (useValue !== void 0 && isAutoSpyValue(propertyValue(useValue))) {
|
|
757
|
+
tally.autoSpies.add(token);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
tally.opaqueTokens.add(token);
|
|
761
|
+
}
|
|
762
|
+
function readProviders(context, array, tally) {
|
|
763
|
+
for (const element of array.elements) {
|
|
764
|
+
if (element === null) {
|
|
765
|
+
tally.opaque = true;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (isObjectExpression(element)) {
|
|
769
|
+
readProviderObject(context, element, tally);
|
|
770
|
+
} else if (!isCallExpression(element) || calleeName(element) !== PROVIDE_AUTO_SPY) {
|
|
771
|
+
tally.opaque = true;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
function unregisteredInjections(tally) {
|
|
776
|
+
if (tally.opaque || !tally.sawProvideAutoSpy) {
|
|
777
|
+
return [];
|
|
778
|
+
}
|
|
779
|
+
return tally.injections.filter(({ token }) => !tally.autoSpies.has(token) && !tally.opaqueTokens.has(token));
|
|
780
|
+
}
|
|
781
|
+
|
|
711
782
|
// src/lib/eslint/rules.ts
|
|
712
783
|
var README = "https://github.com/ASDAlexey/vitest-auto-spy#how-to-mock";
|
|
713
784
|
function defineRule(options) {
|
|
@@ -1116,6 +1187,29 @@ var noImportTimeSpread = defineRule({
|
|
|
1116
1187
|
}
|
|
1117
1188
|
})
|
|
1118
1189
|
});
|
|
1190
|
+
var noUnregisteredInjectSpy = defineRule({
|
|
1191
|
+
anchor: "-a-service-behind-angular-di",
|
|
1192
|
+
description: "Do not read a token with injectSpy unless this file registered it as an auto-spy",
|
|
1193
|
+
messages: {
|
|
1194
|
+
noUnregisteredInjectSpy: "Nothing in this file registers `{{token}}` as an auto-spy, so this hands back whatever Angular DI already had \u2014 the real service, or a hand-rolled object some imported module provides. The line still compiles and the spec still runs: `injectSpy` types the result as a spy, so the helpers are there for the compiler and absent at run time, and the first `.mockReturnValue(\u2026)` or `.calledWith(\u2026)` throws on a real method. Add `provideAutoSpy({{token}})` to the providers, or read the real implementation with `TestBed.inject({{token}})` and say so. Nothing is reported from a file whose providers this cannot read in full \u2014 a spread, an unknown provider factory, `createWithAutoSpies` or `TestBed.overrideProvider` all silence it."
|
|
1195
|
+
},
|
|
1196
|
+
create: (context) => {
|
|
1197
|
+
const tally = emptyRegistrations();
|
|
1198
|
+
return {
|
|
1199
|
+
CallExpression: (node) => {
|
|
1200
|
+
readCall(context, node, tally);
|
|
1201
|
+
},
|
|
1202
|
+
'Property[key.name="providers"] > ArrayExpression': (node) => {
|
|
1203
|
+
readProviders(context, node, tally);
|
|
1204
|
+
},
|
|
1205
|
+
"Program:exit": () => {
|
|
1206
|
+
unregisteredInjections(tally).forEach(({ node, token }) => {
|
|
1207
|
+
context.report({ node, messageId: "noUnregisteredInjectSpy", data: { token } });
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1119
1213
|
var rules = {
|
|
1120
1214
|
"prefer-provide-auto-spy": preferProvideAutoSpy,
|
|
1121
1215
|
"prefer-create-spy-from-class": preferCreateSpyFromClass,
|
|
@@ -1129,7 +1223,8 @@ var rules = {
|
|
|
1129
1223
|
"no-floating-assertion": noFloatingAssertion,
|
|
1130
1224
|
"no-overridden-provider": noOverriddenProvider,
|
|
1131
1225
|
"no-inject-before-override": noInjectBeforeOverride,
|
|
1132
|
-
"no-import-time-spread": noImportTimeSpread
|
|
1226
|
+
"no-import-time-spread": noImportTimeSpread,
|
|
1227
|
+
"no-unregistered-inject-spy": noUnregisteredInjectSpy
|
|
1133
1228
|
};
|
|
1134
1229
|
|
|
1135
1230
|
// src/eslint-plugin.ts
|
|
@@ -1147,7 +1242,8 @@ var recommendedRules = {
|
|
|
1147
1242
|
[`${PLUGIN_NAME}/no-floating-assertion`]: "error",
|
|
1148
1243
|
[`${PLUGIN_NAME}/no-overridden-provider`]: "error",
|
|
1149
1244
|
[`${PLUGIN_NAME}/no-inject-before-override`]: "warn",
|
|
1150
|
-
[`${PLUGIN_NAME}/no-import-time-spread`]: "error"
|
|
1245
|
+
[`${PLUGIN_NAME}/no-import-time-spread`]: "error",
|
|
1246
|
+
[`${PLUGIN_NAME}/no-unregistered-inject-spy`]: "warn"
|
|
1151
1247
|
};
|
|
1152
1248
|
var plugin = {
|
|
1153
1249
|
rules,
|
package/dist/eslint-plugin.js
CHANGED
|
@@ -706,6 +706,77 @@ function breaksAnOverride(injection) {
|
|
|
706
706
|
return countInSubtree(suite, (node) => isMemberCall(node, TEST_BED, OVERRIDES) && !runsFirst(node, injection, hook), true) > 0;
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
+
// src/lib/eslint/unregistered-spy.ts
|
|
710
|
+
var PROVIDE_AUTO_SPY = "provideAutoSpy";
|
|
711
|
+
var INJECT_SPY = "injectSpy";
|
|
712
|
+
var AUTO_SPY_FACTORIES = /* @__PURE__ */ new Set(["createAutoMock", "createSpyFromClass", "createMock", "mockDeep"]);
|
|
713
|
+
var OPAQUE_HELPERS = /* @__PURE__ */ new Set(["createWithAutoSpies", "renderShallow"]);
|
|
714
|
+
function emptyRegistrations() {
|
|
715
|
+
return { autoSpies: /* @__PURE__ */ new Set(), opaqueTokens: /* @__PURE__ */ new Set(), injections: [], sawProvideAutoSpy: false, opaque: false };
|
|
716
|
+
}
|
|
717
|
+
function calleeName(node) {
|
|
718
|
+
return isIdentifier(node.callee) ? node.callee.name : void 0;
|
|
719
|
+
}
|
|
720
|
+
function isOverrideProvider(node) {
|
|
721
|
+
const { callee } = node;
|
|
722
|
+
return isMemberExpression(callee) && isIdentifier(callee.object) && callee.object.name === "TestBed" && isIdentifier(callee.property) && callee.property.name === "overrideProvider";
|
|
723
|
+
}
|
|
724
|
+
function readCall(context, node, tally) {
|
|
725
|
+
const name = calleeName(node);
|
|
726
|
+
const [first] = node.arguments;
|
|
727
|
+
if (isOverrideProvider(node) || name !== void 0 && OPAQUE_HELPERS.has(name)) {
|
|
728
|
+
tally.opaque = true;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (first === void 0) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (name === PROVIDE_AUTO_SPY) {
|
|
735
|
+
tally.sawProvideAutoSpy = true;
|
|
736
|
+
tally.autoSpies.add(context.sourceCode.getText(first));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (name === INJECT_SPY) {
|
|
740
|
+
tally.injections.push({ node, token: context.sourceCode.getText(first) });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function isAutoSpyValue(value) {
|
|
744
|
+
return isCallExpression(value) && isIdentifier(value.callee) && AUTO_SPY_FACTORIES.has(value.callee.name);
|
|
745
|
+
}
|
|
746
|
+
function readProviderObject(context, element, tally) {
|
|
747
|
+
const provide = findProperty(element, "provide");
|
|
748
|
+
if (provide === void 0) {
|
|
749
|
+
tally.opaque = true;
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const token = context.sourceCode.getText(propertyValue(provide));
|
|
753
|
+
const useValue = findProperty(element, "useValue");
|
|
754
|
+
if (useValue !== void 0 && isAutoSpyValue(propertyValue(useValue))) {
|
|
755
|
+
tally.autoSpies.add(token);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
tally.opaqueTokens.add(token);
|
|
759
|
+
}
|
|
760
|
+
function readProviders(context, array, tally) {
|
|
761
|
+
for (const element of array.elements) {
|
|
762
|
+
if (element === null) {
|
|
763
|
+
tally.opaque = true;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (isObjectExpression(element)) {
|
|
767
|
+
readProviderObject(context, element, tally);
|
|
768
|
+
} else if (!isCallExpression(element) || calleeName(element) !== PROVIDE_AUTO_SPY) {
|
|
769
|
+
tally.opaque = true;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function unregisteredInjections(tally) {
|
|
774
|
+
if (tally.opaque || !tally.sawProvideAutoSpy) {
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
return tally.injections.filter(({ token }) => !tally.autoSpies.has(token) && !tally.opaqueTokens.has(token));
|
|
778
|
+
}
|
|
779
|
+
|
|
709
780
|
// src/lib/eslint/rules.ts
|
|
710
781
|
var README = "https://github.com/ASDAlexey/vitest-auto-spy#how-to-mock";
|
|
711
782
|
function defineRule(options) {
|
|
@@ -1114,6 +1185,29 @@ var noImportTimeSpread = defineRule({
|
|
|
1114
1185
|
}
|
|
1115
1186
|
})
|
|
1116
1187
|
});
|
|
1188
|
+
var noUnregisteredInjectSpy = defineRule({
|
|
1189
|
+
anchor: "-a-service-behind-angular-di",
|
|
1190
|
+
description: "Do not read a token with injectSpy unless this file registered it as an auto-spy",
|
|
1191
|
+
messages: {
|
|
1192
|
+
noUnregisteredInjectSpy: "Nothing in this file registers `{{token}}` as an auto-spy, so this hands back whatever Angular DI already had \u2014 the real service, or a hand-rolled object some imported module provides. The line still compiles and the spec still runs: `injectSpy` types the result as a spy, so the helpers are there for the compiler and absent at run time, and the first `.mockReturnValue(\u2026)` or `.calledWith(\u2026)` throws on a real method. Add `provideAutoSpy({{token}})` to the providers, or read the real implementation with `TestBed.inject({{token}})` and say so. Nothing is reported from a file whose providers this cannot read in full \u2014 a spread, an unknown provider factory, `createWithAutoSpies` or `TestBed.overrideProvider` all silence it."
|
|
1193
|
+
},
|
|
1194
|
+
create: (context) => {
|
|
1195
|
+
const tally = emptyRegistrations();
|
|
1196
|
+
return {
|
|
1197
|
+
CallExpression: (node) => {
|
|
1198
|
+
readCall(context, node, tally);
|
|
1199
|
+
},
|
|
1200
|
+
'Property[key.name="providers"] > ArrayExpression': (node) => {
|
|
1201
|
+
readProviders(context, node, tally);
|
|
1202
|
+
},
|
|
1203
|
+
"Program:exit": () => {
|
|
1204
|
+
unregisteredInjections(tally).forEach(({ node, token }) => {
|
|
1205
|
+
context.report({ node, messageId: "noUnregisteredInjectSpy", data: { token } });
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1117
1211
|
var rules = {
|
|
1118
1212
|
"prefer-provide-auto-spy": preferProvideAutoSpy,
|
|
1119
1213
|
"prefer-create-spy-from-class": preferCreateSpyFromClass,
|
|
@@ -1127,7 +1221,8 @@ var rules = {
|
|
|
1127
1221
|
"no-floating-assertion": noFloatingAssertion,
|
|
1128
1222
|
"no-overridden-provider": noOverriddenProvider,
|
|
1129
1223
|
"no-inject-before-override": noInjectBeforeOverride,
|
|
1130
|
-
"no-import-time-spread": noImportTimeSpread
|
|
1224
|
+
"no-import-time-spread": noImportTimeSpread,
|
|
1225
|
+
"no-unregistered-inject-spy": noUnregisteredInjectSpy
|
|
1131
1226
|
};
|
|
1132
1227
|
|
|
1133
1228
|
// src/eslint-plugin.ts
|
|
@@ -1145,7 +1240,8 @@ var recommendedRules = {
|
|
|
1145
1240
|
[`${PLUGIN_NAME}/no-floating-assertion`]: "error",
|
|
1146
1241
|
[`${PLUGIN_NAME}/no-overridden-provider`]: "error",
|
|
1147
1242
|
[`${PLUGIN_NAME}/no-inject-before-override`]: "warn",
|
|
1148
|
-
[`${PLUGIN_NAME}/no-import-time-spread`]: "error"
|
|
1243
|
+
[`${PLUGIN_NAME}/no-import-time-spread`]: "error",
|
|
1244
|
+
[`${PLUGIN_NAME}/no-unregistered-inject-spy`]: "warn"
|
|
1149
1245
|
};
|
|
1150
1246
|
var plugin = {
|
|
1151
1247
|
rules,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-auto-spy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.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.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auto-mock",
|
|
@@ -238,7 +238,7 @@ npx vitest-auto-spy codemod --verify # after a migration: anything the transfor
|
|
|
238
238
|
Most of this library's guarantees are type-level, so a green run that does not type-check is not
|
|
239
239
|
done. Report failures with their output rather than describing them as passing.
|
|
240
240
|
|
|
241
|
-
**After any `eslint --fix` over specs, run `npx tsc --noEmit`.** The
|
|
241
|
+
**After any `eslint --fix` over specs, run `npx tsc --noEmit`.** The fourteen rules in
|
|
242
242
|
`vitest-auto-spy/eslint-plugin` are lint, not typecheck: `no-mocked-for-spy` rewrites a declaration
|
|
243
243
|
to `Spy<T>` and cannot see what the name is assigned two lines below, so a clean lint pass is not
|
|
244
244
|
evidence that the types still hold. Where it cannot prove the rename it downgrades to a suggestion —
|