vitest-auto-spy 1.3.0 → 1.5.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`). Runs on **Vitest**,
9
+ **Bun** (`bun:test`) and **`node:test`** behind one identical API, with **RxJS** spies and
10
+ **Angular / NestJS / React / Vue·Pinia / Svelte** recipes ([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-✓-6E9F18?logo=bun&logoColor=white)](#availability)
23
+ [![node:test](https://img.shields.io/badge/node%3Atest-✓-6E9F18?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**, **Bun** and **`node:test`**, identical API on each
35
+ - 🧩 Framework recipes: **Angular**, **NestJS**, **React**, **Vue/Pinia** and **Svelte**
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,76 @@ 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
+ > **All entry points are published.** As of `vitest-auto-spy@1.4.0`, the **Vitest / Bun / `node:test`**
101
+ > runtimes, the **RxJS** layer, and the **Angular / NestJS / React / Vue·Pinia / Svelte** recipes all
102
+ > ship as importable entry points — one identical API across every runner and framework.
103
+
104
+ | Entry point | Status |
105
+ | --- | --- |
106
+ | `vitest-auto-spy` · `vitest-auto-spy/rxjs` · `vitest-auto-spy/angular` | ✅ **Published** (1.4.0) |
107
+ | `vitest-auto-spy/bun` · `vitest-auto-spy/node` | ✅ **Published** (1.4.0) |
108
+ | `vitest-auto-spy/nestjs` · `/react` · `/vue` · `/svelte` | ✅ **Published** (1.4.0) |
109
+
110
+ ## Quick start
111
+
112
+ Pass a class — every method becomes a typed spy, and the **constructor is never called** (no side
113
+ effects). The helper you get on each method matches its return type:
67
114
 
68
115
  ```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';
116
+ import { beforeEach, expect, it } from 'vitest';
117
+ import { createSpyFromClass, type Spy } from 'vitest-auto-spy';
118
+
119
+ class UserService {
120
+ getName(id: number): string {
121
+ return 'real name';
122
+ }
123
+ async getUser(id: number): Promise<{ id: number; name: string }> {
124
+ return fetchUser(id);
125
+ }
126
+ }
127
+
128
+ let userService: Spy<UserService>;
129
+
130
+ beforeEach(() => {
131
+ userService = createSpyFromClass(UserService); // every method is now a spy
132
+ });
133
+
134
+ it('stubs each method with the right helper for its return type', async () => {
135
+ userService.getName.mockReturnValue('Ada'); // sync
136
+ userService.getUser.resolveWith({ id: 1, name: 'Ada' }); // Promise helper
137
+
138
+ expect(userService.getName(1)).toBe('Ada');
139
+ await expect(userService.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
140
+ expect(userService.getName).toHaveBeenCalledWith(1);
141
+ });
72
142
  ```
73
143
 
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.
144
+ No class, only a TypeScript type? Reach for
145
+ [`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed).
80
146
 
81
147
  ## Why
82
148
 
@@ -105,6 +171,100 @@ beforeEach(() => {
105
171
  `Spy<UserService>` exposes each method as a `vi.fn()` **plus** the right helpers based on
106
172
  the method's return type (sync / `Promise` / `Observable`).
107
173
 
174
+ ## How it works (and what it won't spy)
175
+
176
+ `createSpyFromClass(MyService)` reads `MyService.prototype` and walks the **prototype chain** — it
177
+ never `new`s the class. Concretely:
178
+
179
+ - ✅ **The class is never instantiated.** The constructor and its side effects (HTTP clients, DB
180
+ connections, `inject()` calls) never run — you pass the class itself, not an instance.
181
+ - ✅ **Inherited methods are spied too**, all the way up the prototype chain.
182
+ - ✅ Each method is replaced by a fresh spy carrying the helpers that match its **return type**:
183
+ sync → `mockReturnValue` / `calledWith`; `Promise` → `resolveWith` / `rejectWith`; `Observable`
184
+ → `nextWith` / `throwWith` / … .
185
+
186
+ What it **won't** auto-discover — by design, because these aren't prototype methods:
187
+
188
+ - ⚠️ **Arrow-function class fields** (`doThing = () => {}`) are instance properties set in the
189
+ constructor, so prototype scanning can't see them. Use regular methods, list them explicitly, or
190
+ mock them by hand. (Same constraint as `jest-auto-spies`.)
191
+ - ⚠️ **Getters / setters** are skipped unless named in `gettersToSpyOn` / `settersToSpyOn` — see
192
+ [Getters & setters](#getters--setters).
193
+ - ⚠️ **Plain data properties** carry no value until you set one; auto-spy mocks *behaviour*
194
+ (methods), not state. To mock by type including properties, use
195
+ [`createAutoMock`](#auto-mock-by-type-no-class-needed).
196
+
197
+ ## Entry points & runtimes
198
+
199
+ The library ships a framework-agnostic core plus runtime and framework layers, so a plain
200
+ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
201
+
202
+ | Import | Provides | Pulls in | Status |
203
+ | --- | --- | --- | :---: |
204
+ | `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
205
+ | `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) + `createObservableWithValues` | `rxjs` | ✅ |
206
+ | `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `mockReadonlyProp*`, `mockAccessorsProp` | `@angular/core` | ✅ |
207
+ | `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
208
+ | `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()` | `node:test` | ✅ |
209
+ | `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`) | ✅ |
210
+ | `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | ✅ |
211
+ | `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | ✅ |
212
+ | `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | ✅ |
213
+
214
+ ✅ all entry points published in 1.4.0 (see [Availability](#availability)).
215
+
216
+ > The framework subpaths import **nothing** from their framework — the helpers are structural, so
217
+ > `@nestjs/*`, `react`, `vue`/`pinia` and `svelte` stay your own (already-present) dev dependencies and
218
+ > never reach this package's runtime bundle.
219
+
220
+ ```ts
221
+ import { createSpyFromClass } from 'vitest-auto-spy';
222
+ import 'vitest-auto-spy/rxjs'; // once (e.g. in your test setup) — enables observable spies
223
+ import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/angular';
224
+ ```
225
+
226
+ ### Runtimes
227
+
228
+ The core is runner-agnostic behind a `MockAdapter`: pick the entry that matches your test
229
+ runner — the public API (`createSpyFromClass`, `calledWith`, `resolveWith`, `nextWith`, …) is
230
+ identical across all three.
231
+
232
+ ```ts
233
+ import { createSpyFromClass } from 'vitest-auto-spy'; // Vitest (default, zero-config)
234
+ import { createSpyFromClass } from 'vitest-auto-spy/bun'; // Bun — bun:test
235
+ import { createSpyFromClass } from 'vitest-auto-spy/node'; // node:test
236
+ ```
237
+
238
+ > Only the auto-spy helpers are normalised across runtimes; **native** mock methods stay the
239
+ > runner's own — `mockReturnValue` on Vitest/Bun, `spy.method.mock.mockImplementation` on
240
+ > `node:test`. Each entry registers its adapter on import, so import the one matching your runner.
241
+
242
+ > Using an observable spy (`observablePropsToSpyOn`, `nextWith`, …) without importing
243
+ > `vitest-auto-spy/rxjs` throws a clear hint telling you to add that import.
244
+ >
245
+ > The decoupling is at the **runtime** level. The core's _type_ surface (`Spy<T>`) still
246
+ > references rxjs types, so keep `rxjs` available for type-checking (it's normally already a
247
+ > devDependency); none of it reaches your runtime bundle.
248
+ >
249
+ > The same inversion-of-control applies to the **test runner**: the core no longer imports
250
+ > `vitest` directly — `vi.fn()` / `vi.spyOn()` sit behind a `MockAdapter` that the
251
+ > `vitest-auto-spy` entry registers by default, so it stays zero-config. This is the groundwork
252
+ > for running the exact same core on other Vitest-compatible runners.
253
+
254
+ ## Comparison
255
+
256
+ | Library | Reads a class? | Return-type-aware helpers? | Runtimes | We win on |
257
+ | --- | :---: | :---: | --- | --- |
258
+ | **vitest-auto-spy** | ✅ | ✅ | Vitest (Bun · node:test next) | — |
259
+ | [jest-auto-spies](https://www.npmjs.com/package/jest-auto-spies) | ✅ | ✅ | Jest only | Vitest/Bun/Node successor, **same API** — direct migration path |
260
+ | [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)) |
261
+ | [@golevelup/ts-vitest](https://www.npmjs.com/package/@golevelup/ts-vitest) | partial | ❌ | Vitest | Typed `Promise`/`Observable` helpers + explicit class→spy + `mustBeCalledWith` |
262
+ | [sinon](https://www.npmjs.com/package/sinon) | ❌ (manual) | ❌ | Any | Auto-generated + fully typed vs. manual + loosely typed |
263
+
264
+ **The pitch:** the only auto-spy library that reads a **class** and gives a **fully-typed** spy of
265
+ every method with **return-type-aware** control helpers (`resolveWith` / `nextWith` / `calledWith`) —
266
+ across any Vitest-compatible runtime and framework.
267
+
108
268
  ## Migrating from jest-auto-spies
109
269
 
110
270
  The public API is intentionally identical. In most projects the migration is a
@@ -118,7 +278,7 @@ The public API is intentionally identical. In most projects the migration is a
118
278
  ```
119
279
 
120
280
  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)).
281
+ observable layer live behind the `/angular` and `/rxjs` subpaths (see [Entry points & runtimes](#entry-points--runtimes)).
122
282
 
123
283
  | jest-auto-spies | vitest-auto-spy | Status |
124
284
  | --- | --- | --- |
@@ -152,6 +312,50 @@ createSpyFromClass(MyService, {
152
312
  });
153
313
  ```
154
314
 
315
+ ## Auto-mock by type (no class needed)
316
+
317
+ `createSpyFromClass` reads a real class's prototype. When you only have a TypeScript **interface or
318
+ type** (no runtime class), use `createAutoMock<T>()` — it builds the spy lazily from the type alone,
319
+ via a `Proxy`:
320
+
321
+ ```ts
322
+ import { createAutoMock } from 'vitest-auto-spy';
323
+
324
+ interface UserService {
325
+ getName(id: number): string;
326
+ getUser(id: number): Promise<User>;
327
+ apiUrl: string;
328
+ }
329
+
330
+ // Before — needs a concrete class:
331
+ // const svc = createSpyFromClass(UserServiceClass);
332
+
333
+ // After — type only, no class:
334
+ const svc = createAutoMock<UserService>();
335
+ ```
336
+
337
+ Every accessed method becomes a decorated spy with the **same typed control helpers** as
338
+ `createSpyFromClass`, materialized lazily and cached (same reference on re-access):
339
+
340
+ ```ts
341
+ svc.getName.calledWith(1).mockReturnValue('Ada'); // sync, arg-matched
342
+ svc.getUser.resolveWith({ id: 1, name: 'Ada' }); // promise helper
343
+ expect(svc.getName(1)).toBe('Ada');
344
+ await expect(svc.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
345
+ ```
346
+
347
+ Seed concrete values or implementations with the optional `overrides` argument (seeded keys are
348
+ returned as-is, never turned into spies):
349
+
350
+ ```ts
351
+ const svc = createAutoMock<UserService>({ apiUrl: 'https://api.test' });
352
+ expect(svc.apiUrl).toBe('https://api.test'); // or assign: svc.apiUrl = '...'
353
+ ```
354
+
355
+ > Caveat: with only a type at runtime, methods and plain properties are indistinguishable on
356
+ > access — an un-seeded property read returns a spy. Seed real property values via `overrides`
357
+ > (or assignment) to get them back verbatim.
358
+
155
359
  ## Synchronous methods
156
360
 
157
361
  ```ts
@@ -246,7 +450,144 @@ spy.userName = 'New Name';
246
450
  expect(spy.accessorSpies.setters.userName).toHaveBeenCalledWith('New Name');
247
451
  ```
248
452
 
249
- ## Angular helpers
453
+ ## Framework adapters
454
+
455
+ The core is framework-agnostic — `createSpyFromClass` / `createAutoMock` work in any test. The
456
+ subpaths below add a natural import and, where the framework has class DI, a tiny `provide*` helper.
457
+ None of them pull the framework into this package; they're recipes over the same core.
458
+
459
+ > The **Angular**, **NestJS**, **React**, **Vue/Pinia** and **Svelte** entry points are all published
460
+ > ([Availability](#availability)). Each is a thin recipe over the same core, so you can equally copy it
461
+ > using the core `vitest-auto-spy` import directly.
462
+
463
+ ### NestJS
464
+
465
+ Use `provideAutoSpy` to register a fully-mocked service in a `TestingModule`, then `injectSpy` to
466
+ pull it back out already typed as `Spy<T>`. `@nestjs/common` / `@nestjs/testing` are your own
467
+ (optional) peers — the helper imports neither:
468
+
469
+ ```ts
470
+ import { Test, type TestingModule } from '@nestjs/testing';
471
+ import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/nestjs';
472
+ import { beforeEach, expect, it } from 'vitest';
473
+
474
+ import { AuthService } from './auth.service';
475
+ import { UserService } from './user.service';
476
+
477
+ let moduleRef: TestingModule;
478
+ let userServiceSpy: Spy<UserService>;
479
+
480
+ beforeEach(async () => {
481
+ moduleRef = await Test.createTestingModule({
482
+ providers: [AuthService, provideAutoSpy(UserService)],
483
+ }).compile();
484
+
485
+ userServiceSpy = injectSpy(moduleRef, UserService);
486
+ });
487
+
488
+ it('logs in a known user', () => {
489
+ userServiceSpy.findByEmail.mockReturnValue({ id: 1, name: 'Ada' });
490
+
491
+ const auth = moduleRef.get(AuthService);
492
+ expect(auth.login('ada@example.com')).toBeTruthy();
493
+ expect(userServiceSpy.findByEmail).toHaveBeenCalledWith('ada@example.com');
494
+ });
495
+ ```
496
+
497
+ ### React (Testing Library)
498
+
499
+ React has no DI container, so there's no `provide*` helper — the recipe is: **spy the classes you
500
+ own** (services, stores, API clients, hook deps), then pass the spy into a Context provider or hook.
501
+ The spy is a plain object of spied functions, so it drops straight into `value={...}`:
502
+
503
+ ```tsx
504
+ import { render, screen } from '@testing-library/react';
505
+ import { createSpyFromClass, type Spy } from 'vitest-auto-spy/react';
506
+ import { CartContext, Cart } from './cart';
507
+
508
+ class CartStore {
509
+ getItemCount(): number { return 0; }
510
+ checkout(token: string): Promise<{ orderId: string }> { /* ... */ }
511
+ }
512
+
513
+ let cart: Spy<CartStore>;
514
+
515
+ beforeEach(() => {
516
+ cart = createSpyFromClass(CartStore); // every method is now a spy
517
+ });
518
+
519
+ it('shows the item count from the injected store', () => {
520
+ cart.getItemCount.mockReturnValue(3);
521
+
522
+ render(
523
+ <CartContext.Provider value={cart}>
524
+ <Cart />
525
+ </CartContext.Provider>,
526
+ );
527
+
528
+ expect(screen.getByText('3 items')).toBeInTheDocument();
529
+ });
530
+
531
+ it('drives async deps and asserts the component called them', async () => {
532
+ cart.checkout.resolveWith({ orderId: 'ord_42' });
533
+ // ...trigger checkout in the UI...
534
+ expect(cart.checkout).toHaveBeenCalledWith('tok_abc');
535
+ });
536
+ ```
537
+
538
+ ### Vue / Pinia
539
+
540
+ `provideAutoSpy(token, Class)` returns a `{ [token]: Spy<T> }` map you can spread into
541
+ `@vue/test-utils`' `global.provide`; for a class-based Pinia store, spy it directly:
542
+
543
+ ```ts
544
+ import { mount } from '@vue/test-utils';
545
+ import { createSpyFromClass, provideAutoSpy } from 'vitest-auto-spy/vue';
546
+
547
+ // (a) class-based service injected via provide / global.provide
548
+ import { UserServiceKey, UserService } from '@/services/user.service';
549
+
550
+ const provide = provideAutoSpy(UserServiceKey, UserService); // { [UserServiceKey]: Spy<UserService> }
551
+ provide[UserServiceKey].getName.mockReturnValue('Fake Name');
552
+
553
+ const wrapper = mount(UserBadge, { global: { provide } });
554
+ expect(provide[UserServiceKey].getName).toHaveBeenCalled();
555
+
556
+ // (b) class-based Pinia store — every action becomes a spy
557
+ import { CartStore } from '@/stores/cart.store';
558
+
559
+ const store = createSpyFromClass(CartStore);
560
+ store.itemCount.mockReturnValue(3); // sync action/getter
561
+ store.checkout.resolveWith({ orderId: 'ord_42' }); // async action (Promise)
562
+ await store.checkout('tok_abc');
563
+ expect(store.checkout).toHaveBeenCalledWith('tok_abc');
564
+ ```
565
+
566
+ ### Svelte
567
+
568
+ Svelte has no class-based DI, so it's a recipe: keep your logic in plain class-based
569
+ services/stores, spy the class, and hand the spy to the component the same way it receives the real
570
+ one (props, context, or a mocked module):
571
+
572
+ ```ts
573
+ import { render } from '@testing-library/svelte';
574
+ import { createSpyFromClass } from 'vitest-auto-spy/svelte';
575
+ import Cart from './Cart.svelte';
576
+ import { CartStore } from './cart-store';
577
+
578
+ it('shows the cart total from the store', () => {
579
+ const cartStore = createSpyFromClass(CartStore); // every method is a spy
580
+
581
+ cartStore.total.mockReturnValue(42);
582
+ cartStore.priceOf.calledWith('apple').mockReturnValue(7);
583
+
584
+ render(Cart, { props: { store: cartStore } });
585
+
586
+ expect(cartStore.total).toHaveBeenCalled();
587
+ });
588
+ ```
589
+
590
+ ### Angular
250
591
 
251
592
  `provideAutoSpy` is the shorthand for providing an auto-spy in a `TestBed`:
252
593
 
@@ -274,7 +615,7 @@ beforeEach(() => {
274
615
  > [`@analogjs/vite-plugin-angular`](https://www.npmjs.com/package/@analogjs/vite-plugin-angular)
275
616
  > plus a TestBed setup file (e.g. `@analogjs/vitest-angular`'s `setupTestBed()`).
276
617
 
277
- ### Signal / readonly property mocking (bonus)
618
+ #### Signal / readonly property mocking (bonus)
278
619
 
279
620
  ```ts
280
621
  import { mockReadonlyProp, mockReadonlyPropGetter, mockAccessorsProp } from 'vitest-auto-spy/angular';
@@ -289,8 +630,10 @@ mockAccessorsProp(service, 'theme'); // spied get + set
289
630
  | Export | Description |
290
631
  | --- | --- |
291
632
  | `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>` |
633
+ | `createAutoMock<T>(overrides?)` | Build a `Spy<T>` from a **type/interface** alone (Proxy, no class) |
634
+ | `provideAutoSpy(Class, methodsOrConfig?)` | Angular / NestJS `{ provide, useValue }` shorthand |
635
+ | `provideAutoSpy(token, Class, methodsOrConfig?)` | Vue `{ [token]: Spy<T> }` for `global.provide` |
636
+ | `injectSpy(token)` _(Angular)_ / `injectSpy(moduleRef, token)` _(NestJS)_ | Inject typed as `Spy<T>` |
294
637
  | `createFunctionSpy(name)` | A single standalone function spy with all helpers |
295
638
  | `createObservableWithValues(configs, opts?)` | Build an Observable from value configs |
296
639
  | `mockReadonlyProp` / `mockReadonlyPropGetter` / `mockAccessorsProp` | Mock readonly / accessor / signal props |
@@ -308,6 +651,38 @@ mockAccessorsProp(service, 'theme'); // spied get + set
308
651
 
309
652
  `ValueConfig` (for `nextWithValues`): `{ value, delay? }` | `{ errorValue, delay? }` | `{ complete?, delay? }`.
310
653
 
654
+ ## FAQ & troubleshooting
655
+
656
+ **"I get `X.nextWith is not a function` / observable helpers are missing."**
657
+ Import the rxjs layer once (e.g. in your test setup): `import 'vitest-auto-spy/rxjs';`. Without it,
658
+ requesting an observable spy throws a hint pointing you here.
659
+
660
+ **"My method isn't on the spy."**
661
+ Auto-discovery only sees **prototype methods**. Arrow-function class fields (`foo = () => {}`) and
662
+ plain properties aren't included — see [How it works](#how-it-works-and-what-it-wont-spy). List
663
+ getters/setters via `gettersToSpyOn` / `settersToSpyOn`.
664
+
665
+ **"Does it construct my class? Will the constructor's side effects run?"**
666
+ No. `createSpyFromClass` reads the prototype and never `new`s the class, so constructors (and their
667
+ HTTP/DB/`inject()` side effects) never run.
668
+
669
+ **"I only have an interface/type, not a class."**
670
+ Use [`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed) — it builds the spy lazily from the
671
+ type via a `Proxy`, no runtime class needed.
672
+
673
+ **"Can I use it without TypeScript?"**
674
+ Yes — the runtime works in plain JS; you just lose the compile-time `Spy<T>` typing.
675
+
676
+ **"Native mock methods differ between runners."**
677
+ Only the auto-spy helpers are normalised. Native APIs stay the runner's own (`mockReturnValue` on
678
+ Vitest/Bun, `spy.method.mock.mockImplementation` on `node:test`).
679
+
680
+ ## Versioning
681
+
682
+ This package follows [Semantic Versioning](https://semver.org). Breaking changes to the public API
683
+ land only in major releases; see the [Changelog](./CHANGELOG.md) for what changed in each version.
684
+ Releases are automated from Conventional Commits (see [Contributing](#contributing)).
685
+
311
686
  ## Contributing
312
687
 
313
688
  Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) and the
@@ -322,11 +697,18 @@ npm run build
322
697
 
323
698
  Releases are automated: merging a PR into `master` bumps the version from the
324
699
  Conventional Commit types and publishes to npm — see
325
- [CONTRIBUTING.md → Releases](./CONTRIBUTING.md#releases).
700
+ [CONTRIBUTING.md → Releasing](./CONTRIBUTING.md#releasing).
326
701
 
327
702
  If this package saved you time, a ⭐ on [GitHub](https://github.com/ASDAlexey/vitest-auto-spy)
328
703
  helps others find it.
329
704
 
705
+ ## Acknowledgements
706
+
707
+ API and ergonomics are modelled on Shai Reznik's
708
+ [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — `vitest-auto-spy` is its
709
+ Vitest-era successor with the same surface, so migrations are (mostly) a find-and-replace. Thanks to
710
+ the Vitest, Bun, RxJS and Angular communities whose tooling this builds on.
711
+
330
712
  ## License
331
713
 
332
714
  [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;