vitest-auto-spy 1.2.0 → 1.4.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/README.md CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  # vitest-auto-spy
4
4
 
5
- **Automatic, fully-typed test spies from a class — powered by Vitest's `vi.fn()`.**
5
+ **Auto-generate fully-typed test spies from a class — across any Vitest-compatible runtime and framework.**
6
6
 
7
- A drop-in replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies):
8
- same API, but spying only on **Vitest** instead of Jest.
7
+ The only auto-spy library that reads a **class** and gives a **fully-typed** spy of every method
8
+ with **return-type-aware** helpers (`resolveWith` / `nextWith` / `calledWith`). Available today for
9
+ **Vitest** (with **RxJS** and **Angular** helpers); **Bun**, **node:test**, and NestJS / React /
10
+ Vue·Pinia / Svelte recipes land in the next release ([availability](#availability)). A drop-in
11
+ replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — same API.
9
12
 
10
13
  [![npm version](https://img.shields.io/npm/v/vitest-auto-spy?color=cb3837&logo=npm)](https://www.npmjs.com/package/vitest-auto-spy)
11
14
  [![npm downloads](https://img.shields.io/npm/dm/vitest-auto-spy?color=cb3837&logo=npm)](https://www.npmjs.com/package/vitest-auto-spy)
@@ -15,33 +18,53 @@ same API, but spying only on **Vitest** instead of Jest.
15
18
  [![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
16
19
  [![license](https://img.shields.io/npm/l/vitest-auto-spy?color=blue)](./LICENSE)
17
20
 
21
+ [![Vitest](https://img.shields.io/badge/Vitest-✓-6E9F18?logo=vitest&logoColor=white)](#runtimes)
22
+ [![Bun](https://img.shields.io/badge/Bun-next-yellow?logo=bun&logoColor=white)](#availability)
23
+ [![node:test](https://img.shields.io/badge/node%3Atest-next-yellow?logo=node.js&logoColor=white)](#availability)
24
+ [![runtime deps](https://img.shields.io/badge/runtime%20deps-0-brightgreen)](#install)
25
+
18
26
  📦 [**npm**](https://www.npmjs.com/package/vitest-auto-spy) · 🐙 [**GitHub**](https://github.com/ASDAlexey/vitest-auto-spy) · 🔖 [**Changelog**](./CHANGELOG.md)
19
27
 
20
28
  </div>
21
29
 
22
30
  ---
23
31
 
24
- - 🧪 Reads a class and generates a typed `vi.fn()` spy for **every** method
32
+ - 🧪 Reads a class and generates a typed spy for **every** method — no hand-written `vi.fn()` lists
33
+ - 🧬 Or mock from a **type/interface** alone — `createAutoMock<T>()`, no class required
34
+ - 🌐 One `MockAdapter` core — **Vitest** today; **Bun** and **`node:test`** next release, identical API on each
35
+ - 🧩 Framework recipes: **Angular** today; **NestJS, React, Vue/Pinia, Svelte** next release
25
36
  - 🎯 Return-type-aware helpers — sync, `Promise`, and `Observable` all get the right API
26
37
  - 🔀 `calledWith` / `mustBeCalledWith` argument dispatch
27
38
  - 📡 First-class RxJS `Observable` spying (`nextWith`, `nextWithValues`, `throwWith`, …)
28
39
  - ⚙️ Getter / setter spies via `accessorSpies`
29
- - 🅰️ Angular helpers (`provideAutoSpy`, `injectSpy`) works with **both zoneless and zone.js**
30
- - 🟢 100% test coverage, zero runtime dependencies beyond a tiny arg serializer
40
+ - 🟢 100% test coverage, **zero runtime dependencies** (in-tree arg serializer, no `javascript-stringify`)
31
41
 
32
42
  ## Table of contents
33
43
 
34
44
  - [Install](#install)
45
+ - [Availability](#availability)
46
+ - [Quick start](#quick-start)
35
47
  - [Why](#why)
48
+ - [How it works (and what it won't spy)](#how-it-works-and-what-it-wont-spy)
49
+ - [Comparison](#comparison)
36
50
  - [Migrating from jest-auto-spies](#migrating-from-jest-auto-spies)
37
51
  - [Configuration](#configuration)
52
+ - [Auto-mock by type (no class needed)](#auto-mock-by-type-no-class-needed)
38
53
  - [Synchronous methods](#synchronous-methods)
39
54
  - [Promise-returning methods](#promise-returning-methods)
40
55
  - [Observable methods & properties](#observable-returning-methods--observable-properties)
41
56
  - [Getters & setters](#getters--setters)
42
- - [Angular helpers](#angular-helpers)
57
+ - [Framework adapters](#framework-adapters)
58
+ - [NestJS](#nestjs)
59
+ - [React (Testing Library)](#react-testing-library)
60
+ - [Vue / Pinia](#vue--pinia)
61
+ - [Svelte](#svelte)
62
+ - [Angular](#angular)
43
63
  - [API reference](#api-reference)
64
+ - [FAQ & troubleshooting](#faq--troubleshooting)
65
+ - [Versioning](#versioning)
44
66
  - [Contributing](#contributing)
67
+ - [Acknowledgements](#acknowledgements)
45
68
  - [License](#license)
46
69
 
47
70
  ## Install
@@ -50,33 +73,77 @@ same API, but spying only on **Vitest** instead of Jest.
50
73
  npm i -D vitest-auto-spy
51
74
  ```
52
75
 
53
- Peer dependencies are all **optional** and provided by your project: `vitest` (required),
54
- plus `rxjs` and `@angular/core` only if you use the matching entry point. The package itself
55
- has **zero runtime dependencies**.
76
+ ### Requirements
77
+
78
+ | Tool | Minimum |
79
+ | --- | --- |
80
+ | Node.js | ≥ 18 |
81
+ | Vitest | ≥ 1.0 (required peer) |
82
+ | TypeScript | ≥ 4.7 for the typed helpers (plain JS works too, just untyped) |
56
83
 
57
- ### Entry points
84
+ Ships **dual ESM + CommonJS** with bundled `.d.ts` types, so it drops into both `import`- and
85
+ `require`-style test setups.
58
86
 
59
- The library ships a framework-agnostic core and two opt-in layers, so a plain Node / Bun /
60
- React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
87
+ ### Peer dependencies
61
88
 
62
- | Import | Provides | Pulls in |
89
+ All peers are **provided by your project**; `rxjs` and `@angular/core` are **optional** — install
90
+ them only for the matching entry point. The package itself has **zero runtime dependencies**.
91
+
92
+ | Peer | Needed for | Optional? |
63
93
  | --- | --- | --- |
64
- | `vitest-auto-spy` | `createSpyFromClass`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` |
65
- | `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) + `createObservableWithValues` | `rxjs` |
66
- | `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `mockReadonlyProp*`, `mockAccessorsProp` | `@angular/core` |
94
+ | `vitest` | the default runner | no |
95
+ | `rxjs` | `vitest-auto-spy/rxjs` observable spies (and `Spy<T>` type-checking) | yes |
96
+ | `@angular/core` | `vitest-auto-spy/angular` helpers | yes |
97
+
98
+ ## Availability
99
+
100
+ > **On npm today vs. shipping next.** `vitest-auto-spy@1.3.0` publishes the **Vitest core** plus the
101
+ > **RxJS** and **Angular** layers. The Bun / `node:test` runtimes and the NestJS / React / Vue /
102
+ > Svelte recipes are implemented and tested in the repo but **not yet published as entry points** —
103
+ > they land in the next release. Snippets that use them are marked _next release_ below.
104
+
105
+ | Entry point | Status |
106
+ | --- | --- |
107
+ | `vitest-auto-spy` · `vitest-auto-spy/rxjs` · `vitest-auto-spy/angular` | ✅ **Published** (1.3.0) |
108
+ | `vitest-auto-spy/bun` · `vitest-auto-spy/node` | 🔜 next release |
109
+ | `vitest-auto-spy/nestjs` · `/react` · `/vue` · `/svelte` | 🔜 next release |
110
+
111
+ ## Quick start
112
+
113
+ Pass a class — every method becomes a typed spy, and the **constructor is never called** (no side
114
+ effects). The helper you get on each method matches its return type:
67
115
 
68
116
  ```ts
69
- import { createSpyFromClass } from 'vitest-auto-spy';
70
- import 'vitest-auto-spy/rxjs'; // once (e.g. in your test setup) — enables observable spies
71
- import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/angular';
117
+ import { beforeEach, expect, it } from 'vitest';
118
+ import { createSpyFromClass, type Spy } from 'vitest-auto-spy';
119
+
120
+ class UserService {
121
+ getName(id: number): string {
122
+ return 'real name';
123
+ }
124
+ async getUser(id: number): Promise<{ id: number; name: string }> {
125
+ return fetchUser(id);
126
+ }
127
+ }
128
+
129
+ let userService: Spy<UserService>;
130
+
131
+ beforeEach(() => {
132
+ userService = createSpyFromClass(UserService); // every method is now a spy
133
+ });
134
+
135
+ it('stubs each method with the right helper for its return type', async () => {
136
+ userService.getName.mockReturnValue('Ada'); // sync
137
+ userService.getUser.resolveWith({ id: 1, name: 'Ada' }); // Promise helper
138
+
139
+ expect(userService.getName(1)).toBe('Ada');
140
+ await expect(userService.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
141
+ expect(userService.getName).toHaveBeenCalledWith(1);
142
+ });
72
143
  ```
73
144
 
74
- > Using an observable spy (`observablePropsToSpyOn`, `nextWith`, …) without importing
75
- > `vitest-auto-spy/rxjs` throws a clear hint telling you to add that import.
76
- >
77
- > The decoupling is at the **runtime** level. The core's _type_ surface (`Spy<T>`) still
78
- > references rxjs types, so keep `rxjs` available for type-checking (it's normally already a
79
- > devDependency); none of it reaches your runtime bundle.
145
+ No class, only a TypeScript type? Reach for
146
+ [`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed).
80
147
 
81
148
  ## Why
82
149
 
@@ -105,6 +172,100 @@ beforeEach(() => {
105
172
  `Spy<UserService>` exposes each method as a `vi.fn()` **plus** the right helpers based on
106
173
  the method's return type (sync / `Promise` / `Observable`).
107
174
 
175
+ ## How it works (and what it won't spy)
176
+
177
+ `createSpyFromClass(MyService)` reads `MyService.prototype` and walks the **prototype chain** — it
178
+ never `new`s the class. Concretely:
179
+
180
+ - ✅ **The class is never instantiated.** The constructor and its side effects (HTTP clients, DB
181
+ connections, `inject()` calls) never run — you pass the class itself, not an instance.
182
+ - ✅ **Inherited methods are spied too**, all the way up the prototype chain.
183
+ - ✅ Each method is replaced by a fresh spy carrying the helpers that match its **return type**:
184
+ sync → `mockReturnValue` / `calledWith`; `Promise` → `resolveWith` / `rejectWith`; `Observable`
185
+ → `nextWith` / `throwWith` / … .
186
+
187
+ What it **won't** auto-discover — by design, because these aren't prototype methods:
188
+
189
+ - ⚠️ **Arrow-function class fields** (`doThing = () => {}`) are instance properties set in the
190
+ constructor, so prototype scanning can't see them. Use regular methods, list them explicitly, or
191
+ mock them by hand. (Same constraint as `jest-auto-spies`.)
192
+ - ⚠️ **Getters / setters** are skipped unless named in `gettersToSpyOn` / `settersToSpyOn` — see
193
+ [Getters & setters](#getters--setters).
194
+ - ⚠️ **Plain data properties** carry no value until you set one; auto-spy mocks *behaviour*
195
+ (methods), not state. To mock by type including properties, use
196
+ [`createAutoMock`](#auto-mock-by-type-no-class-needed).
197
+
198
+ ## Entry points & runtimes
199
+
200
+ The library ships a framework-agnostic core plus runtime and framework layers, so a plain
201
+ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
202
+
203
+ | Import | Provides | Pulls in | Status |
204
+ | --- | --- | --- | :---: |
205
+ | `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
206
+ | `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) + `createObservableWithValues` | `rxjs` | ✅ |
207
+ | `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `mockReadonlyProp*`, `mockAccessorsProp` | `@angular/core` | ✅ |
208
+ | `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | 🔜 |
209
+ | `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()` | `node:test` | 🔜 |
210
+ | `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`) | 🔜 |
211
+ | `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | 🔜 |
212
+ | `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | 🔜 |
213
+ | `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | 🔜 |
214
+
215
+ ✅ published in 1.3.0 · 🔜 next release (see [Availability](#availability)).
216
+
217
+ > The framework subpaths import **nothing** from their framework — the helpers are structural, so
218
+ > `@nestjs/*`, `react`, `vue`/`pinia` and `svelte` stay your own (already-present) dev dependencies and
219
+ > never reach this package's runtime bundle.
220
+
221
+ ```ts
222
+ import { createSpyFromClass } from 'vitest-auto-spy';
223
+ import 'vitest-auto-spy/rxjs'; // once (e.g. in your test setup) — enables observable spies
224
+ import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/angular';
225
+ ```
226
+
227
+ ### Runtimes
228
+
229
+ The core is runner-agnostic behind a `MockAdapter`: pick the entry that matches your test
230
+ runner — the public API (`createSpyFromClass`, `calledWith`, `resolveWith`, `nextWith`, …) is
231
+ identical across all three.
232
+
233
+ ```ts
234
+ import { createSpyFromClass } from 'vitest-auto-spy'; // Vitest (default, zero-config)
235
+ import { createSpyFromClass } from 'vitest-auto-spy/bun'; // Bun — bun:test (next release)
236
+ import { createSpyFromClass } from 'vitest-auto-spy/node'; // node:test (next release)
237
+ ```
238
+
239
+ > Only the auto-spy helpers are normalised across runtimes; **native** mock methods stay the
240
+ > runner's own — `mockReturnValue` on Vitest/Bun, `spy.method.mock.mockImplementation` on
241
+ > `node:test`. Each entry registers its adapter on import, so import the one matching your runner.
242
+
243
+ > Using an observable spy (`observablePropsToSpyOn`, `nextWith`, …) without importing
244
+ > `vitest-auto-spy/rxjs` throws a clear hint telling you to add that import.
245
+ >
246
+ > The decoupling is at the **runtime** level. The core's _type_ surface (`Spy<T>`) still
247
+ > references rxjs types, so keep `rxjs` available for type-checking (it's normally already a
248
+ > devDependency); none of it reaches your runtime bundle.
249
+ >
250
+ > The same inversion-of-control applies to the **test runner**: the core no longer imports
251
+ > `vitest` directly — `vi.fn()` / `vi.spyOn()` sit behind a `MockAdapter` that the
252
+ > `vitest-auto-spy` entry registers by default, so it stays zero-config. This is the groundwork
253
+ > for running the exact same core on other Vitest-compatible runners.
254
+
255
+ ## Comparison
256
+
257
+ | Library | Reads a class? | Return-type-aware helpers? | Runtimes | We win on |
258
+ | --- | :---: | :---: | --- | --- |
259
+ | **vitest-auto-spy** | ✅ | ✅ | Vitest (Bun · node:test next) | — |
260
+ | [jest-auto-spies](https://www.npmjs.com/package/jest-auto-spies) | ✅ | ✅ | Jest only | Vitest/Bun/Node successor, **same API** — direct migration path |
261
+ | [vitest-mock-extended](https://www.npmjs.com/package/vitest-mock-extended) | ❌ (Proxy) | ❌ | Vitest | Return-type ergonomics **and** reading a real class (we also ship a Proxy mode: [`createAutoMock`](#auto-mock-by-type-no-class-needed)) |
262
+ | [@golevelup/ts-vitest](https://www.npmjs.com/package/@golevelup/ts-vitest) | partial | ❌ | Vitest | Typed `Promise`/`Observable` helpers + explicit class→spy + `mustBeCalledWith` |
263
+ | [sinon](https://www.npmjs.com/package/sinon) | ❌ (manual) | ❌ | Any | Auto-generated + fully typed vs. manual + loosely typed |
264
+
265
+ **The pitch:** the only auto-spy library that reads a **class** and gives a **fully-typed** spy of
266
+ every method with **return-type-aware** control helpers (`resolveWith` / `nextWith` / `calledWith`) —
267
+ across any Vitest-compatible runtime and framework.
268
+
108
269
  ## Migrating from jest-auto-spies
109
270
 
110
271
  The public API is intentionally identical. In most projects the migration is a
@@ -118,7 +279,7 @@ The public API is intentionally identical. In most projects the migration is a
118
279
  ```
119
280
 
120
281
  The only API-shape change from `jest-auto-spies` is that the Angular helpers and the
121
- observable layer live behind the `/angular` and `/rxjs` subpaths (see [Entry points](#entry-points)).
282
+ observable layer live behind the `/angular` and `/rxjs` subpaths (see [Entry points & runtimes](#entry-points--runtimes)).
122
283
 
123
284
  | jest-auto-spies | vitest-auto-spy | Status |
124
285
  | --- | --- | --- |
@@ -152,6 +313,50 @@ createSpyFromClass(MyService, {
152
313
  });
153
314
  ```
154
315
 
316
+ ## Auto-mock by type (no class needed)
317
+
318
+ `createSpyFromClass` reads a real class's prototype. When you only have a TypeScript **interface or
319
+ type** (no runtime class), use `createAutoMock<T>()` — it builds the spy lazily from the type alone,
320
+ via a `Proxy`:
321
+
322
+ ```ts
323
+ import { createAutoMock } from 'vitest-auto-spy';
324
+
325
+ interface UserService {
326
+ getName(id: number): string;
327
+ getUser(id: number): Promise<User>;
328
+ apiUrl: string;
329
+ }
330
+
331
+ // Before — needs a concrete class:
332
+ // const svc = createSpyFromClass(UserServiceClass);
333
+
334
+ // After — type only, no class:
335
+ const svc = createAutoMock<UserService>();
336
+ ```
337
+
338
+ Every accessed method becomes a decorated spy with the **same typed control helpers** as
339
+ `createSpyFromClass`, materialized lazily and cached (same reference on re-access):
340
+
341
+ ```ts
342
+ svc.getName.calledWith(1).mockReturnValue('Ada'); // sync, arg-matched
343
+ svc.getUser.resolveWith({ id: 1, name: 'Ada' }); // promise helper
344
+ expect(svc.getName(1)).toBe('Ada');
345
+ await expect(svc.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
346
+ ```
347
+
348
+ Seed concrete values or implementations with the optional `overrides` argument (seeded keys are
349
+ returned as-is, never turned into spies):
350
+
351
+ ```ts
352
+ const svc = createAutoMock<UserService>({ apiUrl: 'https://api.test' });
353
+ expect(svc.apiUrl).toBe('https://api.test'); // or assign: svc.apiUrl = '...'
354
+ ```
355
+
356
+ > Caveat: with only a type at runtime, methods and plain properties are indistinguishable on
357
+ > access — an un-seeded property read returns a spy. Seed real property values via `overrides`
358
+ > (or assignment) to get them back verbatim.
359
+
155
360
  ## Synchronous methods
156
361
 
157
362
  ```ts
@@ -246,7 +451,144 @@ spy.userName = 'New Name';
246
451
  expect(spy.accessorSpies.setters.userName).toHaveBeenCalledWith('New Name');
247
452
  ```
248
453
 
249
- ## Angular helpers
454
+ ## Framework adapters
455
+
456
+ The core is framework-agnostic — `createSpyFromClass` / `createAutoMock` work in any test. The
457
+ subpaths below add a natural import and, where the framework has class DI, a tiny `provide*` helper.
458
+ None of them pull the framework into this package; they're recipes over the same core.
459
+
460
+ > **Angular** (`vitest-auto-spy/angular`) is published today. The **NestJS, React, Vue/Pinia and
461
+ > Svelte** entry points ship in the next release ([Availability](#availability)) — until then you
462
+ > can copy the recipe using the core `vitest-auto-spy` import directly.
463
+
464
+ ### NestJS
465
+
466
+ Use `provideAutoSpy` to register a fully-mocked service in a `TestingModule`, then `injectSpy` to
467
+ pull it back out already typed as `Spy<T>`. `@nestjs/common` / `@nestjs/testing` are your own
468
+ (optional) peers — the helper imports neither:
469
+
470
+ ```ts
471
+ import { Test, type TestingModule } from '@nestjs/testing';
472
+ import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/nestjs';
473
+ import { beforeEach, expect, it } from 'vitest';
474
+
475
+ import { AuthService } from './auth.service';
476
+ import { UserService } from './user.service';
477
+
478
+ let moduleRef: TestingModule;
479
+ let userServiceSpy: Spy<UserService>;
480
+
481
+ beforeEach(async () => {
482
+ moduleRef = await Test.createTestingModule({
483
+ providers: [AuthService, provideAutoSpy(UserService)],
484
+ }).compile();
485
+
486
+ userServiceSpy = injectSpy(moduleRef, UserService);
487
+ });
488
+
489
+ it('logs in a known user', () => {
490
+ userServiceSpy.findByEmail.mockReturnValue({ id: 1, name: 'Ada' });
491
+
492
+ const auth = moduleRef.get(AuthService);
493
+ expect(auth.login('ada@example.com')).toBeTruthy();
494
+ expect(userServiceSpy.findByEmail).toHaveBeenCalledWith('ada@example.com');
495
+ });
496
+ ```
497
+
498
+ ### React (Testing Library)
499
+
500
+ React has no DI container, so there's no `provide*` helper — the recipe is: **spy the classes you
501
+ own** (services, stores, API clients, hook deps), then pass the spy into a Context provider or hook.
502
+ The spy is a plain object of spied functions, so it drops straight into `value={...}`:
503
+
504
+ ```tsx
505
+ import { render, screen } from '@testing-library/react';
506
+ import { createSpyFromClass, type Spy } from 'vitest-auto-spy/react';
507
+ import { CartContext, Cart } from './cart';
508
+
509
+ class CartStore {
510
+ getItemCount(): number { return 0; }
511
+ checkout(token: string): Promise<{ orderId: string }> { /* ... */ }
512
+ }
513
+
514
+ let cart: Spy<CartStore>;
515
+
516
+ beforeEach(() => {
517
+ cart = createSpyFromClass(CartStore); // every method is now a spy
518
+ });
519
+
520
+ it('shows the item count from the injected store', () => {
521
+ cart.getItemCount.mockReturnValue(3);
522
+
523
+ render(
524
+ <CartContext.Provider value={cart}>
525
+ <Cart />
526
+ </CartContext.Provider>,
527
+ );
528
+
529
+ expect(screen.getByText('3 items')).toBeInTheDocument();
530
+ });
531
+
532
+ it('drives async deps and asserts the component called them', async () => {
533
+ cart.checkout.resolveWith({ orderId: 'ord_42' });
534
+ // ...trigger checkout in the UI...
535
+ expect(cart.checkout).toHaveBeenCalledWith('tok_abc');
536
+ });
537
+ ```
538
+
539
+ ### Vue / Pinia
540
+
541
+ `provideAutoSpy(token, Class)` returns a `{ [token]: Spy<T> }` map you can spread into
542
+ `@vue/test-utils`' `global.provide`; for a class-based Pinia store, spy it directly:
543
+
544
+ ```ts
545
+ import { mount } from '@vue/test-utils';
546
+ import { createSpyFromClass, provideAutoSpy } from 'vitest-auto-spy/vue';
547
+
548
+ // (a) class-based service injected via provide / global.provide
549
+ import { UserServiceKey, UserService } from '@/services/user.service';
550
+
551
+ const provide = provideAutoSpy(UserServiceKey, UserService); // { [UserServiceKey]: Spy<UserService> }
552
+ provide[UserServiceKey].getName.mockReturnValue('Fake Name');
553
+
554
+ const wrapper = mount(UserBadge, { global: { provide } });
555
+ expect(provide[UserServiceKey].getName).toHaveBeenCalled();
556
+
557
+ // (b) class-based Pinia store — every action becomes a spy
558
+ import { CartStore } from '@/stores/cart.store';
559
+
560
+ const store = createSpyFromClass(CartStore);
561
+ store.itemCount.mockReturnValue(3); // sync action/getter
562
+ store.checkout.resolveWith({ orderId: 'ord_42' }); // async action (Promise)
563
+ await store.checkout('tok_abc');
564
+ expect(store.checkout).toHaveBeenCalledWith('tok_abc');
565
+ ```
566
+
567
+ ### Svelte
568
+
569
+ Svelte has no class-based DI, so it's a recipe: keep your logic in plain class-based
570
+ services/stores, spy the class, and hand the spy to the component the same way it receives the real
571
+ one (props, context, or a mocked module):
572
+
573
+ ```ts
574
+ import { render } from '@testing-library/svelte';
575
+ import { createSpyFromClass } from 'vitest-auto-spy/svelte';
576
+ import Cart from './Cart.svelte';
577
+ import { CartStore } from './cart-store';
578
+
579
+ it('shows the cart total from the store', () => {
580
+ const cartStore = createSpyFromClass(CartStore); // every method is a spy
581
+
582
+ cartStore.total.mockReturnValue(42);
583
+ cartStore.priceOf.calledWith('apple').mockReturnValue(7);
584
+
585
+ render(Cart, { props: { store: cartStore } });
586
+
587
+ expect(cartStore.total).toHaveBeenCalled();
588
+ });
589
+ ```
590
+
591
+ ### Angular
250
592
 
251
593
  `provideAutoSpy` is the shorthand for providing an auto-spy in a `TestBed`:
252
594
 
@@ -274,7 +616,7 @@ beforeEach(() => {
274
616
  > [`@analogjs/vite-plugin-angular`](https://www.npmjs.com/package/@analogjs/vite-plugin-angular)
275
617
  > plus a TestBed setup file (e.g. `@analogjs/vitest-angular`'s `setupTestBed()`).
276
618
 
277
- ### Signal / readonly property mocking (bonus)
619
+ #### Signal / readonly property mocking (bonus)
278
620
 
279
621
  ```ts
280
622
  import { mockReadonlyProp, mockReadonlyPropGetter, mockAccessorsProp } from 'vitest-auto-spy/angular';
@@ -289,8 +631,10 @@ mockAccessorsProp(service, 'theme'); // spied get + set
289
631
  | Export | Description |
290
632
  | --- | --- |
291
633
  | `createSpyFromClass(Class, methodsOrConfig?)` | Build a fully-typed `Spy<T>` from a class |
292
- | `provideAutoSpy(Class, methodsOrConfig?)` | Angular `{ provide, useValue }` shorthand |
293
- | `injectSpy(token)` | `TestBed.inject` typed as `Spy<T>` |
634
+ | `createAutoMock<T>(overrides?)` | Build a `Spy<T>` from a **type/interface** alone (Proxy, no class) |
635
+ | `provideAutoSpy(Class, methodsOrConfig?)` | Angular / NestJS `{ provide, useValue }` shorthand |
636
+ | `provideAutoSpy(token, Class, methodsOrConfig?)` | Vue `{ [token]: Spy<T> }` for `global.provide` |
637
+ | `injectSpy(token)` _(Angular)_ / `injectSpy(moduleRef, token)` _(NestJS)_ | Inject typed as `Spy<T>` |
294
638
  | `createFunctionSpy(name)` | A single standalone function spy with all helpers |
295
639
  | `createObservableWithValues(configs, opts?)` | Build an Observable from value configs |
296
640
  | `mockReadonlyProp` / `mockReadonlyPropGetter` / `mockAccessorsProp` | Mock readonly / accessor / signal props |
@@ -308,6 +652,38 @@ mockAccessorsProp(service, 'theme'); // spied get + set
308
652
 
309
653
  `ValueConfig` (for `nextWithValues`): `{ value, delay? }` | `{ errorValue, delay? }` | `{ complete?, delay? }`.
310
654
 
655
+ ## FAQ & troubleshooting
656
+
657
+ **"I get `X.nextWith is not a function` / observable helpers are missing."**
658
+ Import the rxjs layer once (e.g. in your test setup): `import 'vitest-auto-spy/rxjs';`. Without it,
659
+ requesting an observable spy throws a hint pointing you here.
660
+
661
+ **"My method isn't on the spy."**
662
+ Auto-discovery only sees **prototype methods**. Arrow-function class fields (`foo = () => {}`) and
663
+ plain properties aren't included — see [How it works](#how-it-works-and-what-it-wont-spy). List
664
+ getters/setters via `gettersToSpyOn` / `settersToSpyOn`.
665
+
666
+ **"Does it construct my class? Will the constructor's side effects run?"**
667
+ No. `createSpyFromClass` reads the prototype and never `new`s the class, so constructors (and their
668
+ HTTP/DB/`inject()` side effects) never run.
669
+
670
+ **"I only have an interface/type, not a class."**
671
+ Use [`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed) — it builds the spy lazily from the
672
+ type via a `Proxy`, no runtime class needed.
673
+
674
+ **"Can I use it without TypeScript?"**
675
+ Yes — the runtime works in plain JS; you just lose the compile-time `Spy<T>` typing.
676
+
677
+ **"Native mock methods differ between runners."**
678
+ Only the auto-spy helpers are normalised. Native APIs stay the runner's own (`mockReturnValue` on
679
+ Vitest/Bun, `spy.method.mock.mockImplementation` on `node:test`).
680
+
681
+ ## Versioning
682
+
683
+ This package follows [Semantic Versioning](https://semver.org). Breaking changes to the public API
684
+ land only in major releases; see the [Changelog](./CHANGELOG.md) for what changed in each version.
685
+ Releases are automated from Conventional Commits (see [Contributing](#contributing)).
686
+
311
687
  ## Contributing
312
688
 
313
689
  Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) and the
@@ -322,11 +698,18 @@ npm run build
322
698
 
323
699
  Releases are automated: merging a PR into `master` bumps the version from the
324
700
  Conventional Commit types and publishes to npm — see
325
- [CONTRIBUTING.md → Releases](./CONTRIBUTING.md#releases).
701
+ [CONTRIBUTING.md → Releasing](./CONTRIBUTING.md#releasing).
326
702
 
327
703
  If this package saved you time, a ⭐ on [GitHub](https://github.com/ASDAlexey/vitest-auto-spy)
328
704
  helps others find it.
329
705
 
706
+ ## Acknowledgements
707
+
708
+ API and ergonomics are modelled on Shai Reznik's
709
+ [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — `vitest-auto-spy` is its
710
+ Vitest-era successor with the same surface, so migrations are (mostly) a find-and-replace. Thanks to
711
+ the Vitest, Bun, RxJS and Angular communities whose tooling this builds on.
712
+
330
713
  ## License
331
714
 
332
715
  [MIT](./LICENSE) © [Alexey Popov](https://github.com/ASDAlexey)
package/dist/angular.cjs CHANGED
@@ -1 +1 @@
1
- 'use strict';var testing=require('@angular/core/testing'),vitest=require('vitest');function g(e,t){Object.defineProperty(e,t,{get(){},set(r){},configurable:true});}function T(e,t,r){let n=t;return r==="setter"?vitest.vi.spyOn(e,n,"set"):vitest.vi.spyOn(e,n,"get")}function b(e,t,r){let n={getters:{},setters:{}};e.accessorSpies=n,t.forEach(o=>{g(e,o),n.getters[o]=T(e,o,"getter");}),r.forEach(o=>{Object.prototype.hasOwnProperty.call(e,o)||g(e,o),n.setters[o]=T(e,o,"setter");});}function A(e){return `'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function M(e,t){return Object.entries(e).map(([r,n])=>`${r}:${i(n,t)}`).join(",")}function W(e,t){return `new Map([${[...e.entries()].map(([n,o])=>`[${i(n,t)},${i(o,t)}]`).join(",")}])`}function F(e,t){if(t.has(e))return "[Circular]";t.add(e);let r;return e instanceof Date?r=`new Date(${e.getTime()})`:e instanceof Map?r=W(e,t):e instanceof Set?r=`new Set([${[...e].map(n=>i(n,t)).join(",")}])`:Array.isArray(e)?r=`[${e.map(n=>i(n,t)).join(",")}]`:r=`{${M(e,t)}}`,t.delete(e),r}function i(e,t=new WeakSet){return typeof e=="string"?A(e):typeof e=="bigint"?`${e}n`:typeof e=="symbol"?e.toString():typeof e=="function"?`[Function: ${e.name}]`:typeof e=="object"&&e!==null?F(e,t):Object.is(e,-0)?"-0":String(e)}var l=class{#e={};set(t,r){this.#e[this.#t(t)]=r;}get(t){return this.#e[this.#t(t)]}#t(t){return i(t)}};var E=e=>`The function '${e}' was configured with 'mustBeCalledWith' and expects to be called with specific arguments. `,_="But the function was called without any arguments.",$=e=>{let t=i(e);return `But the actual arguments were: ${t.substring(1,t.length-1)}`},m={throwArgumentsError(e,t){let r=e.length===0?_:$(e);throw new Error(E(t)+r)}};var H="Observable spies require rxjs. Import 'vitest-auto-spy/rxjs' once (e.g. in your test setup) to enable observablePropsToSpyOn / nextWith / nextWithValues / throwWith / complete / returnSubject.";function S(){throw new Error(H);}function a(e,t){return Object.assign(e,t)}function K(e){return e.map(t=>({wrappedValue:t.delay===void 0?Promise.resolve(t.value):new Promise(r=>setTimeout(()=>r(t.value),t.delay))}))}function O(e,t){a(e,{resolveWith:r=>{t({value:Promise.resolve(r)});},rejectWith:r=>{t({value:r,_isRejectedPromise:true});},resolveWithPerCall:r=>{r.length!==0&&t({value:void 0,valuesPerCalls:K(r)});}});}function C(e,t){O(e,r=>{t.value=r.value,t._isRejectedPromise=r._isRejectedPromise??false,t.valuesPerCalls=r.valuesPerCalls??[];});}function j(e,t){O(e,r=>{e.argsToValuesMap.set(t,r);});}function B(e){return typeof e=="object"&&e!==null&&"value"in e}function v(e){if(e._isRejectedPromise)return Promise.reject(e.value);let t=e.valuesPerCalls?.shift();return t?t.wrappedValue:e.value}function h(e,t){let r=e.argsToValuesMap.get(t);return B(r)?{found:true,value:v(r)}:{found:false,value:void 0}}function I(e,t,r,n,o){if(e.wasConfigured){let s=h(e,n);if(s.found)return s.value}if(t.wasConfigured){let s=h(t,n);if(s.found)return s.value;m.throwArgumentsError(n,o);}return v(r)}function w(e,t){return e.wasConfigured=true,a(e,{mockReturnValue:r=>{e.argsToValuesMap.set(t,{value:r});}}),j(e,t),e}function k(){return {wasConfigured:false,argsToValuesMap:new l}}function P(e){let t=k(),r=k(),n={value:void 0},o=vitest.vi.fn((...u)=>I(t,r,n,u,e));o.mockName(e),C(o,n);let s=a(o,{calledWith:(...u)=>w(t,u),mustBeCalledWith:(...u)=>w(r,u)});return s}var R={methodsToSpyOn:[],observablePropsToSpyOn:[],settersToSpyOn:[],gettersToSpyOn:[]};function G(e){let t=Object.getOwnPropertyDescriptors(e);return Object.keys(t).filter(r=>r!=="constructor"&&!t[r]?.get)}function q(e){let t=new Set,r=e;for(;r;){let n=Object.getPrototypeOf(r);n&&G(r).forEach(o=>t.add(o)),r=n;}return [...t]}function D(e){return e?Array.isArray(e)?{...R,methodsToSpyOn:e}:{methodsToSpyOn:e.methodsToSpyOn??[],observablePropsToSpyOn:e.observablePropsToSpyOn??[],settersToSpyOn:e.settersToSpyOn??[],gettersToSpyOn:e.gettersToSpyOn??[]}:{...R}}function x(e,t){let{methodsToSpyOn:r,observablePropsToSpyOn:n,settersToSpyOn:o,gettersToSpyOn:s}=D(t),u=r.length>0?r:q(e.prototype),p={};return n.forEach(c=>{p[c]=S().createPropSpy();}),b(p,s,o),u.forEach(c=>{p[c]=P(c);}),p}function L(e,t){return {provide:e,useValue:x(e,t)}}function N(e){return testing.TestBed.inject(e)}function J(e,t,r){Object.defineProperty(e,t,{get:()=>r,configurable:true});}function X(e,t,r){Object.defineProperty(e,t,{get:r,configurable:true});}function Y(e,t){Object.defineProperty(e,t,{get:vitest.vi.fn(),set:vitest.vi.fn(),configurable:true});}exports.injectSpy=N;exports.mockAccessorsProp=Y;exports.mockReadonlyProp=J;exports.mockReadonlyPropGetter=X;exports.provideAutoSpy=L;
1
+ 'use strict';var vitest=require('vitest'),testing=require('@angular/core/testing');var y;function b(e){y=e;}var W="No mock adapter registered. Import a runtime entry once before creating spies \u2014 'vitest-auto-spy' (default, Vitest) or a runtime variant such as 'vitest-auto-spy/bun'.";function p(){if(!y)throw new Error(W);return y}var k={createMockFn(e,t){let r=e?vitest.vi.fn(e):vitest.vi.fn();return t!==void 0&&r.mockName(t),r},spyOnGetter(e,t){return vitest.vi.spyOn(e,t,"get")},spyOnSetter(e,t){return vitest.vi.spyOn(e,t,"set")},getCalls(e){return e.mock.calls},reset(e){e.mockReset();}};function T(e,t){Object.defineProperty(e,t,{get(){},set(r){},configurable:true});}function S(e,t,r){let n=p();return r==="setter"?n.spyOnSetter(e,t):n.spyOnGetter(e,t)}function O(e,t,r){let n={getters:{},setters:{}};e.accessorSpies=n,t.forEach(o=>{T(e,o),n.getters[o]=S(e,o,"getter");}),r.forEach(o=>{Object.prototype.hasOwnProperty.call(e,o)||T(e,o),n.setters[o]=S(e,o,"setter");});}function E(e){return `'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function _(e,t){return Object.entries(e).map(([r,n])=>`${r}:${i(n,t)}`).join(",")}function $(e,t){return `new Map([${[...e.entries()].map(([n,o])=>`[${i(n,t)},${i(o,t)}]`).join(",")}])`}function H(e,t){if(t.has(e))return "[Circular]";t.add(e);let r;return e instanceof Date?r=`new Date(${e.getTime()})`:e instanceof Map?r=$(e,t):e instanceof Set?r=`new Set([${[...e].map(n=>i(n,t)).join(",")}])`:Array.isArray(e)?r=`[${e.map(n=>i(n,t)).join(",")}]`:r=`{${_(e,t)}}`,t.delete(e),r}function i(e,t=new WeakSet){return typeof e=="string"?E(e):typeof e=="bigint"?`${e}n`:typeof e=="symbol"?e.toString():typeof e=="function"?`[Function: ${e.name}]`:typeof e=="object"&&e!==null?H(e,t):Object.is(e,-0)?"-0":String(e)}var f=class{#e={};set(t,r){this.#e[this.#t(t)]=r;}get(t){return this.#e[this.#t(t)]}#t(t){return i(t)}};var K=e=>`The function '${e}' was configured with 'mustBeCalledWith' and expects to be called with specific arguments. `,z="But the function was called without any arguments.",B=e=>{let t=i(e);return `But the actual arguments were: ${t.substring(1,t.length-1)}`},j={throwArgumentsError(e,t){let r=e.length===0?z:B(e);throw new Error(K(t)+r)}};var G="Observable spies require rxjs. Import 'vitest-auto-spy/rxjs' once (e.g. in your test setup) to enable observablePropsToSpyOn / nextWith / nextWithValues / throwWith / complete / returnSubject.";function C(){throw new Error(G);}function a(e,t){return Object.assign(e,t)}function I(e){return e.map(t=>({wrappedValue:t.delay===void 0?Promise.resolve(t.value):new Promise(r=>setTimeout(()=>r(t.value),t.delay))}))}function w(e,t){a(e,{resolveWith:r=>{t({value:Promise.resolve(r)});},rejectWith:r=>{t({value:r,_isRejectedPromise:true});},resolveWithPerCall:r=>{r.length!==0&&t({value:void 0,valuesPerCalls:I(r)});}});}function h(e,t){w(e,r=>{t.value=r.value,t._isRejectedPromise=r._isRejectedPromise??false,t.valuesPerCalls=r.valuesPerCalls??[];});}function M(e,t){w(e,r=>{e.argsToValuesMap.set(t,r);});}function D(e){return typeof e=="object"&&e!==null&&"value"in e}function F(e){if(e._isRejectedPromise)return Promise.reject(e.value);let t=e.valuesPerCalls?.shift();return t?t.wrappedValue:e.value}function v(e,t){let r=e.argsToValuesMap.get(t);return D(r)?{found:true,value:F(r)}:{found:false,value:void 0}}function q(e,t,r,n,o){if(e.wasConfigured){let s=v(e,n);if(s.found)return s.value}if(t.wasConfigured){let s=v(t,n);if(s.found)return s.value;j.throwArgumentsError(n,o);}return F(r)}function P(e,t){return e.wasConfigured=true,a(e,{mockReturnValue:r=>{e.argsToValuesMap.set(t,{value:r});}}),M(e,t),e}function A(){return {wasConfigured:false,argsToValuesMap:new f}}function R(e){let t=A(),r=A(),n={value:void 0},o=p().createMockFn((...c)=>q(t,r,n,c,e),e);h(o,n);let s=a(o,{calledWith:(...c)=>P(t,c),mustBeCalledWith:(...c)=>P(r,c)});return s}var x={methodsToSpyOn:[],observablePropsToSpyOn:[],settersToSpyOn:[],gettersToSpyOn:[]};function U(e){let t=Object.getOwnPropertyDescriptors(e);return Object.keys(t).filter(r=>r!=="constructor"&&!t[r]?.get)}function L(e){let t=new Set,r=e;for(;r;){let n=Object.getPrototypeOf(r);n&&U(r).forEach(o=>t.add(o)),r=n;}return [...t]}function N(e){return e?Array.isArray(e)?{...x,methodsToSpyOn:e}:{methodsToSpyOn:e.methodsToSpyOn??[],observablePropsToSpyOn:e.observablePropsToSpyOn??[],settersToSpyOn:e.settersToSpyOn??[],gettersToSpyOn:e.gettersToSpyOn??[]}:{...x}}function V(e,t){let{methodsToSpyOn:r,observablePropsToSpyOn:n,settersToSpyOn:o,gettersToSpyOn:s}=N(t),c=r.length>0?r:L(e.prototype),u={};return n.forEach(l=>{u[l]=C().createPropSpy();}),O(u,s,o),c.forEach(l=>{u[l]=R(l);}),u}function X(e,t){return {provide:e,useValue:V(e,t)}}function Y(e){return testing.TestBed.inject(e)}function Q(e,t,r){Object.defineProperty(e,t,{get:()=>r,configurable:true});}function Z(e,t,r){Object.defineProperty(e,t,{get:r,configurable:true});}function ee(e,t){let r=p();Object.defineProperty(e,t,{get:r.createMockFn(),set:r.createMockFn(),configurable:true});}b(k);exports.injectSpy=Y;exports.mockAccessorsProp=ee;exports.mockReadonlyProp=Q;exports.mockReadonlyPropGetter=Z;exports.provideAutoSpy=X;
@@ -1,4 +1,4 @@
1
- import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-Cg9eZmvm.cjs';
1
+ import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-C84-zP_V.cjs';
2
2
  import 'rxjs';
3
3
  import 'vitest';
4
4
 
@@ -15,7 +15,7 @@ declare function injectSpy<T>(token: ClassType<T> | (abstract new (...args: neve
15
15
  declare function mockReadonlyProp<T, K extends keyof T>(object: T, property: K, value: T[K]): void;
16
16
  /** Override a readonly property with a dynamic getter. */
17
17
  declare function mockReadonlyPropGetter<T, K extends keyof T>(object: T, property: K, getter: () => unknown): void;
18
- /** Replace a property with spied `get`/`set` accessors (`vi.fn()`). */
18
+ /** Replace a property with spied `get`/`set` accessors (host-runner mocks). */
19
19
  declare function mockAccessorsProp<T, K extends keyof T>(object: T, property: K): void;
20
20
 
21
21
  export { type AngularValueProvider, injectSpy, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, provideAutoSpy };
package/dist/angular.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-Cg9eZmvm.js';
1
+ import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-C84-zP_V.js';
2
2
  import 'rxjs';
3
3
  import 'vitest';
4
4
 
@@ -15,7 +15,7 @@ declare function injectSpy<T>(token: ClassType<T> | (abstract new (...args: neve
15
15
  declare function mockReadonlyProp<T, K extends keyof T>(object: T, property: K, value: T[K]): void;
16
16
  /** Override a readonly property with a dynamic getter. */
17
17
  declare function mockReadonlyPropGetter<T, K extends keyof T>(object: T, property: K, getter: () => unknown): void;
18
- /** Replace a property with spied `get`/`set` accessors (`vi.fn()`). */
18
+ /** Replace a property with spied `get`/`set` accessors (host-runner mocks). */
19
19
  declare function mockAccessorsProp<T, K extends keyof T>(object: T, property: K): void;
20
20
 
21
21
  export { type AngularValueProvider, injectSpy, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, provideAutoSpy };
package/dist/angular.js CHANGED
@@ -1 +1 @@
1
- import {c as c$1}from'./chunk-6S2BI3IA.js';import'./chunk-7IUYJ6ZW.js';import {TestBed}from'@angular/core/testing';import {vi}from'vitest';function i(e,o){return {provide:e,useValue:c$1(e,o)}}function s(e){return TestBed.inject(e)}function y(e,o,r){Object.defineProperty(e,o,{get:()=>r,configurable:true});}function a(e,o,r){Object.defineProperty(e,o,{get:r,configurable:true});}function c(e,o){Object.defineProperty(e,o,{get:vi.fn(),set:vi.fn(),configurable:true});}export{s as injectSpy,c as mockAccessorsProp,y as mockReadonlyProp,a as mockReadonlyPropGetter,i as provideAutoSpy};
1
+ import {a as a$1}from'./chunk-6LFQMHSX.js';import {a,e,b}from'./chunk-VLNCYBUN.js';import'./chunk-7IUYJ6ZW.js';import {TestBed}from'@angular/core/testing';function c(e$1,o){return {provide:e$1,useValue:e(e$1,o)}}function s(e){return TestBed.inject(e)}function y(e,o,r){Object.defineProperty(e,o,{get:()=>r,configurable:true});}function d(e,o,r){Object.defineProperty(e,o,{get:r,configurable:true});}function u(e,o){let r=b();Object.defineProperty(e,o,{get:r.createMockFn(),set:r.createMockFn(),configurable:true});}a(a$1);export{s as injectSpy,u as mockAccessorsProp,y as mockReadonlyProp,d as mockReadonlyPropGetter,c as provideAutoSpy};
package/dist/bun.cjs ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var bun_test=require('bun:test');function S({createMockFn:e,getCalls:t,reset:r}){return {createMockFn:e,spyOnGetter:(o,n)=>b(e,o,n,"get"),spyOnSetter:(o,n)=>b(e,o,n,"set"),getCalls:t,reset:r}}function b(e,t,r,o){let n=Object.getOwnPropertyDescriptor(t,r),s=o==="get"?n?.get:n?.set,i=e(s),c={configurable:true,enumerable:n?.enumerable??true};return n?.get&&(c.get=n.get),n?.set&&(c.set=n.set),o==="get"?c.get=i:c.set=i,Object.defineProperty(t,r,c),i}function w(e){return S({createMockFn:(r,o)=>{let n=e.mock(r??(()=>{}));return o!==void 0&&n.mockName&&n.mockName(o),n},getCalls:r=>r.mock.calls,reset:r=>r.mockReset()})}var y;function M(e){y=e;}var W="No mock adapter registered. Import a runtime entry once before creating spies \u2014 'vitest-auto-spy' (default, Vitest) or a runtime variant such as 'vitest-auto-spy/bun'.";function d(){if(!y)throw new Error(W);return y}function O(e,t){Object.defineProperty(e,t,{get(){},set(r){},configurable:true});}function T(e,t,r){let o=d();return r==="setter"?o.spyOnSetter(e,t):o.spyOnGetter(e,t)}function h(e,t,r){let o={getters:{},setters:{}};e.accessorSpies=o,t.forEach(n=>{O(e,n),o.getters[n]=T(e,n,"getter");}),r.forEach(n=>{Object.prototype.hasOwnProperty.call(e,n)||O(e,n),o.setters[n]=T(e,n,"setter");});}function E(e){return `'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function _(e,t){return Object.entries(e).map(([r,o])=>`${r}:${a(o,t)}`).join(",")}function B(e,t){return `new Map([${[...e.entries()].map(([o,n])=>`[${a(o,t)},${a(n,t)}]`).join(",")}])`}function H(e,t){if(t.has(e))return "[Circular]";t.add(e);let r;return e instanceof Date?r=`new Date(${e.getTime()})`:e instanceof Map?r=B(e,t):e instanceof Set?r=`new Set([${[...e].map(o=>a(o,t)).join(",")}])`:Array.isArray(e)?r=`[${e.map(o=>a(o,t)).join(",")}]`:r=`{${_(e,t)}}`,t.delete(e),r}function a(e,t=new WeakSet){return typeof e=="string"?E(e):typeof e=="bigint"?`${e}n`:typeof e=="symbol"?e.toString():typeof e=="function"?`[Function: ${e.name}]`:typeof e=="object"&&e!==null?H(e,t):Object.is(e,-0)?"-0":String(e)}var f=class{#e={};set(t,r){this.#e[this.#t(t)]=r;}get(t){return this.#e[this.#t(t)]}#t(t){return a(t)}};var $=e=>`The function '${e}' was configured with 'mustBeCalledWith' and expects to be called with specific arguments. `,z="But the function was called without any arguments.",D=e=>{let t=a(e);return `But the actual arguments were: ${t.substring(1,t.length-1)}`},m={throwArgumentsError(e,t){let r=e.length===0?z:D(e);throw new Error($(t)+r)}};var I="Observable spies require rxjs. Import 'vitest-auto-spy/rxjs' once (e.g. in your test setup) to enable observablePropsToSpyOn / nextWith / nextWithValues / throwWith / complete / returnSubject.";function C(){throw new Error(I);}function u(e,t){return Object.assign(e,t)}function G(e){return e.map(t=>({wrappedValue:t.delay===void 0?Promise.resolve(t.value):new Promise(r=>setTimeout(()=>r(t.value),t.delay))}))}function j(e,t){u(e,{resolveWith:r=>{t({value:Promise.resolve(r)});},rejectWith:r=>{t({value:r,_isRejectedPromise:true});},resolveWithPerCall:r=>{r.length!==0&&t({value:void 0,valuesPerCalls:G(r)});}});}function F(e,t){j(e,r=>{t.value=r.value,t._isRejectedPromise=r._isRejectedPromise??false,t.valuesPerCalls=r.valuesPerCalls??[];});}function A(e,t){j(e,r=>{e.argsToValuesMap.set(t,r);});}function K(e){return typeof e=="object"&&e!==null&&"value"in e}function x(e){if(e._isRejectedPromise)return Promise.reject(e.value);let t=e.valuesPerCalls?.shift();return t?t.wrappedValue:e.value}function P(e,t){let r=e.argsToValuesMap.get(t);return K(r)?{found:true,value:x(r)}:{found:false,value:void 0}}function N(e,t,r,o,n){if(e.wasConfigured){let s=P(e,o);if(s.found)return s.value}if(t.wasConfigured){let s=P(t,o);if(s.found)return s.value;m.throwArgumentsError(o,n);}return x(r)}function v(e,t){return e.wasConfigured=true,u(e,{mockReturnValue:r=>{e.argsToValuesMap.set(t,{value:r});}}),A(e,t),e}function R(){return {wasConfigured:false,argsToValuesMap:new f}}function p(e){let t=R(),r=R(),o={value:void 0},n=d().createMockFn((...i)=>N(t,r,o,i,e),e);F(n,o);let s=u(n,{calledWith:(...i)=>v(t,i),mustBeCalledWith:(...i)=>v(r,i)});return s}var V={methodsToSpyOn:[],observablePropsToSpyOn:[],settersToSpyOn:[],gettersToSpyOn:[]};function q(e){let t=Object.getOwnPropertyDescriptors(e);return Object.keys(t).filter(r=>r!=="constructor"&&!t[r]?.get)}function U(e){let t=new Set,r=e;for(;r;){let o=Object.getPrototypeOf(r);o&&q(r).forEach(n=>t.add(n)),r=o;}return [...t]}function L(e){return e?Array.isArray(e)?{...V,methodsToSpyOn:e}:{methodsToSpyOn:e.methodsToSpyOn??[],observablePropsToSpyOn:e.observablePropsToSpyOn??[],settersToSpyOn:e.settersToSpyOn??[],gettersToSpyOn:e.gettersToSpyOn??[]}:{...V}}function J(e,t){let{methodsToSpyOn:r,observablePropsToSpyOn:o,settersToSpyOn:n,gettersToSpyOn:s}=L(t),i=r.length>0?r:U(e.prototype),c={};return o.forEach(l=>{c[l]=C().createPropSpy();}),h(c,s,n),i.forEach(l=>{c[l]=p(l);}),c}function X(e={}){let t=new Map;for(let o of Reflect.ownKeys(e))t.set(o,Reflect.get(e,o));let r={get(o,n){if(t.has(n))return t.get(n);if(typeof n=="symbol"||n==="then"||n==="constructor")return;let s=p(String(n));return t.set(n,s),s},set(o,n,s){return t.set(n,s),true},has(o,n){return t.has(n)},ownKeys(){return [...t.keys()]},getOwnPropertyDescriptor(o,n){if(t.has(n))return {configurable:true,enumerable:true,value:t.get(n),writable:true}}};return new Proxy({},r)}M(w({mock:bun_test.mock}));exports.createAutoMock=X;exports.createFunctionSpy=p;exports.createSpyFromClass=J;exports.errorHandler=m;