@codefast/di-testing 0.1.0 → 0.1.2

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +140 -63
  3. package/dist/discovery/dependency-scanner.d.ts +1 -2
  4. package/dist/discovery/dependency-scanner.js +1 -2
  5. package/dist/discovery/mock-binder.d.ts +3 -3
  6. package/dist/discovery/mock-binder.js +26 -12
  7. package/dist/errors/errors.d.ts +1 -2
  8. package/dist/errors/errors.js +1 -2
  9. package/dist/index.d.ts +1 -2
  10. package/dist/index.js +1 -2
  11. package/dist/mocking/auto-mock.d.ts +1 -2
  12. package/dist/mocking/auto-mock.js +1 -2
  13. package/dist/mocking/mock-factory.d.ts +1 -2
  14. package/dist/mocking/mock-factory.js +1 -2
  15. package/dist/mocking/spy.d.ts +1 -2
  16. package/dist/mocking/spy.js +1 -2
  17. package/dist/test-bed/bed-builder.d.ts +1 -2
  18. package/dist/test-bed/bed-builder.js +1 -2
  19. package/dist/test-bed/sociable-builder.d.ts +1 -2
  20. package/dist/test-bed/sociable-builder.js +1 -2
  21. package/dist/test-bed/solitary-builder.d.ts +1 -2
  22. package/dist/test-bed/solitary-builder.js +1 -2
  23. package/dist/test-bed/test-bed.d.ts +1 -2
  24. package/dist/test-bed/test-bed.js +1 -2
  25. package/dist/test-bed/unit-test-bed.d.ts +1 -2
  26. package/dist/test-bed/unit-test-bed.js +1 -2
  27. package/dist/types.d.ts +1 -2
  28. package/dist/types.js +1 -2
  29. package/package.json +7 -29
  30. package/dist/discovery/dependency-scanner.d.ts.map +0 -1
  31. package/dist/discovery/dependency-scanner.js.map +0 -1
  32. package/dist/discovery/mock-binder.d.ts.map +0 -1
  33. package/dist/discovery/mock-binder.js.map +0 -1
  34. package/dist/errors/errors.d.ts.map +0 -1
  35. package/dist/errors/errors.js.map +0 -1
  36. package/dist/index.d.ts.map +0 -1
  37. package/dist/index.js.map +0 -1
  38. package/dist/mocking/auto-mock.d.ts.map +0 -1
  39. package/dist/mocking/auto-mock.js.map +0 -1
  40. package/dist/mocking/mock-factory.d.ts.map +0 -1
  41. package/dist/mocking/mock-factory.js.map +0 -1
  42. package/dist/mocking/spy.d.ts.map +0 -1
  43. package/dist/mocking/spy.js.map +0 -1
  44. package/dist/test-bed/bed-builder.d.ts.map +0 -1
  45. package/dist/test-bed/bed-builder.js.map +0 -1
  46. package/dist/test-bed/sociable-builder.d.ts.map +0 -1
  47. package/dist/test-bed/sociable-builder.js.map +0 -1
  48. package/dist/test-bed/solitary-builder.d.ts.map +0 -1
  49. package/dist/test-bed/solitary-builder.js.map +0 -1
  50. package/dist/test-bed/test-bed.d.ts.map +0 -1
  51. package/dist/test-bed/test-bed.js.map +0 -1
  52. package/dist/test-bed/unit-test-bed.d.ts.map +0 -1
  53. package/dist/test-bed/unit-test-bed.js.map +0 -1
  54. package/dist/types.d.ts.map +0 -1
  55. package/dist/types.js.map +0 -1
  56. package/src/discovery/dependency-scanner.ts +0 -80
  57. package/src/discovery/mock-binder.ts +0 -315
  58. package/src/errors/errors.ts +0 -119
  59. package/src/index.ts +0 -26
  60. package/src/mocking/auto-mock.ts +0 -243
  61. package/src/mocking/mock-factory.ts +0 -35
  62. package/src/mocking/spy.ts +0 -92
  63. package/src/test-bed/bed-builder.ts +0 -172
  64. package/src/test-bed/sociable-builder.ts +0 -148
  65. package/src/test-bed/solitary-builder.ts +0 -70
  66. package/src/test-bed/test-bed.ts +0 -64
  67. package/src/test-bed/unit-test-bed.ts +0 -161
  68. package/src/types.ts +0 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @codefast/di-testing
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#791](https://github.com/codefastlabs/codefast/pull/791) [`37a212b`](https://github.com/codefastlabs/codefast/commit/37a212b4d805588413159e11e872b98db82326bf) Thanks [@thevuong](https://github.com/thevuong)! - Point the README's license badge and "License" section at the package's own `LICENSE` file instead of the monorepo root.
8
+
9
+ - [#796](https://github.com/codefastlabs/codefast/pull/796) [`ba04d27`](https://github.com/codefastlabs/codefast/commit/ba04d2703c59a1677f52e6a9fffd0ec202328218) Thanks [@thevuong](https://github.com/thevuong)! - Rewrite the README around one structure — what the package is, installation and requirements, a complete quick start,
10
+ the concepts in learning order, then the documents and the license — with every API claim checked against the current
11
+ code and no figures that go stale.
12
+
13
+ - [#784](https://github.com/codefastlabs/codefast/pull/784) [`ad2f93a`](https://github.com/codefastlabs/codefast/commit/ad2f93a688e99c3ed8be6ceeae9d6cdd6be861bc) Thanks [@thevuong](https://github.com/thevuong)! - Ship the MIT `LICENSE` file in the published package. `files` already listed it, but the file was missing from the
14
+ package directory, so the tarball had none.
15
+ - Updated dependencies [[`37a212b`](https://github.com/codefastlabs/codefast/commit/37a212b4d805588413159e11e872b98db82326bf), [`ba04d27`](https://github.com/codefastlabs/codefast/commit/ba04d2703c59a1677f52e6a9fffd0ec202328218), [`ad2f93a`](https://github.com/codefastlabs/codefast/commit/ad2f93a688e99c3ed8be6ceeae9d6cdd6be861bc)]:
16
+ - @codefast/di@0.8.1
17
+
18
+ ## 0.1.1
19
+
20
+ ### Patch Changes
21
+
22
+ - [#774](https://github.com/codefastlabs/codefast/pull/774) [`176e95a`](https://github.com/codefastlabs/codefast/commit/176e95a32b44fa16d81a22e4a48f53176837c839) Thanks [@thevuong](https://github.com/thevuong)! - Fold di's reserved `slotName` criterion into slot addressing, so `{ name: "x" }` and `{ tag: slotName.of("x") }` — one
23
+ slot to the container — are one slot to TestBed too: a mock registered with either spelling now matches a dependency
24
+ declared with the other, and `mocks.get(token, options)` accepts both.
25
+ - Updated dependencies [[`96af502`](https://github.com/codefastlabs/codefast/commit/96af502ed8dd7fc02c4440d03b40dc6677b7bcec)]:
26
+ - @codefast/di@0.8.0
27
+
3
28
  ## 0.1.0
4
29
 
5
30
  ### Minor Changes
package/README.md CHANGED
@@ -1,28 +1,29 @@
1
1
  # @codefast/di-testing
2
2
 
3
- Solitary and sociable auto-mocking test beds for
4
- [`@codefast/di`](https://github.com/codefastlabs/codefast/tree/main/packages/di) — write an isolated unit test for an
5
- `@injectable` class in two lines, with every collaborator mocked for you, or keep chosen collaborators real.
3
+ Solitary and sociable auto-mocking test beds for [`@codefast/di`](../di): unit-test an `@injectable` class with every
4
+ collaborator mocked for you, or keep chosen collaborators real.
6
5
 
7
6
  [![npm version](https://img.shields.io/npm/v/@codefast/di-testing)](https://www.npmjs.com/package/@codefast/di-testing)
8
- [![license](https://img.shields.io/npm/l/@codefast/di-testing)](https://github.com/codefastlabs/codefast/blob/main/LICENSE)
7
+ [![license](https://img.shields.io/npm/l/@codefast/di-testing)](./LICENSE)
8
+
9
+ ## Overview
10
+
11
+ `@codefast/di-testing` gives you two auto-mocking test beds for [`@codefast/di`](../di). A **solitary** bed constructs
12
+ the class under test with every collaborator mocked for you; a **sociable** bed keeps chosen collaborators real and
13
+ mocks the rest.
14
+
15
+ The unit is built through a real container, so `@postConstruct`, accessor injection, and `@preDestroy` run exactly as in
16
+ production — and you assert against the mocks the bed created.
9
17
 
10
18
  - **Auto-mocking.** `TestBed.solitary(Class)` reads the class's declared dependencies through di's own `MetadataReader`
11
19
  and builds a mock for each — no per-collaborator `bind(...).toConstantValue(...)`.
12
20
  - **Real instance, real wiring.** The unit is constructed through a container, so `@postConstruct`, accessor injection,
13
21
  and `@preDestroy` run exactly as in production.
14
- - **Zero test-framework dependency.** The default mock is a small built-in spy. Pass `() => vi.fn()` (or `jest.fn`,
15
- `() => sinon.stub()`) to build the mocks from that backend and use its matchers instead.
22
+ - **Zero test-framework dependency.** The default mock is a small built-in spy. Pass `() => vi.fn()` — or `jest.fn`,
23
+ `() => sinon.stub()` — to build the mocks from that backend and use its matchers instead.
16
24
  - **Backend-typed lookups.** The factory's return type flows through the whole bed: with `() => vi.fn()`,
17
- `mocks.get(EmailToken).send` carries Vitest's own mock surface (`mockReturnValueOnce`, `mockClear`, …) — no adapter
18
- package, no module augmentation, no postinstall scripts.
19
-
20
- ## Requirements
21
-
22
- - **Node.js 24 or later**, matching `@codefast/di`.
23
- - A peer install of `@codefast/di` with `@injectable` / `inject` classes to test.
24
- - Types for `AsyncDisposable` (`await using`): `@types/node` provides them; without it, add `"ESNext.Disposable"` to
25
- your tsconfig `lib`.
25
+ `mocks.get(EmailToken).send` carries Vitest's own mock surface (`mockReturnValueOnce`, `mockClear`, and so on), with
26
+ no adapter package and no module augmentation.
26
27
 
27
28
  ## Installation
28
29
 
@@ -30,18 +31,32 @@ Solitary and sociable auto-mocking test beds for
30
31
  pnpm add -D @codefast/di-testing
31
32
  ```
32
33
 
34
+ `@codefast/di-testing` requires Node.js 24 or later and a peer install of `@codefast/di` (`>=0.8.0`), with the same
35
+ TypeScript setup: native Stage 3 decorators, `experimentalDecorators` off. The package is published on 0.x and versioned
36
+ on its own track: breaking changes ship as minor versions, so pin the minor version when you need stability.
37
+
33
38
  ## Quick start
34
39
 
35
- ```typescript
36
- import { inject, injectable, token } from "@codefast/di";
40
+ ```ts
41
+ import { injectable, token } from "@codefast/di";
37
42
  import { TestBed } from "@codefast/di-testing";
38
43
  import { expect, it, vi } from "vitest";
39
44
 
45
+ interface UserService {
46
+ findUser(id: string): { id: string; email: string };
47
+ }
48
+ interface PaymentGateway {
49
+ charge(userId: string, amount: number): void;
50
+ }
51
+ interface EmailService {
52
+ send(to: string, body: string): void;
53
+ }
54
+
40
55
  const UserServiceToken = token<UserService>("UserService");
41
56
  const PaymentGatewayToken = token<PaymentGateway>("PaymentGateway");
42
57
  const EmailServiceToken = token<EmailService>("EmailService");
43
58
 
44
- @injectable([inject(UserServiceToken), inject(PaymentGatewayToken), inject(EmailServiceToken)])
59
+ @injectable([UserServiceToken, PaymentGatewayToken, EmailServiceToken])
45
60
  class OrderProcessor {
46
61
  constructor(
47
62
  private readonly users: UserService,
@@ -70,10 +85,10 @@ it("charges then emails a confirmation", () => {
70
85
  });
71
86
  ```
72
87
 
73
- The zero-dependency default reads the same, minus the `mockFactory` — assert against the built-in spy's `.mock.calls`
74
- and stub with `.mockReturnValue()`:
88
+ The zero-dependency default reads the same, minus the `mockFactory`. Assert against the built-in spy's `.mock.calls` and
89
+ stub with `.mockReturnValue()`:
75
90
 
76
- ```typescript
91
+ ```ts
77
92
  import { TestBed } from "@codefast/di-testing";
78
93
  import assert from "node:assert/strict";
79
94
 
@@ -82,81 +97,143 @@ unit.placeOrder("u1", 42);
82
97
  assert.deepEqual(mocks.get(PaymentGatewayToken).charge.mock.calls[0], ["u1", 42]);
83
98
  ```
84
99
 
85
- ## API
100
+ ## Solitary beds
86
101
 
87
- ### `TestBed.solitary(target, options?)`
102
+ `TestBed.solitary(target, options?)` begins a bed for `target` and auto-mocks every dependency it declares. Nothing is
103
+ instantiated until you call `compile()`. The options:
88
104
 
89
- Begins a solitary test bed for `target`, auto-mocking every dependency it declares. `options`:
90
-
91
- - `mockFactory?: () => spy` — the spy backend for each auto-mock. Defaults to the built-in zero-dependency spy.
105
+ - `mockFactory?: () => spy` — the spy backend each auto-mock is built from. Defaults to the built-in spy.
92
106
  - `metadataReader?: MetadataReader` — the reader dependencies are discovered through. Defaults to di's reader.
93
107
 
94
- ### Builder
108
+ The builder records overrides, then compiles:
95
109
 
96
110
  - `.mock(token).stub((fn) => stub)` — bind a partial stub built from the active spy factory; unlisted members stay
97
111
  auto-mocked. `fn()` is typed as whatever the backend produces, so `fn().mockReturnValue(...)` (jest-shaped) or
98
- `fn().returns(...)` (Sinon) both type-check against the factory you chose.
112
+ `fn().returns(...)` (Sinon) type-check against the factory you chose.
99
113
  - `.mock(token).using(value)` — bind a fixed value. The value is **sealed**: it has no mock surface, so `mocks.get`
100
114
  refuses it rather than hand it back mistyped — the test already holds the reference it passed in.
101
115
  - `.mock(token).absent()` — leave the dependency unbound: an `optional()` slot resolves `undefined`, an `injectAll()`
102
- slot `[]`. Anything else is an `OverrideMismatchError`.
116
+ slot `[]`. On a required dependency this is an `OverrideMismatchError`.
103
117
  - `.mock(token).usingAll([a, b])` — supply the elements of an unconstrained `injectAll()` slot, in order. Sealed like
104
118
  `.using`.
105
119
  - `.mock(token, { name })` / `.mock(token, { tag })` — target one slot of a token that is injected several ways; the
106
- slotless form covers every slot without a more specific override.
107
- - `.compile()` — instantiate the unit synchronously (the primary path).
108
- - `.compileAsync()` — for a unit whose `@postConstruct` is asynchronous.
120
+ slotless form covers every slot without a more specific override. Registering the same target twice replaces the
121
+ earlier override.
122
+ - `.compile()` — instantiate the unit synchronously, running accessor injection and `@postConstruct`.
123
+ - `.compileAsync()` — the same for a unit whose `@postConstruct` is asynchronous.
124
+
125
+ `.mock(...)` accepts a token or a class, matching how the unit declares the dependency.
109
126
 
110
- ### `TestBed.sociable(target, options?)`
127
+ ## Sociable beds
111
128
 
112
129
  A sociable bed keeps chosen collaborators real while everything else stays mocked — a unit test over a small real
113
- subtree, not an integration test. It returns only `.expose()`, steering the first call:
130
+ subtree, not an integration test. `TestBed.sociable(target, options?)` takes the same options and returns only
131
+ `.expose()`, because a sociable bed with nothing exposed is a solitary bed.
132
+
133
+ ```ts
134
+ import { injectable, token } from "@codefast/di";
135
+ import { TestBed } from "@codefast/di-testing";
136
+ import { expect, it, vi } from "vitest";
137
+
138
+ interface TaxPolicy {
139
+ rateFor(currency: string): number;
140
+ }
141
+
142
+ const TaxPolicyToken = token<TaxPolicy>("TaxPolicy");
143
+
144
+ @injectable([TaxPolicyToken])
145
+ class PricingService {
146
+ constructor(private readonly tax: TaxPolicy) {}
114
147
 
115
- ```typescript
116
- const bed = TestBed.sociable(CheckoutService, { mockFactory: () => vi.fn() })
117
- .expose(PricingService) // real instance, wired through the container
118
- .mock(TaxPolicyToken)
119
- .stub((fn) => ({ rateFor: fn().mockReturnValue(0.1) }))
120
- .compile();
148
+ total(amount: number, currency: string): number {
149
+ return amount * (1 + this.tax.rateFor(currency));
150
+ }
151
+ }
152
+
153
+ @injectable([PricingService])
154
+ class CheckoutService {
155
+ constructor(private readonly pricing: PricingService) {}
156
+
157
+ checkout(amount: number, currency: string): number {
158
+ return this.pricing.total(amount, currency);
159
+ }
160
+ }
161
+
162
+ it("prices through the real PricingService over a mocked tax boundary", () => {
163
+ const bed = TestBed.sociable(CheckoutService, { mockFactory: () => vi.fn() })
164
+ .expose(PricingService)
165
+ .mock(TaxPolicyToken)
166
+ .stub((fn) => ({ rateFor: fn().mockReturnValue(0.1) }))
167
+ .compile();
121
168
 
122
- bed.unit.checkout(100, "USD"); // real PricingService math over the mocked tax boundary
123
- bed.exposed(PricingService); // the real instance the unit was built with
169
+ expect(bed.unit.checkout(100, "USD")).toBe(110);
170
+ expect(bed.exposed(PricingService)).toBeInstanceOf(PricingService);
171
+ });
124
172
  ```
125
173
 
126
174
  - **Exposure follows class identity.** A class-keyed dependency — of the unit or of another exposed class — stays real
127
175
  when exposed, and its own dependencies follow the same rules recursively.
128
176
  - **Tokens are the boundary.** A `Token`-keyed dependency is always mocked, in both modes: tokens mark where the logic
129
177
  under test meets the outside world.
130
- - Exposed collaborators are real singletons resolved through the container, so their `@postConstruct` runs at compile
131
- and `@preDestroy` on dispose. `bed.exposed(Class)` retrieves them; `mocks.get(Class)` refuses them
132
- (`SealedDependencyError`) because they carry no mock surface.
133
- - Exposing a class the unit never reaches, or exposing and mocking the same class, is an error at compile.
134
-
135
- ### Behavior notes
136
-
137
- - An `optional()` dependency is auto-mocked like any other — it resolves to the mock, not `undefined` as an unbound
138
- optional would in production. Use `.mock(token).absent()` to exercise the absent branch.
178
+ - Exposed collaborators are singletons resolved through the container, so their `@postConstruct` runs at compile and
179
+ `@preDestroy` on dispose. `bed.exposed(Class)` returns the instance the unit was built with; `mocks.get(Class)`
180
+ refuses it with a `SealedDependencyError` because it carries no mock surface.
181
+ - Exposing the unit itself, or a class the unit never reaches through exposed collaborators, is an `ExposureError` at
182
+ compile.
183
+
184
+ ## Behaviour notes
185
+
186
+ - A class whose constructor takes no parameters is testable without `@injectable`; a parameterful constructor with no
187
+ metadata is a `NotInjectableError`.
188
+ - An `optional()` dependency is auto-mocked like any other — it resolves to the mock, not to the `undefined` an unbound
189
+ optional would give in production. Use `.mock(token).absent()` to exercise the absent branch.
139
190
  - An `injectAll()` dependency receives a one-element array holding the token's mock; use `.mock(token).usingAll([...])`
140
- to supply several elements. When the same token also has a named or tagged slot, that slot's binding is collected by
141
- the unconstrained `injectAll` too — di's `resolveAll` takes every binding of the token.
191
+ to supply several elements.
142
192
  - Named or tagged parameters of one token share the token's mock unless a slot-targeted `.mock(token, { name })` gives
143
193
  that slot its own; either way `mocks.get(token, { name })` addresses the slot directly.
194
+ - A `.mock(...)` that names a token or slot the unit does not declare is an `UndeclaredDependencyError` at compile — a
195
+ typo fails loudly instead of binding an unused constant.
196
+ - A failed compile disposes the container it was building, so no lifecycle state leaks between tests.
197
+
198
+ ## Result
144
199
 
145
- ### Result — `UnitTestBed`
200
+ `compile()` returns a `UnitTestBed` (a sociable bed returns a `SociableUnitTestBed`, which adds `exposed`):
146
201
 
147
202
  - `unit` — the real class under test.
148
- - `mocks.get(token, options?)` — the `Mocked<T>` bound for a dependency (or one slot of it). Only auto-mocks and `.stub`
149
- stubs come back; sealed values throw `SealedDependencyError`.
150
- - `resetMocks()` — clear the call history and configured behaviour of every auto-mock the bed created.
151
- - `dispose()` — run the unit's `@preDestroy` hooks and dispose the container. Also runs via `await using`.
203
+ - `mocks.get(token, options?)` — the `Mocked<T>` bound for a dependency, or for one slot of it. Only auto-mocks and
204
+ `.stub` stubs come back; sealed values and exposed classes throw `SealedDependencyError`.
205
+ - `resetMocks()` — clear the call history and configured behaviour of every auto-mock and stub the bed created.
206
+ - `dispose()` — run the unit's `@preDestroy` hooks and dispose the container.
207
+
208
+ The bed implements `AsyncDisposable`, so `await using bed = TestBed.solitary(X).compile()` disposes it at the end of the
209
+ block; that needs the `esnext.disposable` lib in your TypeScript configuration if your `target` does not include it.
210
+
211
+ The lower-level pieces are exported too: `createAutoMock`, `createSpy`, `defaultMockFactory`, and the `Mocked`,
212
+ `DeepPartial`, `MockFactory`, and `Spy` types.
152
213
 
153
214
  ## Errors
154
215
 
155
- - `NotInjectableError` — the class under test takes constructor parameters but is not `@injectable`.
156
- - `UndeclaredDependencyError` — a `.mock(...)` or `mocks.get(...)` named a token or slot the class does not use.
157
- - `SealedDependencyError` — `mocks.get(...)` asked for a `.using()`/`.absent()`/`.all()` value.
158
- - `OverrideMismatchError` — `.absent()` on a required dependency, or `.all()` on a non-`injectAll` slot.
216
+ Every error extends `TestingError` and carries a stable `code`. `TestingError` mirrors di's `DiError` shape without
217
+ extending it, so setup failures can be caught separately from resolution failures.
218
+
219
+ | Error | Code | Raised when |
220
+ | --------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------- |
221
+ | `NotInjectableError` | `NOT_INJECTABLE` | A scanned class takes constructor parameters but is not `@injectable` |
222
+ | `UndeclaredDependencyError` | `UNDECLARED_DEPENDENCY` | A `.mock(...)` or `mocks.get(...)` named a token or slot the unit does not use |
223
+ | `SealedDependencyError` | `SEALED_DEPENDENCY` | `mocks.get(...)` asked for a `.using()`/`.absent()`/`.usingAll()` value or an exposed class |
224
+ | `OverrideMismatchError` | `OVERRIDE_MISMATCH` | `.absent()` on a required dependency, or `.usingAll()` with no unconstrained `injectAll()` slot |
225
+ | `ExposureError` | `EXPOSURE` | An exposed class is the unit, is unreachable, or `bed.exposed()` names a class that was not exposed |
226
+
227
+ ## Documentation
228
+
229
+ - [Rendered docs on codefastlabs.com](https://codefastlabs.com/docs/di-testing)
230
+ - [`@codefast/di`](../di/README.md) — the container this package builds on, including its `SPEC.md`.
231
+ - [`CHANGELOG.md`](./CHANGELOG.md) — release history.
232
+
233
+ ## Contributing
234
+
235
+ See the repo-wide [contributing guide](../../CONTRIBUTING.md) for setup, conventions, and the test taxonomy.
159
236
 
160
237
  ## License
161
238
 
162
- MIT
239
+ Released under the [MIT License](./LICENSE).
@@ -31,5 +31,4 @@ export interface SociableScan {
31
31
  *
32
32
  * @since 0.1.0
33
33
  */
34
- export declare function scanSociableDependencies(target: Constructor, exposed: ReadonlySet<Constructor>, reader: MetadataReader): SociableScan;
35
- //# sourceMappingURL=dependency-scanner.d.ts.map
34
+ export declare function scanSociableDependencies(target: Constructor, exposed: ReadonlySet<Constructor>, reader: MetadataReader): SociableScan;
@@ -51,5 +51,4 @@ export function scanSociableDependencies(target, exposed, reader) {
51
51
  }
52
52
  }
53
53
  return { mockSlots, realClasses, realSlots };
54
- }
55
- //# sourceMappingURL=dependency-scanner.js.map
54
+ }
@@ -60,7 +60,8 @@ export interface BoundMock {
60
60
  * Folds an `InjectOptions` into slot criteria, or `undefined` when it names no criterion.
61
61
  *
62
62
  * @remarks Tags are copied and deduplicated by identity — di interns each `(key, value)` pair, so a
63
- * repeated pair is the same object.
63
+ * repeated pair is the same object. A reserved `slotName` criterion folds into `name`, so both
64
+ * spellings address one slot.
64
65
  *
65
66
  * @since 0.1.0
66
67
  */
@@ -83,5 +84,4 @@ export declare function criteriaEquals(left: SlotCriteria, right: SlotCriteria):
83
84
  *
84
85
  * @since 0.1.0
85
86
  */
86
- export declare function bindMocks(container: Container, dependencies: ReadonlyArray<DependencySlot>, overrides: ReadonlyMap<DependencyKey, ReadonlyArray<SlottedOverride>>, mockFactory: MockFactory, exposed?: ReadonlySet<Constructor>): ReadonlyMap<DependencyKey, ReadonlyArray<BoundMock>>;
87
- //# sourceMappingURL=mock-binder.d.ts.map
87
+ export declare function bindMocks(container: Container, dependencies: ReadonlyArray<DependencySlot>, overrides: ReadonlyMap<DependencyKey, ReadonlyArray<SlottedOverride>>, mockFactory: MockFactory, exposed?: ReadonlySet<Constructor>): ReadonlyMap<DependencyKey, ReadonlyArray<BoundMock>>;
@@ -1,14 +1,28 @@
1
1
  /** Binds a unit's discovered dependencies onto a container as mocks — the sole container coupling. */
2
- import { tokenName } from "@codefast/di";
2
+ import { slotName, tokenName } from "@codefast/di";
3
3
  import { OverrideMismatchError, UndeclaredDependencyError } from "#/errors/errors";
4
4
  import { createAutoMock } from "#/mocking/auto-mock";
5
5
  /** The name prefix `usingAll` elements are bound under, so identical constants keep distinct slots. */
6
6
  const ALL_ELEMENT_SLOT_PREFIX = "di-testing:all:";
7
+ /**
8
+ * One name and plain tag set, with di's reserved criterion folded into the name.
9
+ *
10
+ * @remarks di treats `{name: "x"}` and `{tag: slotName.of("x")}` as one slot, so addressing here
11
+ * must too — mirroring `bindingSlotToResolveOptions` in di.
12
+ */
13
+ function foldReservedName(name, tags) {
14
+ const reserved = tags.find((tag) => tag.key === slotName);
15
+ if (reserved === undefined) {
16
+ return { name, tags };
17
+ }
18
+ return { name: name ?? reserved.value, tags: tags.filter((tag) => tag.key !== slotName) };
19
+ }
7
20
  /**
8
21
  * Folds an `InjectOptions` into slot criteria, or `undefined` when it names no criterion.
9
22
  *
10
23
  * @remarks Tags are copied and deduplicated by identity — di interns each `(key, value)` pair, so a
11
- * repeated pair is the same object.
24
+ * repeated pair is the same object. A reserved `slotName` criterion folds into `name`, so both
25
+ * spellings address one slot.
12
26
  *
13
27
  * @since 0.1.0
14
28
  */
@@ -16,12 +30,12 @@ export function normalizeCriteria(options) {
16
30
  if (options === undefined) {
17
31
  return undefined;
18
32
  }
19
- const folded = options.tag === undefined ? (options.tags ?? []) : [options.tag, ...(options.tags ?? [])];
20
- const tags = [...new Set(folded)];
21
- if (options.name === undefined && tags.length === 0) {
33
+ const merged = options.tag === undefined ? (options.tags ?? []) : [options.tag, ...(options.tags ?? [])];
34
+ const { name, tags } = foldReservedName(options.name, [...new Set(merged)]);
35
+ if (name === undefined && tags.length === 0) {
22
36
  return undefined;
23
37
  }
24
- return { name: options.name, tags };
38
+ return { name, tags };
25
39
  }
26
40
  /**
27
41
  * Returns whether two criteria address the same slot — equal name and the same tag set.
@@ -47,15 +61,16 @@ function tagSetEquals(left, right) {
47
61
  }
48
62
  /** Returns the slot's own criteria, or `undefined` for an unconstrained slot. */
49
63
  function criteriaOfSlot(slot) {
50
- const tags = slot.tags ?? [];
51
- if (slot.name === undefined && tags.length === 0) {
64
+ const { name, tags } = foldReservedName(slot.name, slot.tags ?? []);
65
+ if (name === undefined && tags.length === 0) {
52
66
  return undefined;
53
67
  }
54
- return { name: slot.name, tags };
68
+ return { name, tags };
55
69
  }
56
70
  /** Returns whether an override's criteria address exactly this slot. */
57
71
  function matchesSlot(criteria, slot) {
58
- return criteria.name === slot.name && tagSetEquals(criteria.tags, slot.tags ?? []);
72
+ const folded = foldReservedName(slot.name, slot.tags ?? []);
73
+ return criteria.name === folded.name && tagSetEquals(criteria.tags, folded.tags);
59
74
  }
60
75
  /** A readable rendering of criteria for error messages. */
61
76
  function describeCriteria(criteria) {
@@ -208,5 +223,4 @@ function buildMock(override, mockFactory) {
208
223
  }
209
224
  // The stub's seed is built here, once per compile, so beds never share seeded spies.
210
225
  return createAutoMock(mockFactory, override.setup(mockFactory));
211
- }
212
- //# sourceMappingURL=mock-binder.js.map
226
+ }
@@ -74,5 +74,4 @@ export declare class ExposureError extends TestingError {
74
74
  readonly code = "EXPOSURE";
75
75
  readonly targetName: string;
76
76
  constructor(targetName: string, detail: string);
77
- }
78
- //# sourceMappingURL=errors.d.ts.map
77
+ }
@@ -91,5 +91,4 @@ export class ExposureError extends TestingError {
91
91
  super(`'${targetName}': ${detail}`);
92
92
  this.targetName = targetName;
93
93
  }
94
- }
95
- //# sourceMappingURL=errors.js.map
94
+ }
package/dist/index.d.ts CHANGED
@@ -13,5 +13,4 @@ export { createSpy } from "#/mocking/spy";
13
13
  export type { Spy, SpyResult, SpyState } from "#/mocking/spy";
14
14
  export type { InjectionIdentifier } from "#/types";
15
15
  export { ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#/errors/errors";
16
- export type { SealedCause } from "#/errors/errors";
17
- //# sourceMappingURL=index.d.ts.map
16
+ export type { SealedCause } from "#/errors/errors";
package/dist/index.js CHANGED
@@ -3,5 +3,4 @@ export { TestBed } from "#/test-bed/test-bed";
3
3
  export { createAutoMock, MOCK_RESET } from "#/mocking/auto-mock";
4
4
  export { defaultMockFactory } from "#/mocking/mock-factory";
5
5
  export { createSpy } from "#/mocking/spy";
6
- export { ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#/errors/errors";
7
- //# sourceMappingURL=index.js.map
6
+ export { ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#/errors/errors";
@@ -51,5 +51,4 @@ export declare const MOCK_RESET: unique symbol;
51
51
  *
52
52
  * @since 0.1.0
53
53
  */
54
- export declare function createAutoMock<Dependency, Backend extends MockFunction = Spy>(mockFactory: MockFactory<Backend>, seed?: DeepPartial<Dependency>): Mocked<Dependency, Backend>;
55
- //# sourceMappingURL=auto-mock.d.ts.map
54
+ export declare function createAutoMock<Dependency, Backend extends MockFunction = Spy>(mockFactory: MockFactory<Backend>, seed?: DeepPartial<Dependency>): Mocked<Dependency, Backend>;
@@ -180,5 +180,4 @@ export function createAutoMock(mockFactory, seed) {
180
180
  });
181
181
  self = proxy;
182
182
  return proxy;
183
- }
184
- //# sourceMappingURL=auto-mock.js.map
183
+ }
@@ -27,5 +27,4 @@ export type MockFactory<Backend extends MockFunction = MockFunction> = () => Bac
27
27
  *
28
28
  * @since 0.1.0
29
29
  */
30
- export declare const defaultMockFactory: MockFactory<Spy>;
31
- //# sourceMappingURL=mock-factory.d.ts.map
30
+ export declare const defaultMockFactory: MockFactory<Spy>;
@@ -5,5 +5,4 @@ import { createSpy } from "#/mocking/spy";
5
5
  *
6
6
  * @since 0.1.0
7
7
  */
8
- export const defaultMockFactory = () => createSpy();
9
- //# sourceMappingURL=mock-factory.js.map
8
+ export const defaultMockFactory = () => createSpy();
@@ -43,5 +43,4 @@ export interface Spy<Args extends ReadonlyArray<unknown> = ReadonlyArray<unknown
43
43
  *
44
44
  * @since 0.1.0
45
45
  */
46
- export declare function createSpy(): Spy;
47
- //# sourceMappingURL=spy.d.ts.map
46
+ export declare function createSpy(): Spy;
@@ -41,5 +41,4 @@ export function createSpy() {
41
41
  },
42
42
  });
43
43
  return spy;
44
- }
45
- //# sourceMappingURL=spy.js.map
44
+ }
@@ -87,5 +87,4 @@ export declare abstract class BedBuilder<Class, Backend extends MockFunction = S
87
87
  protected compileWith<Prepared extends PreparedBed, Bed>(prepare: () => Prepared, build: (unit: Class, prepared: Prepared) => Bed): Bed;
88
88
  /** The async twin of {@link compileWith}, for units whose activation is asynchronous. */
89
89
  protected compileWithAsync<Prepared extends PreparedBed, Bed>(prepare: () => Prepared, build: (unit: Class, prepared: Prepared) => Bed): Promise<Bed>;
90
- }
91
- //# sourceMappingURL=bed-builder.d.ts.map
90
+ }
@@ -93,5 +93,4 @@ export class BedBuilder {
93
93
  list[existing] = { criteria, override };
94
94
  }
95
95
  }
96
- }
97
- //# sourceMappingURL=bed-builder.js.map
96
+ }
@@ -41,5 +41,4 @@ export declare class SociableBuilder<Class, Backend extends MockFunction = Spy>
41
41
  expose(target: Constructor): this;
42
42
  compile(): SociableUnitTestBed<Class, Backend>;
43
43
  compileAsync(): Promise<SociableUnitTestBed<Class, Backend>>;
44
- }
45
- //# sourceMappingURL=sociable-builder.d.ts.map
44
+ }
@@ -76,5 +76,4 @@ export class SociableBuilder extends BedBuilder {
76
76
  container.bind(this.target).toSelf().singleton();
77
77
  return { container, mocks: merged, realClasses };
78
78
  }
79
- }
80
- //# sourceMappingURL=sociable-builder.js.map
79
+ }
@@ -34,5 +34,4 @@ export declare class SolitaryBuilder<Class, Backend extends MockFunction = Spy>
34
34
  #private;
35
35
  compile(): UnitTestBed<Class, Backend>;
36
36
  compileAsync(): Promise<UnitTestBed<Class, Backend>>;
37
- }
38
- //# sourceMappingURL=solitary-builder.d.ts.map
37
+ }
@@ -27,5 +27,4 @@ export class SolitaryBuilder extends BedBuilder {
27
27
  container.bind(this.target).toSelf().singleton();
28
28
  return { container, mocks };
29
29
  }
30
- }
31
- //# sourceMappingURL=solitary-builder.js.map
30
+ }
@@ -35,5 +35,4 @@ export interface TestBedStatic {
35
35
  *
36
36
  * @since 0.1.0
37
37
  */
38
- export declare const TestBed: TestBedStatic;
39
- //# sourceMappingURL=test-bed.d.ts.map
38
+ export declare const TestBed: TestBedStatic;
@@ -16,5 +16,4 @@ export const TestBed = {
16
16
  sociable(target, options) {
17
17
  return new SociableBuilder(target, options);
18
18
  },
19
- };
20
- //# sourceMappingURL=test-bed.js.map
19
+ };
@@ -71,5 +71,4 @@ export declare function createSociableUnitTestBed<Class, Backend extends MockFun
71
71
  *
72
72
  * @since 0.1.0
73
73
  */
74
- export declare function createUnitTestBed<Class, Backend extends MockFunction = Spy>(unit: Class, entries: ReadonlyMap<DependencyKey, ReadonlyArray<BoundMock>>, container: Container): UnitTestBed<Class, Backend>;
75
- //# sourceMappingURL=unit-test-bed.d.ts.map
74
+ export declare function createUnitTestBed<Class, Backend extends MockFunction = Spy>(unit: Class, entries: ReadonlyMap<DependencyKey, ReadonlyArray<BoundMock>>, container: Container): UnitTestBed<Class, Backend>;
@@ -70,5 +70,4 @@ export function createUnitTestBed(unit, entries, container) {
70
70
  dispose,
71
71
  [Symbol.asyncDispose]: dispose,
72
72
  };
73
- }
74
- //# sourceMappingURL=unit-test-bed.js.map
73
+ }
package/dist/types.d.ts CHANGED
@@ -10,5 +10,4 @@ import type { Constructor, Token } from "@codefast/di";
10
10
  *
11
11
  * @since 0.1.0
12
12
  */
13
- export type InjectionIdentifier<Value> = Token<Value> | Constructor<Value>;
14
- //# sourceMappingURL=types.d.ts.map
13
+ export type InjectionIdentifier<Value> = Token<Value> | Constructor<Value>;
package/dist/types.js CHANGED
@@ -1,3 +1,2 @@
1
1
  /** Shared vocabulary the test bed keys its dependencies by. */
2
- export {};
3
- //# sourceMappingURL=types.js.map
2
+ export {};