vitest-auto-spy 5.10.0 → 5.10.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 +11 -5
- package/dist/angular-router.js +19 -16
- package/dist/cli.js +1 -1
- package/dist/eslint-plugin.cjs +91 -19
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -2369,11 +2369,17 @@ Five things to know:
|
|
|
2369
2369
|
`provideRouter()` is what tests a navigation.
|
|
2370
2370
|
- **`emitNavigation()` takes what you have** — nothing, a URL string, or an event you built. A
|
|
2371
2371
|
`NavigationEnd` moves the URL with it; any other event does not.
|
|
2372
|
-
- **The navigation in flight follows the events
|
|
2373
|
-
puts one up and `setCurrentNavigation(null)`
|
|
2374
|
-
`emitNavigation()` starts one with that event's id,
|
|
2375
|
-
|
|
2376
|
-
|
|
2372
|
+
- **The navigation in flight follows the events, and a terminal event ends it _after_ delivering
|
|
2373
|
+
it.** `setCurrentNavigation({ extras: { state } })` puts one up and `setCurrentNavigation(null)`
|
|
2374
|
+
ends it; a `NavigationStart` pushed through `emitNavigation()` starts one with that event's id,
|
|
2375
|
+
URL and trigger, and a `NavigationEnd`, `NavigationCancel`, `NavigationError` or
|
|
2376
|
+
`NavigationSkipped` drops it back to `null` once its subscribers have run. Angular's doc comment
|
|
2377
|
+
— "the current navigation becomes to null after the NavigationEnd event is emitted" — means
|
|
2378
|
+
_after_ literally: the router emits the terminal event from a `tap` with the navigation still in
|
|
2379
|
+
flight and clears it in the `finalize` below, and `events` is a Subject, so a synchronous
|
|
2380
|
+
subscriber runs between the two. **A component that reads `currentNavigation()` while handling a
|
|
2381
|
+
`NavigationEnd` gets the navigation that just finished**, here and in production; it reads `null`
|
|
2382
|
+
only once `navigate()` has resolved. Probed against a real `provideRouter()` on Angular 22.
|
|
2377
2383
|
- **Ordering does not matter here.** `Router` is `providedIn: 'root'` and `provideRouter()` does not
|
|
2378
2384
|
re-provide the token, so the double wins in either order — and a `TestBed` without it hands out a
|
|
2379
2385
|
real router, which is what `injectRouterDouble()` says by name.
|
package/dist/angular-router.js
CHANGED
|
@@ -246,22 +246,22 @@ function createUrlState(initial) {
|
|
|
246
246
|
}
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
|
-
function
|
|
250
|
-
if (event instanceof NavigationStart) {
|
|
251
|
-
|
|
252
|
-
navigationOf({
|
|
253
|
-
id: event.id,
|
|
254
|
-
initialUrl: serializer.parse(event.url),
|
|
255
|
-
extractedUrl: serializer.parse(event.url),
|
|
256
|
-
trigger: event.navigationTrigger ?? "imperative"
|
|
257
|
-
})
|
|
258
|
-
);
|
|
259
|
-
return event.id;
|
|
260
|
-
}
|
|
261
|
-
if (event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError || event instanceof NavigationSkipped) {
|
|
262
|
-
inFlight.set(null);
|
|
249
|
+
function startEvent(event, inFlight, navigationOf) {
|
|
250
|
+
if (!(event instanceof NavigationStart)) {
|
|
251
|
+
return void 0;
|
|
263
252
|
}
|
|
264
|
-
|
|
253
|
+
inFlight.set(
|
|
254
|
+
navigationOf({
|
|
255
|
+
id: event.id,
|
|
256
|
+
initialUrl: serializer.parse(event.url),
|
|
257
|
+
extractedUrl: serializer.parse(event.url),
|
|
258
|
+
trigger: event.navigationTrigger ?? "imperative"
|
|
259
|
+
})
|
|
260
|
+
);
|
|
261
|
+
return event.id;
|
|
262
|
+
}
|
|
263
|
+
function endsNavigation(event) {
|
|
264
|
+
return event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError || event instanceof NavigationSkipped;
|
|
265
265
|
}
|
|
266
266
|
function buildNavigation(state, id, named) {
|
|
267
267
|
return {
|
|
@@ -334,8 +334,11 @@ function createRouterDouble(init = {}) {
|
|
|
334
334
|
if (next instanceof NavigationEnd) {
|
|
335
335
|
state.set(next.urlAfterRedirects);
|
|
336
336
|
}
|
|
337
|
-
navigationId =
|
|
337
|
+
navigationId = startEvent(next, inFlight, navigationOf) ?? navigationId;
|
|
338
338
|
events.next(next);
|
|
339
|
+
if (endsNavigation(next)) {
|
|
340
|
+
inFlight.set(null);
|
|
341
|
+
}
|
|
339
342
|
}
|
|
340
343
|
};
|
|
341
344
|
doubles2.set(router, double);
|
package/dist/cli.js
CHANGED
|
@@ -2453,7 +2453,7 @@ function checkForeignPragma(graph) {
|
|
|
2453
2453
|
}
|
|
2454
2454
|
|
|
2455
2455
|
// src/cli/checks/export-map.generated.ts
|
|
2456
|
-
var EXPORT_MAP_VERSION = "5.10.
|
|
2456
|
+
var EXPORT_MAP_VERSION = "5.10.1";
|
|
2457
2457
|
var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rstest 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/angular-router vitest-auto-spy/signal-forms 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";
|
|
2458
2458
|
var EXPORTED_BY = {
|
|
2459
2459
|
AccessorImplementations: "0 1 2 3 4 12 17 18 19",
|
package/dist/eslint-plugin.cjs
CHANGED
|
@@ -757,14 +757,22 @@ function factoryBody(context, value) {
|
|
|
757
757
|
}
|
|
758
758
|
var SPY_FACTORIES = /* @__PURE__ */ new Set([
|
|
759
759
|
"autoMocked",
|
|
760
|
+
"createActivatedRoute",
|
|
760
761
|
"createAutoMock",
|
|
762
|
+
"createDocumentDouble",
|
|
761
763
|
"createMock",
|
|
764
|
+
"createRouterDouble",
|
|
762
765
|
"createSpyClass",
|
|
763
766
|
"createSpyFromClass",
|
|
767
|
+
"createWindowDouble",
|
|
764
768
|
"mockConstructor",
|
|
765
769
|
"mockDeep",
|
|
770
|
+
"provideActivatedRoute",
|
|
766
771
|
"provideAutoSpy",
|
|
767
|
-
"provideAutoSpyForToken"
|
|
772
|
+
"provideAutoSpyForToken",
|
|
773
|
+
"provideDocumentDouble",
|
|
774
|
+
"provideRouterDouble",
|
|
775
|
+
"provideWindowDouble"
|
|
768
776
|
]);
|
|
769
777
|
function isFactoryCall(node) {
|
|
770
778
|
return isCallExpression(node) && isIdentifier(node.callee) && SPY_FACTORIES.has(node.callee.name);
|
|
@@ -2104,6 +2112,46 @@ function testCall(node) {
|
|
|
2104
2112
|
}
|
|
2105
2113
|
return { skipped: SKIPPED_TESTS.has(chain.root) || chain.members.some((member) => SKIPPED_MEMBERS.has(member)) };
|
|
2106
2114
|
}
|
|
2115
|
+
var SUBJECT_BUILDERS = /* @__PURE__ */ new Set(["get", "inject"]);
|
|
2116
|
+
function containsCall(node) {
|
|
2117
|
+
let current = node;
|
|
2118
|
+
while (isMemberExpression(current)) {
|
|
2119
|
+
if (isCallExpression(current.object)) {
|
|
2120
|
+
return true;
|
|
2121
|
+
}
|
|
2122
|
+
current = current.object;
|
|
2123
|
+
}
|
|
2124
|
+
return isCallExpression(current);
|
|
2125
|
+
}
|
|
2126
|
+
function namesSubject(subject, matcher) {
|
|
2127
|
+
if (isIdentifier(subject)) {
|
|
2128
|
+
return true;
|
|
2129
|
+
}
|
|
2130
|
+
if (isMemberExpression(subject)) {
|
|
2131
|
+
return !containsCall(subject);
|
|
2132
|
+
}
|
|
2133
|
+
if (!isCallExpression(subject)) {
|
|
2134
|
+
return false;
|
|
2135
|
+
}
|
|
2136
|
+
if (matcher === "toBeInstanceOf") {
|
|
2137
|
+
return false;
|
|
2138
|
+
}
|
|
2139
|
+
if (isIdentifier(subject.callee)) {
|
|
2140
|
+
return subject.arguments.length === 0;
|
|
2141
|
+
}
|
|
2142
|
+
const [token, ...rest] = subject.arguments;
|
|
2143
|
+
return isMemberExpression(subject.callee) && SUBJECT_BUILDERS.has(memberName(subject.callee) ?? "") && !containsCall(subject.callee.object) && rest.length === 0 && token !== void 0 && (isIdentifier(token) || isMemberExpression(token) && !containsCall(token));
|
|
2144
|
+
}
|
|
2145
|
+
function subjectReference(subject, context) {
|
|
2146
|
+
if (isIdentifier(subject) || isMemberExpression(subject)) {
|
|
2147
|
+
return context.sourceCode.getText(subject);
|
|
2148
|
+
}
|
|
2149
|
+
let current = subject;
|
|
2150
|
+
while (isCallExpression(current) || isMemberExpression(current)) {
|
|
2151
|
+
current = isCallExpression(current) ? current.callee : current.object;
|
|
2152
|
+
}
|
|
2153
|
+
return isIdentifier(current) ? current.name : void 0;
|
|
2154
|
+
}
|
|
2107
2155
|
function existenceSubject(expression) {
|
|
2108
2156
|
if (!isCallExpression(expression) || !isMemberExpression(expression.callee)) {
|
|
2109
2157
|
return void 0;
|
|
@@ -2119,7 +2167,10 @@ function existenceSubject(expression) {
|
|
|
2119
2167
|
return void 0;
|
|
2120
2168
|
}
|
|
2121
2169
|
const [subject] = target.arguments;
|
|
2122
|
-
|
|
2170
|
+
if (!subject || !(negated ? MISSING.has(matcher) : EXISTS.has(matcher))) {
|
|
2171
|
+
return void 0;
|
|
2172
|
+
}
|
|
2173
|
+
return namesSubject(subject, matcher) ? subject : void 0;
|
|
2123
2174
|
}
|
|
2124
2175
|
function existenceOnly(callback) {
|
|
2125
2176
|
const body = callback.body;
|
|
@@ -2139,6 +2190,14 @@ function removal2(context, statement) {
|
|
|
2139
2190
|
return fixer.replaceTextRange([start, statement.range[1]], "");
|
|
2140
2191
|
};
|
|
2141
2192
|
}
|
|
2193
|
+
function recordName(names, node, context) {
|
|
2194
|
+
const text = context.sourceCode.getText(node);
|
|
2195
|
+
for (let scope = enclosingFunction(node); scope; scope = enclosingFunction(scope.parent)) {
|
|
2196
|
+
const seen = names.get(scope) ?? /* @__PURE__ */ new Set();
|
|
2197
|
+
seen.add(text);
|
|
2198
|
+
names.set(scope, seen);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2142
2201
|
function countNested(blocks) {
|
|
2143
2202
|
blocks.forEach((block, scope) => {
|
|
2144
2203
|
let current = scope;
|
|
@@ -2146,6 +2205,7 @@ function countNested(blocks) {
|
|
|
2146
2205
|
const above = blocks.get(current);
|
|
2147
2206
|
if (above) {
|
|
2148
2207
|
above.below += block.proving;
|
|
2208
|
+
above.running.push(...block.running);
|
|
2149
2209
|
}
|
|
2150
2210
|
if (current === void 0) {
|
|
2151
2211
|
return;
|
|
@@ -2154,6 +2214,27 @@ function countNested(blocks) {
|
|
|
2154
2214
|
}
|
|
2155
2215
|
});
|
|
2156
2216
|
}
|
|
2217
|
+
function reportBlock(context, block, names) {
|
|
2218
|
+
if (block.below === 0) {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
const referenced = new Set(block.running.flatMap((callback) => [...names.get(callback) ?? []]));
|
|
2222
|
+
block.smoke.forEach(({ node, subject }) => {
|
|
2223
|
+
const reference = subjectReference(subject, context);
|
|
2224
|
+
if (reference !== void 0 && !referenced.has(reference)) {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
const data = {
|
|
2228
|
+
siblings: block.below === 1 ? "another test under the same setup already runs against it" : `${block.below} other tests under the same setup already run against it`,
|
|
2229
|
+
subject: context.sourceCode.getText(subject)
|
|
2230
|
+
};
|
|
2231
|
+
const statement = node.parent;
|
|
2232
|
+
const report = { data, messageId: "noRedundantSmokeTest", node };
|
|
2233
|
+
context.report(
|
|
2234
|
+
isExpressionStatement(statement) ? { ...report, suggest: [{ desc: "Remove this test", fix: removal2(context, statement) }] } : report
|
|
2235
|
+
);
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2157
2238
|
var noRedundantSmokeTest = defineRule({
|
|
2158
2239
|
anchor: "-a-promise-a-test-forgets-to-await",
|
|
2159
2240
|
description: "Delete a test whose whole body asserts that the subject exists, where the block already has tests that use it",
|
|
@@ -2163,41 +2244,32 @@ var noRedundantSmokeTest = defineRule({
|
|
|
2163
2244
|
},
|
|
2164
2245
|
create: (context) => {
|
|
2165
2246
|
const blocks = /* @__PURE__ */ new Map();
|
|
2247
|
+
const names = /* @__PURE__ */ new Map();
|
|
2166
2248
|
return {
|
|
2249
|
+
Identifier: (node) => recordName(names, node, context),
|
|
2250
|
+
MemberExpression: (node) => recordName(names, node, context),
|
|
2167
2251
|
CallExpression: (node) => {
|
|
2168
2252
|
const test = testCall(node);
|
|
2169
2253
|
if (!test) {
|
|
2170
2254
|
return;
|
|
2171
2255
|
}
|
|
2172
2256
|
const scope = enclosingFunction(node);
|
|
2173
|
-
const block = blocks.get(scope) ?? { below: 0, proving: 0, smoke: [] };
|
|
2257
|
+
const block = blocks.get(scope) ?? { below: 0, proving: 0, running: [], smoke: [] };
|
|
2174
2258
|
const callback = node.arguments.find(isFunctionNode);
|
|
2175
2259
|
const subject = callback && existenceOnly(callback);
|
|
2176
2260
|
if (subject) {
|
|
2177
2261
|
block.smoke.push({ node, subject });
|
|
2178
2262
|
} else if (!test.skipped) {
|
|
2179
2263
|
block.proving += 1;
|
|
2264
|
+
if (callback) {
|
|
2265
|
+
block.running.push(callback);
|
|
2266
|
+
}
|
|
2180
2267
|
}
|
|
2181
2268
|
blocks.set(scope, block);
|
|
2182
2269
|
},
|
|
2183
2270
|
"Program:exit": () => {
|
|
2184
2271
|
countNested(blocks);
|
|
2185
|
-
blocks.forEach((block) =>
|
|
2186
|
-
if (block.below === 0) {
|
|
2187
|
-
return;
|
|
2188
|
-
}
|
|
2189
|
-
block.smoke.forEach(({ node, subject }) => {
|
|
2190
|
-
const data = {
|
|
2191
|
-
siblings: block.below === 1 ? "another test under the same setup already runs against it" : `${block.below} other tests under the same setup already run against it`,
|
|
2192
|
-
subject: context.sourceCode.getText(subject)
|
|
2193
|
-
};
|
|
2194
|
-
const statement = node.parent;
|
|
2195
|
-
const report = { data, messageId: "noRedundantSmokeTest", node };
|
|
2196
|
-
context.report(
|
|
2197
|
-
isExpressionStatement(statement) ? { ...report, suggest: [{ desc: "Remove this test", fix: removal2(context, statement) }] } : report
|
|
2198
|
-
);
|
|
2199
|
-
});
|
|
2200
|
-
});
|
|
2272
|
+
blocks.forEach((block) => reportBlock(context, block, names));
|
|
2201
2273
|
}
|
|
2202
2274
|
};
|
|
2203
2275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-auto-spy",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.1",
|
|
4
4
|
"description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun, node:test and Rstest, 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",
|