vitest-auto-spy 5.40.0 → 5.41.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/CHANGELOG.md CHANGED
@@ -10,6 +10,55 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.41.0] - 2026-09-27
14
+
15
+ ### Changed
16
+
17
+ - **Every Angular-adapter message prints class names as the source spells them.** 5.40.1 took the
18
+ esbuild `_` prefix and the Rollup `$1` suffix off in `toHaveDirectiveApplied` only; the rest still
19
+ told the reader to edit `_ReportCardComponent`. The same cleanup now applies to `injectSpy`,
20
+ `overrideComponentProvider`, `assertComponentDefIntact`, `enableAngularDiagnostics`' shadowed-provider
21
+ report, `setInputs` / `renderShallow` input errors, `renderShallow({ keepTemplate: true })`,
22
+ `createComponentStub` (and the stub's own class name), `provideMatDialogRef` / `injectMatDialogRef`,
23
+ `hostElement` / `queryElement`, `trackInjections(...).get()` and the `createSpyFromClass(X)` label.
24
+ `trackInjections(...).names()` still returns `Function.name` unchanged.
25
+ - **`toHaveDirectiveApplied` blames the selector when the host already has the directive in scope.**
26
+ A `createDirectiveHost({ template, scope: [FlagDirective] })` whose template no longer matched the
27
+ directive's selector failed with "Build the host with createDirectiveHost({ template, scope:
28
+ [FlagDirective] })", the call the spec already made. When the host's compiled scope carries the
29
+ directive, the failure now says its selector matches nothing the template renders and prints it
30
+ (`'[appFlagX]'`; a `:not()` selector is left out). With a selector argument, a directive found only
31
+ on other elements is reported as `it is on 1 element that selector does not match`.
32
+ `/angular/matchers` is +421 B min+gzip (2241 → 2662 B, +18.8 %) and +2.4 kB to import
33
+ (19972 → 22349 B): the scope lookup, the selector printer and the two messages.
34
+
35
+ ### Fixed
36
+
37
+ - **The `signalStore()` `fillMissing` default recognises the ngrx base under a bundler's rename.** The
38
+ base was matched by `name === 'SignalStore'`, which a `_SignalStore` or `SignalStore$1` missed; the
39
+ name is now compared with the rename taken off. The `HttpTestingController` lookup behind
40
+ `enableAngularDiagnostics({ pendingRequests })` compares the same way.
41
+
42
+ ## [5.40.1] - 2026-09-27
43
+
44
+ ### Changed
45
+
46
+ - **`toHaveDirectiveApplied` prints class names as the source spells them.** Under
47
+ `@angular/build:unit-test` esbuild emits a decorated class as `_TestIdDirective`, and the failure told
48
+ the reader to edit `_ReportRenameComponent`, a name no file contains. The leading `_` (and a Rollup
49
+ `$1` suffix) now comes off, the way strict-mode messages already took it off a spied class.
50
+
51
+ ### Fixed
52
+
53
+ - **`toHaveDirectiveApplied` finds a structural directive.** `*dir` and `<ng-template dir>` put the
54
+ directive on the comment Angular leaves in place of the template, and the search read elements only,
55
+ so a directive that rendered its view failed the matcher, whose hint then advised the
56
+ `createDirectiveHost({ template, scope })` the spec already used. The search now takes template
57
+ anchors in; `.not` reports them as `1 template anchor`, and a selector naming the rendered element
58
+ fails with a message that says to drop it. `/angular/matchers` is +226 B min+gzip (2015 → 2241 B,
59
+ +11.2 %) and +1.0 kB to import (18929 → 19972 B): the anchor search, its two messages and the
60
+ class-name cleanup.
61
+
13
62
  ## [5.40.0] - 2026-09-27
14
63
 
15
64
  ### Added
@@ -5541,7 +5590,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
5541
5590
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
5542
5591
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
5543
5592
 
5544
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.40.0...HEAD
5593
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.41.0...HEAD
5594
+ [5.41.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.40.1...v5.41.0
5595
+ [5.40.1]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.40.0...v5.40.1
5545
5596
  [5.40.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.39.0...v5.40.0
5546
5597
  [5.39.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.38.0...v5.39.0
5547
5598
  [5.38.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.37.0...v5.38.0
@@ -1207,7 +1207,11 @@ assertion on the attribute alone stays green when the entry is dropped and the a
1207
1207
  written statically, or set by something else; the fixture's root element is searched, so the entry
1208
1208
  itself can be asserted with no selector. On a fixture of the component under test the failure names
1209
1209
  that component's `hostDirectives` / `imports` as the fix; only a spec-built host (`createDirectiveHost`,
1210
- `TestBed.createDirective`) is pointed at `createDirectiveHost`:
1210
+ `TestBed.createDirective`) is pointed at `createDirectiveHost`. A directive already in the host's
1211
+ compiled scope gets neither hint: the failure prints its own selector, which matches nothing in the
1212
+ template (a mutated or mistyped selector). A structural directive (`*dir`,
1213
+ `<ng-template dir>`) counts too: it sits on the template's comment anchor, which the search includes;
1214
+ assert it without a selector, since the element it renders does not carry it:
1211
1215
 
1212
1216
  ```ts
1213
1217
  import { registerDirectiveMatchers } from 'vitest-auto-spy/angular/matchers';
@@ -39,7 +39,8 @@ the reference suite). Method discovery walks the _prototype chain_; a callable a
39
39
 
40
40
  - an Angular `signal()` / `computed()` field — the dominant case in a signals codebase
41
41
  - an arrow-function property — `readonly reload = (): void => {}`
42
- - anything on an ngrx `signalStore()`, which puts **everything** on the instance
42
+ - anything on an ngrx `signalStore()`, which puts **everything** on the instance — covered by the
43
+ `fillMissing` default below, not by a list
43
44
  - **members Angular's own classes moved onto the instance** — `Router.currentNavigation` in
44
45
  Angular 20 is `currentNavigation = this.navigationTransitions.currentNavigation.asReadonly()`
45
46
 
@@ -1,14 +1,14 @@
1
- import { pendingRequestsReport, assertAngularInternals, instrumentTestBed, onTestingModuleConfigured, onComponentCreated, beforeTestBedReset, verifyOnTeardown } from './chunk-GKSVYPZQ.js';
2
- export { disableTestBedDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, reportSpecTiming } from './chunk-GKSVYPZQ.js';
3
- import { failOnUnspiedProvider } from './chunk-3UUM6YM5.js';
1
+ import { pendingRequestsReport, assertAngularInternals, instrumentTestBed, onTestingModuleConfigured, onComponentCreated, beforeTestBedReset, verifyOnTeardown } from './chunk-JFOJFCVQ.js';
2
+ export { disableTestBedDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, reportSpecTiming } from './chunk-JFOJFCVQ.js';
3
+ import { failOnUnspiedProvider } from './chunk-OWDUWUAO.js';
4
4
  import './chunk-GG7BQKEK.js';
5
- import './chunk-RCUQDITG.js';
6
- import './chunk-Z7RCF47R.js';
5
+ import './chunk-MNY4ZHEQ.js';
6
+ import './chunk-T7EGA4G4.js';
7
7
  import './chunk-FOL5RH22.js';
8
8
  import './chunk-PC3FAZT3.js';
9
- import './chunk-RYFF55PV.js';
9
+ import './chunk-HUS2MHK5.js';
10
10
  import { isAutoSpyLike } from './chunk-Q7L4AWI7.js';
11
- import { count, taskName } from './chunk-M3NTXLZG.js';
11
+ import { count, sourceClassName, taskName } from './chunk-DYAS4OK4.js';
12
12
  import { angularPendingRequests, angularShadowedProviders, angularDeadSchemas, angularNgModuleScopesAuto } from './chunk-5FQZGJLJ.js';
13
13
  import { withDocs } from './chunk-ENCEEH23.js';
14
14
  import { getTestBed } from '@angular/core/testing';
@@ -31,7 +31,7 @@ function componentInjector(fixture, component) {
31
31
  }
32
32
  function describeResolved(resolved) {
33
33
  const constructorName = readProperty(readProperty(resolved, "constructor"), "name");
34
- return typeof constructorName === "string" && constructorName.length > 0 ? `a ${constructorName} instance` : String(resolved);
34
+ return typeof constructorName === "string" && constructorName.length > 0 ? `a ${sourceClassName(constructorName)} instance` : String(resolved);
35
35
  }
36
36
  function readProperty(value, key) {
37
37
  if (value === null || typeof value !== "object" && typeof value !== "function") {
@@ -59,7 +59,7 @@ function isDeadNgModuleImport(module) {
59
59
  }
60
60
  function moduleName(module) {
61
61
  const name = readProperty(module, "name");
62
- return typeof name === "string" ? name : String(module);
62
+ return typeof name === "string" ? sourceClassName(name) : String(module);
63
63
  }
64
64
  function listed(names) {
65
65
  return names.length === 1 ? String(names[0]) : `${names.slice(0, -1).join(", ")} and ${String(names.at(-1))}`;
@@ -89,7 +89,7 @@ function isComponentClass(value) {
89
89
  }
90
90
  function className(value) {
91
91
  const name = readProperty(value, "name");
92
- return typeof name === "string" ? name : String(value);
92
+ return typeof name === "string" ? sourceClassName(name) : String(value);
93
93
  }
94
94
  function checkNgModuleScopes(config) {
95
95
  failDeadNgModuleImports(readList(config, "imports").filter(isDeadNgModuleImport));
@@ -135,7 +135,7 @@ function flattenProviders(value, into) {
135
135
  function findControllerToken(providers) {
136
136
  const flat = [];
137
137
  flattenProviders(providers, flat);
138
- return flat.map((provider) => readProperty(provider, "provide")).find((token) => typeof token === "function" && token.name === HTTP_TESTING_CONTROLLER);
138
+ return flat.map((provider) => readProperty(provider, "provide")).find((token) => typeof token === "function" && sourceClassName(token.name) === HTTP_TESTING_CONTROLLER);
139
139
  }
140
140
  var controllerTokens = /* @__PURE__ */ new WeakMap();
141
141
  function findControllerTokenInModule(imported, seen) {
@@ -221,7 +221,7 @@ function collectModuleDoubles(config) {
221
221
  }
222
222
  function tokenName(token) {
223
223
  const name = readProperty(token, "name");
224
- return typeof name === "string" && name.length > 0 ? name : String(token);
224
+ return typeof name === "string" && name.length > 0 ? sourceClassName(name) : String(token);
225
225
  }
226
226
  function codeName(token) {
227
227
  const description = readProperty(token, "_desc");
@@ -1,13 +1,13 @@
1
- import { describeInstance } from './chunk-BS432KB2.js';
2
- import { useVitestAdapter } from './chunk-YNXQ2WFH.js';
3
- import './chunk-B6C3K27C.js';
1
+ import { describeInstance } from './chunk-XEIOGUGP.js';
2
+ import { useVitestAdapter } from './chunk-6ZGH56G4.js';
3
+ import './chunk-IE4NI2OQ.js';
4
4
  import './chunk-YHDBRBGU.js';
5
5
  import './chunk-TXDBDMIL.js';
6
6
  import './chunk-FOL5RH22.js';
7
7
  import './chunk-PC3FAZT3.js';
8
- import { createFunctionSpy } from './chunk-RYFF55PV.js';
8
+ import { createFunctionSpy } from './chunk-HUS2MHK5.js';
9
9
  import './chunk-Q7L4AWI7.js';
10
- import './chunk-M3NTXLZG.js';
10
+ import { sourceClassName } from './chunk-DYAS4OK4.js';
11
11
  import { angularDialog } from './chunk-5FQZGJLJ.js';
12
12
  import { withDocs } from './chunk-ENCEEH23.js';
13
13
  import { TestBed } from '@angular/core/testing';
@@ -22,10 +22,10 @@ function guardMissingMembers(RefClass, double) {
22
22
  const guarded = new Proxy(double, {
23
23
  get(target, key, receiver) {
24
24
  if (typeof key === "string" && !(key in target) && declared.has(key)) {
25
- const repair = key === "componentInstance" ? `Hand the double the component's stand-in: provideMatDialogRef(${RefClass.name}, { componentInstance: { \u2026 } }).` : `${key} is the dialog doing its own work; open the real dialog through MatDialog for that.`;
25
+ const repair = key === "componentInstance" ? `Hand the double the component's stand-in: provideMatDialogRef(${sourceClassName(RefClass.name)}, { componentInstance: { \u2026 } }).` : `${key} is the dialog doing its own work; open the real dialog through MatDialog for that.`;
26
26
  throw new Error(
27
27
  withDocs(
28
- `[vitest-auto-spy] provideMatDialogRef: the code under test read ${RefClass.name}.${key}, which the double does not have.
28
+ `[vitest-auto-spy] provideMatDialogRef: the code under test read ${sourceClassName(RefClass.name)}.${key}, which the double does not have.
29
29
  ${repair}`,
30
30
  angularDialog
31
31
  )
@@ -47,7 +47,7 @@ function createMatDialogRef(RefClass, init = {}) {
47
47
  closed.next(result);
48
48
  closed.complete();
49
49
  };
50
- const close = createFunctionSpy(`${RefClass.name}.close`);
50
+ const close = createFunctionSpy(`${sourceClassName(RefClass.name)}.close`);
51
51
  getMockAdapter().restoreImplementation(close, emitClose);
52
52
  if (init.closedWith !== void 0) {
53
53
  emitClose(init.closedWith);
@@ -79,7 +79,7 @@ function injectMatDialogRef(RefClass, injector) {
79
79
  if (ref === null) {
80
80
  throw new Error(
81
81
  withDocs(
82
- `${caller}: nothing provides ${RefClass.name} in the injector given. Add provideMatDialogRef(${RefClass.name}) to its providers.`,
82
+ `${caller}: nothing provides ${sourceClassName(RefClass.name)} in the injector given. Add provideMatDialogRef(${sourceClassName(RefClass.name)}) to its providers.`,
83
83
  angularDialog
84
84
  )
85
85
  );
@@ -88,7 +88,7 @@ function injectMatDialogRef(RefClass, injector) {
88
88
  if (double === void 0) {
89
89
  throw new Error(
90
90
  withDocs(
91
- `${caller}: the ${RefClass.name} here is ${describeInstance(ref)}, not one provideMatDialogRef() built. A later provider of ${RefClass.name} won over the double \u2014 list provideMatDialogRef() last, or drop the other one.`,
91
+ `${caller}: the ${sourceClassName(RefClass.name)} here is ${describeInstance(ref)}, not one provideMatDialogRef() built. A later provider of ${sourceClassName(RefClass.name)} won over the double \u2014 list provideMatDialogRef() last, or drop the other one.`,
92
92
  angularDialog
93
93
  )
94
94
  );
@@ -1,8 +1,8 @@
1
- import { assertAngularInternals, pendingRequestsReport, verifyOnTeardown, beforeTestBedReset } from './chunk-GKSVYPZQ.js';
2
- import { flushEffects } from './chunk-DBC6526L.js';
1
+ import { assertAngularInternals, pendingRequestsReport, verifyOnTeardown, beforeTestBedReset } from './chunk-JFOJFCVQ.js';
2
+ import { flushEffects } from './chunk-UH3KWLVL.js';
3
3
  import './chunk-GG7BQKEK.js';
4
- import './chunk-W5EXSI22.js';
5
- import { count, taskName } from './chunk-M3NTXLZG.js';
4
+ import './chunk-HVNKTPKG.js';
5
+ import { count, taskName } from './chunk-DYAS4OK4.js';
6
6
  import { angularHttpNoRequest, angularHttpPending, angularHttpController, angularHttpExpectRequest } from './chunk-5FQZGJLJ.js';
7
7
  import { withDocs } from './chunk-ENCEEH23.js';
8
8
  import { provideHttpClient } from '@angular/common/http';
@@ -1,8 +1,8 @@
1
- import { count } from './chunk-M3NTXLZG.js';
1
+ import { sourceClassName, count } from './chunk-DYAS4OK4.js';
2
2
  import { angularDirectiveApplied } from './chunk-5FQZGJLJ.js';
3
3
  import { withDocs } from './chunk-ENCEEH23.js';
4
4
  import '@angular/compiler';
5
- import { ɵSIGNAL as _SIGNAL, isStandalone, reflectComponentType } from '@angular/core';
5
+ import { ɵSIGNAL as _SIGNAL, isStandalone, DebugElement, reflectComponentType } from '@angular/core';
6
6
  import { By } from '@angular/platform-browser';
7
7
  import { expect } from 'vitest';
8
8
 
@@ -23,6 +23,17 @@ function rootOf(received) {
23
23
  function matching(root, predicate) {
24
24
  return predicate(root) ? [root, ...root.queryAll(predicate)] : root.queryAll(predicate);
25
25
  }
26
+ function matchingNodes(root, predicate) {
27
+ return predicate(root) ? [root, ...root.queryAllNodes(predicate)] : root.queryAllNodes(predicate);
28
+ }
29
+ function isAnchor(node) {
30
+ return !(node instanceof DebugElement);
31
+ }
32
+ function located(nodes) {
33
+ const anchors = nodes.filter(isAnchor).length;
34
+ const elements = nodes.length - anchors;
35
+ return [elements > 0 ? count(elements, "element") : "", anchors > 0 ? count(anchors, "template anchor") : ""].filter((part) => part !== "").join(" and ");
36
+ }
26
37
  function isClass(value) {
27
38
  return typeof value === "function";
28
39
  }
@@ -33,12 +44,64 @@ function componentUnderTest(root) {
33
44
  return void 0;
34
45
  }
35
46
  const mirror = reflectComponentType(type);
36
- return mirror === null || mirror.selector.includes(TESTBED_DIRECTIVE_HOST) ? void 0 : type.name;
47
+ return mirror === null || mirror.selector.includes(TESTBED_DIRECTIVE_HOST) ? void 0 : sourceClassName(type.name);
48
+ }
49
+ function inHostScope(root, directive) {
50
+ const host = Reflect.get(Object(root.componentInstance), "constructor");
51
+ const defs = Reflect.get(Object(Reflect.get(Object(host), "\u0275cmp")), "directiveDefs");
52
+ const list = typeof defs === "function" ? Reflect.apply(defs, void 0, []) : defs;
53
+ return Array.isArray(list) && list.some((def) => Reflect.get(Object(def), "type") === directive);
54
+ }
55
+ var SELECTOR_NOT = 1;
56
+ var SELECTOR_CLASS = 8;
57
+ function selectorText(directive) {
58
+ const selectors = Reflect.get(Object(Reflect.get(directive, "\u0275dir") ?? Reflect.get(directive, "\u0275cmp")), "selectors");
59
+ if (!Array.isArray(selectors) || selectors.length === 0) {
60
+ return void 0;
61
+ }
62
+ const parts = [];
63
+ for (const selector of selectors) {
64
+ if (!Array.isArray(selector)) {
65
+ return void 0;
66
+ }
67
+ let text = String(selector[0]);
68
+ let classes = false;
69
+ for (let index = 1; index < selector.length; index++) {
70
+ const entry = selector[index];
71
+ if (typeof entry === "number") {
72
+ if ((entry & SELECTOR_NOT) !== 0) {
73
+ return void 0;
74
+ }
75
+ classes = (entry & SELECTOR_CLASS) !== 0;
76
+ } else if (classes) {
77
+ text += `.${String(entry)}`;
78
+ } else {
79
+ const value = String(selector[++index]);
80
+ text += value === "" ? `[${String(entry)}]` : `[${String(entry)}="${value}"]`;
81
+ }
82
+ }
83
+ parts.push(text);
84
+ }
85
+ return parts.join(", ");
86
+ }
87
+ function selectorMismatch(directive, selector, root, withDirective) {
88
+ const name = sourceClassName(directive.name);
89
+ if (selector !== void 0 && withDirective.length > 0) {
90
+ return `it is on ${located(withDirective)} that selector does not match.
91
+ Check the selector against the template, or drop it: expect(fixture).toHaveDirectiveApplied(${name}).`;
92
+ }
93
+ if (!inHostScope(root, directive)) {
94
+ return void 0;
95
+ }
96
+ const own = selectorText(directive);
97
+ return `it is not on any element of this fixture, though it is in the host's scope \u2014 so its selector${own === void 0 ? "" : ` '${own}'`} matches nothing the template renders.
98
+ Check that selector against the template, and run fixture.detectChanges() before asserting if the element renders later.`;
37
99
  }
38
- function diagnose(directive, selector, root) {
100
+ function diagnose(directive, selector, root, withDirective) {
101
+ const name = sourceClassName(directive.name);
39
102
  const elements = selector === void 0 ? [] : matching(root, By.css(selector));
40
103
  const where = selector === void 0 ? "" : ` on '${selector}'`;
41
- const prefix = `[vitest-auto-spy] expected ${directive.name} to be applied${where}`;
104
+ const prefix = `[vitest-auto-spy] expected ${name} to be applied${where}`;
42
105
  if (selector !== void 0 && elements.length === 0) {
43
106
  return withDocs(
44
107
  `${prefix}, but no element matches that selector.
@@ -46,19 +109,30 @@ Run fixture.detectChanges() before asserting, and check the selector against the
46
109
  angularDirectiveApplied
47
110
  );
48
111
  }
112
+ if (selector !== void 0 && withDirective.some(isAnchor)) {
113
+ return withDocs(
114
+ `${prefix}, but it is on a template anchor, not on an element \u2014 a structural directive sits on the comment Angular leaves in place of its template.
115
+ Assert it without a selector: expect(fixture).toHaveDirectiveApplied(${name}).`,
116
+ angularDirectiveApplied
117
+ );
118
+ }
119
+ const mismatch = selectorMismatch(directive, selector, root, withDirective);
120
+ if (mismatch !== void 0) {
121
+ return withDocs(`${prefix}, but ${mismatch}`, angularDirectiveApplied);
122
+ }
49
123
  const component = componentUnderTest(root);
50
124
  if (component !== void 0) {
51
- const scope = isStandalone(directive) ? `list ${directive.name} in ${component}'s hostDirectives if the component should carry it, or in its imports if its template uses it` : `add the NgModule that declares ${directive.name} to ${component}'s imports if its template uses it`;
125
+ const scope = isStandalone(directive) ? `list ${name} in ${component}'s hostDirectives if the component should carry it, or in its imports if its template uses it` : `add the NgModule that declares ${name} to ${component}'s imports if its template uses it`;
52
126
  return withDocs(
53
127
  `${prefix}, but it is not on ${component}'s host element or in its template.
54
128
  To apply it, ${scope}.`,
55
129
  angularDirectiveApplied
56
130
  );
57
131
  }
58
- const cause = isStandalone(directive) ? `${directive.name} is standalone, so only the host component's own imports put it in scope` : `${directive.name} is declared by an NgModule, and a test bundle drops that module's scope, so importing it contributes nothing`;
132
+ const cause = isStandalone(directive) ? `${name} is standalone, so only the host component's own imports put it in scope` : `${name} is declared by an NgModule, and a test bundle drops that module's scope, so importing it contributes nothing`;
59
133
  return withDocs(
60
134
  `${prefix}, but it is not on any element of this fixture \u2014 ${cause}.
61
- Build the host with createDirectiveHost({ template, scope: [${isStandalone(directive) ? directive.name : "ItsModule"}] }).`,
135
+ Build the host with createDirectiveHost({ template, scope: [${isStandalone(directive) ? name : "ItsModule"}] }).`,
62
136
  angularDirectiveApplied
63
137
  );
64
138
  }
@@ -80,11 +154,11 @@ Pass the fixture itself, or fixture.debugElement \u2014 not fixture.nativeElemen
80
154
  )
81
155
  );
82
156
  }
83
- const withDirective = matching(root, By.directive(directive));
157
+ const withDirective = matchingNodes(root, By.directive(directive));
84
158
  const applied = selector === void 0 ? withDirective.length > 0 : matching(root, By.css(selector)).some((element) => element.providerTokens.includes(directive));
85
159
  return {
86
160
  pass: applied,
87
- message: () => applied ? `[vitest-auto-spy] expected ${directive.name} not to be applied, but it is on ${count(withDirective.length, "element")}.` : diagnose(directive, selector, root)
161
+ message: () => applied ? `[vitest-auto-spy] expected ${sourceClassName(directive.name)} not to be applied, but it is on ${located(withDirective)}.` : diagnose(directive, selector, root, withDirective)
88
162
  };
89
163
  }
90
164
  function registerDirectiveMatchers() {
@@ -1,8 +1,8 @@
1
- import { describeInstance } from './chunk-BS432KB2.js';
1
+ import { describeInstance } from './chunk-XEIOGUGP.js';
2
2
  import { angularInternalsError } from './chunk-GG7BQKEK.js';
3
- import { createFunctionSpy } from './chunk-RYFF55PV.js';
3
+ import { createFunctionSpy } from './chunk-HUS2MHK5.js';
4
4
  import './chunk-Q7L4AWI7.js';
5
- import { count } from './chunk-M3NTXLZG.js';
5
+ import { count } from './chunk-DYAS4OK4.js';
6
6
  import { angularRouteDouble, angularRouterInject, angularRouterEvents, angularLocationDouble, angularRouteWiring, angularRouterDouble } from './chunk-5FQZGJLJ.js';
7
7
  import { withDocs } from './chunk-ENCEEH23.js';
8
8
  import { TestBed } from '@angular/core/testing';
package/dist/angular.js CHANGED
@@ -40,6 +40,14 @@ var angularTypedElements = "https://asdalexey.github.io/vitest-auto-spy/adapters
40
40
  var angularTrackRuns = "https://asdalexey.github.io/vitest-auto-spy/adapters/angular#counting-recomputations-and-effect-runs";
41
41
  var trackInjectionsUntracked = "https://asdalexey.github.io/vitest-auto-spy/utilities/track-injections#get-on-a-token-that-is-not-tracked";
42
42
 
43
+ // src/lib/message-text.ts
44
+ function count(value, noun, plural = `${noun}s`) {
45
+ return `${value} ${value === 1 ? noun : plural}`;
46
+ }
47
+ function sourceClassName(name) {
48
+ return name.replace(/^_(?=[A-Z])/, "").replace(/\$\d+$/, "");
49
+ }
50
+
43
51
  // src/lib/angular.ts
44
52
  function provideAutoSpy(ObjectClass, methodsToSpyOnOrConfig) {
45
53
  return {
@@ -75,11 +83,11 @@ function describeUnspied(token, injected) {
75
83
  return `[vitest-auto-spy] injectSpy(${name2}): got a plain value, not an auto-spy \u2014 the token is provided for real.
76
84
  Provide the double instead: { provide: ${name2}, useValue: createAutoMock<T>() }.`;
77
85
  }
78
- const name = token.name;
86
+ const name = sourceClassName(token.name);
79
87
  const provider = Reflect.get(token, "\u0275prov");
80
88
  const fromRoot = Reflect.get(Object(provider), "providedIn") === "root";
81
89
  const built = Reflect.get(Object(Reflect.get(Object(injected), "constructor")), "name");
82
- const what = typeof built === "string" && built.length > 0 ? `a real ${built}` : "a real instance";
90
+ const what = typeof built === "string" && built.length > 0 ? `a real ${sourceClassName(built)}` : "a real instance";
83
91
  const why = fromRoot ? "nothing in the testing module provides a double, so Angular built it (providedIn: 'root')" : "the testing module provides the real class, not a double";
84
92
  return `[vitest-auto-spy] injectSpy(${name}): got ${what} \u2014 ${why}.
85
93
  Add provideAutoSpy(${name}) to providers.`;
@@ -240,7 +248,7 @@ function explainLateOverride(error, component, token) {
240
248
  }
241
249
  return new Error(
242
250
  _message_link$withDocs(
243
- `[vitest-auto-spy] overrideComponentProvider(${component.name}, ${token.name}) ran after the testing module was instantiated, and Angular accepts no override past that point. Something read the injector first \u2014 a \`TestBed.inject\`, an \`injectSpy\`, a \`createComponent\` \u2014 earlier in this test or in the same \`beforeCreate\`. Override first, then inject.`,
251
+ `[vitest-auto-spy] overrideComponentProvider(${sourceClassName(component.name)}, ${sourceClassName(token.name)}) ran after the testing module was instantiated, and Angular accepts no override past that point. Something read the injector first \u2014 a \`TestBed.inject\`, an \`injectSpy\`, a \`createComponent\` \u2014 earlier in this test or in the same \`beforeCreate\`. Override first, then inject.`,
244
252
  angularOverrideLate
245
253
  ),
246
254
  { cause: error }
@@ -288,7 +296,7 @@ function componentInjector(fixture, component) {
288
296
  }
289
297
  function describeResolved(resolved) {
290
298
  const constructorName = readProperty(readProperty(resolved, "constructor"), "name");
291
- return typeof constructorName === "string" && constructorName.length > 0 ? `a ${constructorName} instance` : String(resolved);
299
+ return typeof constructorName === "string" && constructorName.length > 0 ? `a ${sourceClassName(constructorName)} instance` : String(resolved);
292
300
  }
293
301
  function verify(fixture, { component, token, spy }) {
294
302
  const injector = componentInjector(fixture, component);
@@ -301,7 +309,7 @@ function verify(fixture, { component, token, spy }) {
301
309
  }
302
310
  throw new Error(
303
311
  _message_link$withDocs(
304
- `[vitest-auto-spy] overrideComponentProvider(${component.name}, ${token.name}): the override did not apply \u2014 ${component.name} resolved ${token.name} to ${describeResolved(resolved)}, not the spy this call returned.
312
+ `[vitest-auto-spy] overrideComponentProvider(${sourceClassName(component.name)}, ${sourceClassName(token.name)}): the override did not apply \u2014 ${sourceClassName(component.name)} resolved ${sourceClassName(token.name)} to ${describeResolved(resolved)}, not the spy this call returned.
305
313
  ` + explainMissedOverride(token, resolved),
306
314
  angularOverrideApplied
307
315
  )
@@ -309,7 +317,7 @@ function verify(fixture, { component, token, spy }) {
309
317
  }
310
318
  function explainMissedOverride(token, resolved) {
311
319
  const later = _spy_mark$isAutoSpyLike(resolved) ? "a different double" : "the real service";
312
- return `It got ${later} because something configured ${token.name} again after this call \u2014 a later TestBed.overrideProvider or configureTestingModule. Keep overrideComponentProvider as the last word on it.`;
320
+ return `It got ${later} because something configured ${sourceClassName(token.name)} again after this call \u2014 a later TestBed.overrideProvider or configureTestingModule. Keep overrideComponentProvider as the last word on it.`;
313
321
  }
314
322
  function verifyOnNextCreate(entry) {
315
323
  if (readTestBedMethod("createComponent") === void 0) {
@@ -342,7 +350,7 @@ function hasEmptyRuntimeScope(module) {
342
350
  }
343
351
  function moduleName(module) {
344
352
  const name = readProperty(module, "name");
345
- return typeof name === "string" ? name : String(module);
353
+ return typeof name === "string" ? sourceClassName(name) : String(module);
346
354
  }
347
355
  var DEFINITION_LISTS = ["providers", "viewProviders", "dependencies"];
348
356
  function resolveList(list) {
@@ -437,6 +445,9 @@ function tokenName(token) {
437
445
  const name = typeof token === "function" ? token.name : void 0;
438
446
  return typeof name === "string" && name.length > 0 ? name : String(token);
439
447
  }
448
+ function messageName(token) {
449
+ return sourceClassName(tokenName(token));
450
+ }
440
451
  function trackInjections(tokens, options = {}) {
441
452
  const build = options.double ?? createSpyForToken;
442
453
  const doubles = new Map(tokens.map((token) => [token, build(token)]));
@@ -457,8 +468,8 @@ function trackInjections(tokens, options = {}) {
457
468
  if (!doubles.has(token)) {
458
469
  throw new Error(
459
470
  _message_link$withDocs(
460
- `[vitest-auto-spy] trackInjections(...).get(${tokenName(token)}): that token is not tracked by this log.
461
- Tracked here: ${tokens.map(tokenName).join(", ") || "(none)"}. Add it to the trackInjections([...]) list, or read it from the injector directly \u2014 \`get\` only answers for the tokens whose providers this log created.`,
471
+ `[vitest-auto-spy] trackInjections(...).get(${messageName(token)}): that token is not tracked by this log.
472
+ Tracked here: ${tokens.map(messageName).join(", ") || "(none)"}. Add it to the trackInjections([...]) list, or read it from the injector directly \u2014 \`get\` only answers for the tokens whose providers this log created.`,
462
473
  trackInjectionsUntracked
463
474
  )
464
475
  );
@@ -536,7 +547,7 @@ var CLASS = 8;
536
547
  var SIGNAL_BASED = 1;
537
548
  var stubCount = 0;
538
549
  function createComponentStub(real, overrides = {}, options = {}) {
539
- const realName = typeof real === "function" ? real.name : String(real);
550
+ const realName = typeof real === "function" ? sourceClassName(real.name) : String(real);
540
551
  const name = `${realName}Stub`;
541
552
  const pipe = definition(real, "\u0275pipe");
542
553
  if (pipe) {
@@ -712,7 +723,7 @@ ${askFor(autoSpies.tokens().map(describeToken))}`,
712
723
  // src/lib/host-element.ts
713
724
  function describeElement(element) {
714
725
  const classes = [...element.classList].map((name) => `.${name}`).join("");
715
- return `<${element.tagName.toLowerCase()}${classes}> (${element.constructor.name})`;
726
+ return `<${element.tagName.toLowerCase()}${classes}> (${sourceClassName(element.constructor.name)})`;
716
727
  }
717
728
  function fail(message) {
718
729
  return _message_link$withDocs(`[vitest-auto-spy] ${message}`, angularTypedElements);
@@ -738,7 +749,7 @@ Check the predicate against the rendered template, or use queryElement(fixture,
738
749
  }
739
750
  function checkType(element, type, what, helper) {
740
751
  if (!(element instanceof type)) {
741
- throw new TypeError(fail(`${helper}: ${what} ${describeElement(element)}, not ${type.name}.`));
752
+ throw new TypeError(fail(`${helper}: ${what} ${describeElement(element)}, not ${sourceClassName(type.name)}.`));
742
753
  }
743
754
  return element;
744
755
  }
@@ -779,10 +790,10 @@ function whyNoInputs(component) {
779
790
  Import the component from its own file rather than through the barrel.`;
780
791
  }
781
792
  if (Reflect.get(component, "\u0275pipe") !== void 0) {
782
- return `${component.name} is a @Pipe, and a pipe has no inputs.
793
+ return `${sourceClassName(component.name)} is a @Pipe, and a pipe has no inputs.
783
794
  Call its transform() directly, or render it inside a host component.`;
784
795
  }
785
- return `${component.name} carries no \u0275cmp, so Angular never compiled it as a component and there are no inputs to set.
796
+ return `${sourceClassName(component.name)} carries no \u0275cmp, so Angular never compiled it as a component and there are no inputs to set.
786
797
  Pass the @Component class the fixture was created from.`;
787
798
  }
788
799
  function unknownInputsError(caller, component, unknown, declared) {
@@ -795,7 +806,7 @@ function unknownInputsError(caller, component, unknown, declared) {
795
806
  const known = declared.length > 0 ? `Its inputs are ${quote(declared)}.` : "It declares no inputs at all.";
796
807
  return new Error(
797
808
  _message_link$withDocs(
798
- `[vitest-auto-spy] ${caller}: ${component.name} declares no input named ${quote(unknown)}.${hint}
809
+ `[vitest-auto-spy] ${caller}: ${sourceClassName(component.name)} declares no input named ${quote(unknown)}.${hint}
799
810
  ${known} Angular would answer the name with an NG0303 and change nothing; a plain field is assigned on the instance instead, and a signal the component owns is set through the signal.`,
800
811
  angularInputs
801
812
  )
@@ -888,31 +899,32 @@ function refusedInImports(dependency) {
888
899
  return void 0;
889
900
  }
890
901
  const definition2 = DECLARATION_KEYS.map((key) => Reflect.get(dependency, key)).find((value) => value !== void 0);
891
- return definition2 !== void 0 && Reflect.get(definition2, "standalone") !== true ? dependency.name : void 0;
902
+ return definition2 !== void 0 && Reflect.get(definition2, "standalone") !== true ? sourceClassName(dependency.name) : void 0;
892
903
  }
893
904
  function assertScopeIsImportable(component, kept, candidates) {
894
905
  const refused = kept.filter((dependency) => refusedInImports(dependency) !== void 0);
895
906
  if (refused.length === 0) {
896
907
  return;
897
908
  }
898
- const names = refused.map((dependency) => dependency.name);
909
+ const names = refused.map((dependency) => sourceClassName(dependency.name));
899
910
  const owners = [...new Set(refused.map((dependency) => declaringModule(dependency, candidates)))];
900
911
  const [owner] = owners;
901
912
  const fix = owners.length === 1 && owner !== void 0 ? `keepModules: [${owner}]` : `keepModules: [<the NgModule that declares ${String(names[0])}>]`;
902
913
  throw new Error(
903
914
  _message_link$withDocs(
904
- `[vitest-auto-spy] renderShallow(${component.name}, { keepTemplate: true }): ${names.join(", ")} ${names.length === 1 ? "is" : "are"} declared by ${owner === void 0 || owners.length > 1 ? "an NgModule" : owner}, not standalone, and Angular takes only standalone declarations and NgModules in \`imports\`.
915
+ `[vitest-auto-spy] renderShallow(${sourceClassName(component.name)}, { keepTemplate: true }): ${names.join(", ")} ${names.length === 1 ? "is" : "are"} declared by ${owner === void 0 || owners.length > 1 ? "an NgModule" : owner}, not standalone, and Angular takes only standalone declarations and NgModules in \`imports\`.
905
916
  An AOT build flattened that module away, so name it and it is put back whole: ${fix}.`,
906
917
  angularKeepTemplate
907
918
  )
908
919
  );
909
920
  }
910
921
  function declaringModule(dependency, candidates) {
911
- return candidates.filter((candidate) => typeof candidate === "function").find((candidate) => {
922
+ const owner = candidates.filter((candidate) => typeof candidate === "function").find((candidate) => {
912
923
  const scope = /* @__PURE__ */ new Set();
913
924
  exportedScope(candidate, scope, /* @__PURE__ */ new Set());
914
925
  return scope.has(dependency);
915
- })?.name;
926
+ });
927
+ return owner === void 0 ? void 0 : sourceClassName(owner.name);
916
928
  }
917
929
  function unwrapList(value) {
918
930
  const resolved = typeof value === "function" ? Reflect.apply(value, void 0, []) : value;
@@ -1047,11 +1059,6 @@ function runEffect(effectRef) {
1047
1059
  });
1048
1060
  }
1049
1061
 
1050
- // src/lib/message-text.ts
1051
- function count(value, noun, plural = `${noun}s`) {
1052
- return `${value} ${value === 1 ? noun : plural}`;
1053
- }
1054
-
1055
1062
  // src/lib/zoneless.ts
1056
1063
  function flushEffects() {
1057
1064
  TestBed.inject(NgZone).run(() => {