@navios/di 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/README.md +117 -17
- package/lib/browser/container/abstract-container.d.mts +112 -0
- package/lib/browser/container/abstract-container.d.mts.map +1 -0
- package/lib/browser/container/abstract-container.mjs +100 -0
- package/lib/browser/container/abstract-container.mjs.map +1 -0
- package/lib/browser/container/container.d.mts +100 -0
- package/lib/browser/container/container.d.mts.map +1 -0
- package/lib/browser/container/container.mjs +424 -0
- package/lib/browser/container/container.mjs.map +1 -0
- package/lib/browser/container/scoped-container.d.mts +93 -0
- package/lib/browser/container/scoped-container.d.mts.map +1 -0
- package/lib/browser/container/scoped-container.mjs +119 -0
- package/lib/browser/container/scoped-container.mjs.map +1 -0
- package/lib/browser/decorators/factory.decorator.d.mts +26 -0
- package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/factory.decorator.mjs +20 -0
- package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
- package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
- package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/injectable.decorator.mjs +21 -0
- package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
- package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
- package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
- package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-type.enum.mjs +10 -0
- package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
- package/lib/browser/errors/di-error.d.mts +43 -0
- package/lib/browser/errors/di-error.d.mts.map +1 -0
- package/lib/browser/errors/di-error.mjs +98 -0
- package/lib/browser/errors/di-error.mjs.map +1 -0
- package/lib/browser/event-emitter.d.mts +16 -0
- package/lib/browser/event-emitter.d.mts.map +1 -0
- package/lib/browser/event-emitter.mjs +320 -0
- package/lib/browser/event-emitter.mjs.map +1 -0
- package/lib/browser/index.d.mts +37 -1558
- package/lib/browser/index.mjs +29 -2749
- package/lib/browser/interfaces/container.interface.d.mts +59 -0
- package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/factory.interface.d.mts +14 -0
- package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
- package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
- package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
- package/lib/browser/internal/context/factory-context.d.mts +23 -0
- package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.d.mts +43 -0
- package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.mjs +56 -0
- package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
- package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
- package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
- package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
- package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/instance-resolver.mjs +306 -0
- package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/name-resolver.d.mts +52 -0
- package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/name-resolver.mjs +118 -0
- package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
- package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
- package/lib/browser/internal/core/scope-tracker.mjs +120 -0
- package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
- package/lib/browser/internal/core/service-initializer.d.mts +44 -0
- package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
- package/lib/browser/internal/core/service-initializer.mjs +109 -0
- package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
- package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
- package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
- package/lib/browser/internal/core/service-invalidator.mjs +142 -0
- package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
- package/lib/browser/internal/core/token-resolver.d.mts +54 -0
- package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/token-resolver.mjs +77 -0
- package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
- package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.mjs +19 -0
- package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
- package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
- package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
- package/lib/browser/internal/holder/unified-storage.mjs +144 -0
- package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
- package/lib/browser/internal/stub-factory-class.d.mts +14 -0
- package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
- package/lib/browser/internal/stub-factory-class.mjs +18 -0
- package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
- package/lib/browser/symbols/injectable-token.d.mts +5 -0
- package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
- package/lib/browser/symbols/injectable-token.mjs +6 -0
- package/lib/browser/symbols/injectable-token.mjs.map +1 -0
- package/lib/browser/token/injection-token.d.mts +55 -0
- package/lib/browser/token/injection-token.d.mts.map +1 -0
- package/lib/browser/token/injection-token.mjs +100 -0
- package/lib/browser/token/injection-token.mjs.map +1 -0
- package/lib/browser/token/registry.d.mts +37 -0
- package/lib/browser/token/registry.d.mts.map +1 -0
- package/lib/browser/token/registry.mjs +86 -0
- package/lib/browser/token/registry.mjs.map +1 -0
- package/lib/browser/utils/default-injectors.d.mts +12 -0
- package/lib/browser/utils/default-injectors.d.mts.map +1 -0
- package/lib/browser/utils/default-injectors.mjs +13 -0
- package/lib/browser/utils/default-injectors.mjs.map +1 -0
- package/lib/browser/utils/get-injectable-token.d.mts +9 -0
- package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
- package/lib/browser/utils/get-injectable-token.mjs +13 -0
- package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
- package/lib/browser/utils/get-injectors.d.mts +55 -0
- package/lib/browser/utils/get-injectors.d.mts.map +1 -0
- package/lib/browser/utils/get-injectors.mjs +121 -0
- package/lib/browser/utils/get-injectors.mjs.map +1 -0
- package/lib/browser/utils/types.d.mts +23 -0
- package/lib/browser/utils/types.d.mts.map +1 -0
- package/lib/{container-DAKOvAgr.mjs → container-8-z89TyQ.mjs} +1325 -1462
- package/lib/container-8-z89TyQ.mjs.map +1 -0
- package/lib/{container-Bp1W-pWJ.d.mts → container-CNiqesCL.d.mts} +598 -617
- package/lib/container-CNiqesCL.d.mts.map +1 -0
- package/lib/{container-DENMeJ87.cjs → container-CaY2fDuk.cjs} +1369 -1512
- package/lib/container-CaY2fDuk.cjs.map +1 -0
- package/lib/{container-YPwvmlK2.d.cts → container-D-0Ho3qL.d.cts} +598 -612
- package/lib/container-D-0Ho3qL.d.cts.map +1 -0
- package/lib/index.cjs +13 -15
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +58 -223
- package/lib/index.d.cts.map +1 -1
- package/lib/index.d.mts +62 -222
- package/lib/index.d.mts.map +1 -1
- package/lib/index.mjs +5 -6
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +569 -311
- package/lib/testing/index.cjs.map +1 -1
- package/lib/testing/index.d.cts +370 -41
- package/lib/testing/index.d.cts.map +1 -1
- package/lib/testing/index.d.mts +370 -41
- package/lib/testing/index.d.mts.map +1 -1
- package/lib/testing/index.mjs +568 -305
- package/lib/testing/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/circular-detector.spec.mts +193 -0
- package/src/__tests__/concurrent.spec.mts +368 -0
- package/src/__tests__/container.spec.mts +32 -30
- package/src/__tests__/di-error.spec.mts +351 -0
- package/src/__tests__/e2e.browser.spec.mts +0 -4
- package/src/__tests__/e2e.spec.mts +10 -19
- package/src/__tests__/event-emitter.spec.mts +232 -109
- package/src/__tests__/get-injectors.spec.mts +250 -39
- package/src/__tests__/injection-token.spec.mts +293 -349
- package/src/__tests__/library-findings.spec.mts +8 -8
- package/src/__tests__/registry.spec.mts +358 -210
- package/src/__tests__/resolution-context.spec.mts +255 -0
- package/src/__tests__/scope-tracker.spec.mts +598 -0
- package/src/__tests__/scope-upgrade.spec.mts +808 -0
- package/src/__tests__/scoped-container.spec.mts +595 -0
- package/src/__tests__/test-container.spec.mts +293 -0
- package/src/__tests__/token-resolver.spec.mts +207 -0
- package/src/__tests__/unified-storage.spec.mts +535 -0
- package/src/__tests__/unit-test-container.spec.mts +405 -0
- package/src/__type-tests__/container.spec-d.mts +180 -0
- package/src/__type-tests__/factory.spec-d.mts +15 -3
- package/src/__type-tests__/inject.spec-d.mts +115 -20
- package/src/__type-tests__/injectable.spec-d.mts +69 -52
- package/src/__type-tests__/injection-token.spec-d.mts +176 -0
- package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
- package/src/container/abstract-container.mts +327 -0
- package/src/container/container.mts +142 -170
- package/src/container/scoped-container.mts +126 -208
- package/src/decorators/factory.decorator.mts +16 -11
- package/src/decorators/injectable.decorator.mts +20 -16
- package/src/enums/index.mts +2 -2
- package/src/enums/injectable-scope.enum.mts +1 -0
- package/src/enums/injectable-type.enum.mts +1 -0
- package/src/errors/di-error.mts +96 -0
- package/src/event-emitter.mts +3 -27
- package/src/index.mts +6 -153
- package/src/interfaces/container.interface.mts +13 -0
- package/src/interfaces/factory.interface.mts +1 -1
- package/src/interfaces/index.mts +1 -1
- package/src/internal/context/async-local-storage.mts +3 -2
- package/src/internal/context/async-local-storage.types.mts +1 -0
- package/src/internal/context/factory-context.mts +1 -0
- package/src/internal/context/index.mts +3 -1
- package/src/internal/context/resolution-context.mts +1 -0
- package/src/internal/context/service-initialization-context.mts +43 -0
- package/src/internal/core/index.mts +5 -4
- package/src/internal/core/instance-resolver.mts +460 -302
- package/src/internal/core/name-resolver.mts +196 -0
- package/src/internal/core/scope-tracker.mts +242 -0
- package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
- package/src/internal/core/service-invalidator.mts +290 -0
- package/src/internal/core/token-resolver.mts +122 -0
- package/src/internal/holder/holder-storage.interface.mts +11 -5
- package/src/internal/holder/index.mts +2 -5
- package/src/internal/holder/instance-holder.mts +1 -3
- package/src/internal/holder/unified-storage.mts +245 -0
- package/src/internal/index.mts +2 -1
- package/src/internal/lifecycle/circular-detector.mts +1 -0
- package/src/internal/lifecycle/index.mts +1 -1
- package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
- package/src/internal/stub-factory-class.mts +16 -0
- package/src/symbols/injectable-token.mts +3 -1
- package/src/testing/index.mts +2 -0
- package/src/testing/test-container.mts +546 -85
- package/src/testing/types.mts +117 -0
- package/src/testing/unit-test-container.mts +509 -0
- package/src/token/injection-token.mts +41 -4
- package/src/token/registry.mts +75 -9
- package/src/utils/default-injectors.mts +16 -0
- package/src/utils/get-injectable-token.mts +2 -3
- package/src/utils/get-injectors.mts +26 -15
- package/src/utils/index.mts +3 -1
- package/src/utils/types.mts +1 -0
- package/tsdown.config.mts +11 -1
- package/lib/browser/index.d.mts.map +0 -1
- package/lib/browser/index.mjs.map +0 -1
- package/lib/container-Bp1W-pWJ.d.mts.map +0 -1
- package/lib/container-DAKOvAgr.mjs.map +0 -1
- package/lib/container-DENMeJ87.cjs.map +0 -1
- package/lib/container-YPwvmlK2.d.cts.map +0 -1
- package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
- package/src/__tests__/async-local-storage.spec.mts +0 -333
- package/src/__tests__/errors.spec.mts +0 -87
- package/src/__tests__/factory.spec.mts +0 -137
- package/src/__tests__/injectable.spec.mts +0 -246
- package/src/__tests__/request-scope.spec.mts +0 -416
- package/src/__tests__/service-instantiator.spec.mts +0 -410
- package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
- package/src/__tests__/service-locator-manager.spec.mts +0 -300
- package/src/__tests__/service-locator.spec.mts +0 -966
- package/src/__tests__/unified-api.spec.mts +0 -130
- package/src/browser.mts +0 -11
- package/src/injectors.mts +0 -18
- package/src/internal/context/request-context.mts +0 -225
- package/src/internal/core/invalidator.mts +0 -437
- package/src/internal/core/service-locator.mts +0 -202
- package/src/internal/core/token-processor.mts +0 -252
- package/src/internal/holder/base-holder-manager.mts +0 -334
- package/src/internal/holder/holder-manager.mts +0 -85
- package/src/internal/holder/request-storage.mts +0 -127
- package/src/internal/holder/singleton-storage.mts +0 -92
- package/src/testing/README.md +0 -80
- package/src/testing/__tests__/test-container.spec.mts +0 -173
|
@@ -1,120 +1,581 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type {
|
|
2
|
+
BindingBuilder,
|
|
3
|
+
DependencyGraph,
|
|
4
|
+
DependencyNode,
|
|
5
|
+
LifecycleRecord,
|
|
6
|
+
MethodCallRecord,
|
|
7
|
+
MockServiceStats,
|
|
8
|
+
TestContainerOptions,
|
|
9
|
+
} from './types.mjs'
|
|
4
10
|
|
|
5
11
|
import { Container } from '../container/container.mjs'
|
|
6
|
-
import { Injectable } from '../decorators/injectable.decorator.mjs'
|
|
7
12
|
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
13
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
14
|
+
import { globalRegistry, Registry } from '../token/registry.mjs'
|
|
15
|
+
import { getInjectableToken } from '../utils/get-injectable-token.mjs'
|
|
16
|
+
import { defaultInjectors } from '../utils/index.mjs'
|
|
17
|
+
|
|
18
|
+
type AnyToken = InjectionToken<any, any> | (new (...args: any[]) => any)
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
|
-
*
|
|
21
|
+
* TestContainer extends Container with testing utilities.
|
|
22
|
+
*
|
|
23
|
+
* Provides simple value/class binding for integration/e2e tests,
|
|
24
|
+
* plus assertion helpers and dependency graph inspection.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const container = new TestContainer()
|
|
29
|
+
*
|
|
30
|
+
* // Bind mock values
|
|
31
|
+
* container.bind(DatabaseToken).toValue(mockDatabase)
|
|
32
|
+
* container.bind(UserService).toClass(MockUserService)
|
|
33
|
+
*
|
|
34
|
+
* // Use container normally
|
|
35
|
+
* const service = await container.get(MyService)
|
|
36
|
+
*
|
|
37
|
+
* // Assert on container state
|
|
38
|
+
* container.expectResolved(MyService)
|
|
39
|
+
* container.expectSingleton(MyService)
|
|
40
|
+
* ```
|
|
13
41
|
*/
|
|
14
|
-
export class
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
)
|
|
42
|
+
export class TestContainer extends Container {
|
|
43
|
+
private readonly testRegistry: Registry
|
|
44
|
+
private readonly methodCalls = new Map<string, MethodCallRecord[]>()
|
|
45
|
+
private readonly lifecycleEvents = new Map<string, LifecycleRecord[]>()
|
|
46
|
+
private readonly instanceCounts = new Map<string, number>()
|
|
47
|
+
private readonly boundTokens = new Set<string>()
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a new TestContainer.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Configuration options
|
|
53
|
+
* @param options.parentRegistry - Parent registry. Defaults to globalRegistry.
|
|
54
|
+
* Pass `null` for a completely isolated container.
|
|
55
|
+
* @param options.logger - Optional logger for debugging.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* // Uses globalRegistry as parent (default)
|
|
60
|
+
* const container = new TestContainer()
|
|
61
|
+
*
|
|
62
|
+
* // Isolated container (no access to @Injectable classes)
|
|
63
|
+
* const isolated = new TestContainer({ parentRegistry: null })
|
|
64
|
+
*
|
|
65
|
+
* // Custom parent registry
|
|
66
|
+
* const custom = new TestContainer({ parentRegistry: myRegistry })
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
constructor(options: TestContainerOptions = {}) {
|
|
70
|
+
const { parentRegistry = globalRegistry, logger = null } = options
|
|
71
|
+
const testRegistry = parentRegistry
|
|
72
|
+
? new Registry(parentRegistry)
|
|
73
|
+
: new Registry()
|
|
74
|
+
super(testRegistry, logger, defaultInjectors)
|
|
75
|
+
this.testRegistry = testRegistry
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// BINDING API
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a binding builder for the given token.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* container.bind(UserService).toValue(mockUserService)
|
|
88
|
+
* container.bind(DatabaseToken).toClass(MockDatabase)
|
|
89
|
+
* container.bind(ConfigToken).toFactory(() => ({ apiKey: 'test' }))
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
bind<T>(
|
|
93
|
+
token: InjectionToken<T, any> | (new (...args: any[]) => T),
|
|
94
|
+
): BindingBuilder<T> {
|
|
95
|
+
const realToken = this.resolveToken(token)
|
|
96
|
+
const tokenId = realToken.id
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
toValue: (value: T) => {
|
|
100
|
+
this.boundTokens.add(tokenId)
|
|
101
|
+
this.registerValueBinding(realToken, value)
|
|
102
|
+
},
|
|
103
|
+
toClass: <C extends new (...args: any[]) => T>(cls: C) => {
|
|
104
|
+
this.boundTokens.add(tokenId)
|
|
105
|
+
this.registerClassBinding(realToken, cls)
|
|
106
|
+
},
|
|
107
|
+
toFactory: (factory: () => T | Promise<T>) => {
|
|
108
|
+
this.boundTokens.add(tokenId)
|
|
109
|
+
this.registerFactoryBinding(realToken, factory)
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
}
|
|
19
113
|
|
|
20
114
|
/**
|
|
21
|
-
*
|
|
22
|
-
* This is useful for testing with mock values or constants.
|
|
23
|
-
* @param value The value to bind to the token
|
|
115
|
+
* Clears all bindings and resets container state.
|
|
24
116
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
117
|
+
async clear(): Promise<void> {
|
|
118
|
+
await this.dispose()
|
|
119
|
+
this.methodCalls.clear()
|
|
120
|
+
this.lifecycleEvents.clear()
|
|
121
|
+
this.instanceCounts.clear()
|
|
122
|
+
this.boundTokens.clear()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// ASSERTION HELPERS
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Asserts that a service has been resolved at least once.
|
|
131
|
+
*/
|
|
132
|
+
expectResolved(token: AnyToken): void {
|
|
133
|
+
const realToken = this.resolveToken(token)
|
|
134
|
+
const storage = this.getStorage()
|
|
135
|
+
const names = storage.getAllNames()
|
|
136
|
+
const found = names.some((name) => name.includes(realToken.id))
|
|
137
|
+
|
|
138
|
+
if (!found) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Expected ${realToken.toString()} to be resolved, but it was not`,
|
|
37
141
|
)
|
|
38
|
-
|
|
142
|
+
}
|
|
39
143
|
}
|
|
40
144
|
|
|
41
145
|
/**
|
|
42
|
-
*
|
|
43
|
-
* @param target The class constructor to bind to
|
|
146
|
+
* Asserts that a service has NOT been resolved.
|
|
44
147
|
*/
|
|
45
|
-
|
|
46
|
-
this.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
148
|
+
expectNotResolved(token: AnyToken): void {
|
|
149
|
+
const realToken = this.resolveToken(token)
|
|
150
|
+
const storage = this.getStorage()
|
|
151
|
+
const names = storage.getAllNames()
|
|
152
|
+
const found = names.some((name) => name.includes(realToken.id))
|
|
153
|
+
|
|
154
|
+
if (found) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Expected ${realToken.toString()} to NOT be resolved, but it was`,
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Asserts that a service is registered as singleton scope.
|
|
163
|
+
*/
|
|
164
|
+
expectSingleton(token: AnyToken): void {
|
|
165
|
+
const realToken = this.resolveToken(token)
|
|
166
|
+
const registry = this.getRegistry()
|
|
167
|
+
|
|
168
|
+
if (!registry.has(realToken)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Expected ${realToken.toString()} to be registered, but it was not`,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const record = registry.get(realToken)
|
|
175
|
+
if (record.scope !== InjectableScope.Singleton) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Expected ${realToken.toString()} to be Singleton scope, but it was ${record.scope}`,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Asserts that a service is registered as transient scope.
|
|
184
|
+
*/
|
|
185
|
+
expectTransient(token: AnyToken): void {
|
|
186
|
+
const realToken = this.resolveToken(token)
|
|
187
|
+
const registry = this.getRegistry()
|
|
188
|
+
|
|
189
|
+
if (!registry.has(realToken)) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Expected ${realToken.toString()} to be registered, but it was not`,
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const record = registry.get(realToken)
|
|
196
|
+
if (record.scope !== InjectableScope.Transient) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Expected ${realToken.toString()} to be Transient scope, but it was ${record.scope}`,
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Asserts that a service is registered as request scope.
|
|
205
|
+
*/
|
|
206
|
+
expectRequestScoped(token: AnyToken): void {
|
|
207
|
+
const realToken = this.resolveToken(token)
|
|
208
|
+
const registry = this.getRegistry()
|
|
209
|
+
|
|
210
|
+
if (!registry.has(realToken)) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Expected ${realToken.toString()} to be registered, but it was not`,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const record = registry.get(realToken)
|
|
217
|
+
if (record.scope !== InjectableScope.Request) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Expected ${realToken.toString()} to be Request scope, but it was ${record.scope}`,
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Asserts that two service resolutions return the same instance.
|
|
226
|
+
*/
|
|
227
|
+
async expectSameInstance(token: AnyToken): Promise<void> {
|
|
228
|
+
const instance1 = await this.get(token as any)
|
|
229
|
+
const instance2 = await this.get(token as any)
|
|
230
|
+
|
|
231
|
+
if (instance1 !== instance2) {
|
|
232
|
+
const realToken = this.resolveToken(token)
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Expected ${realToken.toString()} to return same instance, but got different instances`,
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Asserts that two service resolutions return different instances.
|
|
241
|
+
*/
|
|
242
|
+
async expectDifferentInstances(token: AnyToken): Promise<void> {
|
|
243
|
+
const instance1 = await this.get(token as any)
|
|
244
|
+
const instance2 = await this.get(token as any)
|
|
245
|
+
|
|
246
|
+
if (instance1 === instance2) {
|
|
247
|
+
const realToken = this.resolveToken(token)
|
|
248
|
+
throw new Error(
|
|
249
|
+
`Expected ${realToken.toString()} to return different instances, but got same instance`,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// LIFECYCLE ASSERTIONS
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Asserts that a service's onServiceInit was called.
|
|
260
|
+
*/
|
|
261
|
+
expectInitialized(token: AnyToken): void {
|
|
262
|
+
const realToken = this.resolveToken(token)
|
|
263
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
264
|
+
const initialized = events.some((e) => e.event === 'initialized')
|
|
265
|
+
|
|
266
|
+
if (!initialized) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Expected ${realToken.toString()} to be initialized, but onServiceInit was not called`,
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Asserts that a service's onServiceDestroy was called.
|
|
275
|
+
*/
|
|
276
|
+
expectDestroyed(token: AnyToken): void {
|
|
277
|
+
const realToken = this.resolveToken(token)
|
|
278
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
279
|
+
const destroyed = events.some((e) => e.event === 'destroyed')
|
|
280
|
+
|
|
281
|
+
if (!destroyed) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`Expected ${realToken.toString()} to be destroyed, but onServiceDestroy was not called`,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Asserts that a service has NOT been destroyed.
|
|
290
|
+
*/
|
|
291
|
+
expectNotDestroyed(token: AnyToken): void {
|
|
292
|
+
const realToken = this.resolveToken(token)
|
|
293
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
294
|
+
const destroyed = events.some((e) => e.event === 'destroyed')
|
|
295
|
+
|
|
296
|
+
if (destroyed) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
`Expected ${realToken.toString()} to NOT be destroyed, but onServiceDestroy was called`,
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ============================================================================
|
|
304
|
+
// CALL TRACKING
|
|
305
|
+
// ============================================================================
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Records a method call for tracking.
|
|
309
|
+
* Call this from your mock implementations.
|
|
310
|
+
*/
|
|
311
|
+
recordMethodCall(
|
|
312
|
+
token: AnyToken,
|
|
313
|
+
method: string,
|
|
314
|
+
args: unknown[],
|
|
315
|
+
result?: unknown,
|
|
316
|
+
error?: Error,
|
|
317
|
+
): void {
|
|
318
|
+
const realToken = this.resolveToken(token)
|
|
319
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
320
|
+
calls.push({
|
|
321
|
+
method,
|
|
322
|
+
args,
|
|
323
|
+
timestamp: Date.now(),
|
|
324
|
+
result,
|
|
325
|
+
error,
|
|
326
|
+
})
|
|
327
|
+
this.methodCalls.set(realToken.id, calls)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Records a lifecycle event for tracking.
|
|
332
|
+
*/
|
|
333
|
+
recordLifecycleEvent(
|
|
334
|
+
token: AnyToken,
|
|
335
|
+
event: 'created' | 'initialized' | 'destroyed',
|
|
336
|
+
instanceName: string,
|
|
337
|
+
): void {
|
|
338
|
+
const realToken = this.resolveToken(token)
|
|
339
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
340
|
+
events.push({
|
|
341
|
+
event,
|
|
342
|
+
timestamp: Date.now(),
|
|
343
|
+
instanceName,
|
|
344
|
+
})
|
|
345
|
+
this.lifecycleEvents.set(realToken.id, events)
|
|
346
|
+
|
|
347
|
+
if (event === 'created') {
|
|
348
|
+
const count = this.instanceCounts.get(realToken.id) || 0
|
|
349
|
+
this.instanceCounts.set(realToken.id, count + 1)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Asserts that a method was called on a service.
|
|
355
|
+
*/
|
|
356
|
+
expectCalled(token: AnyToken, method: string): void {
|
|
357
|
+
const realToken = this.resolveToken(token)
|
|
358
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
359
|
+
const found = calls.some((c) => c.method === method)
|
|
360
|
+
|
|
361
|
+
if (!found) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Expected ${realToken.toString()}.${method}() to be called, but it was not`,
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Asserts that a method was called with specific arguments.
|
|
370
|
+
*/
|
|
371
|
+
expectCalledWith(
|
|
372
|
+
token: AnyToken,
|
|
373
|
+
method: string,
|
|
374
|
+
expectedArgs: unknown[],
|
|
375
|
+
): void {
|
|
376
|
+
const realToken = this.resolveToken(token)
|
|
377
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
378
|
+
const found = calls.some(
|
|
379
|
+
(c) => c.method === method && this.argsMatch(c.args, expectedArgs),
|
|
51
380
|
)
|
|
52
|
-
|
|
381
|
+
|
|
382
|
+
if (!found) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Expected ${realToken.toString()}.${method}() to be called with ${JSON.stringify(expectedArgs)}, but it was not`,
|
|
385
|
+
)
|
|
386
|
+
}
|
|
53
387
|
}
|
|
54
|
-
}
|
|
55
388
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Asserts that a method was called a specific number of times.
|
|
391
|
+
*/
|
|
392
|
+
expectCallCount(token: AnyToken, method: string, count: number): void {
|
|
393
|
+
const realToken = this.resolveToken(token)
|
|
394
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
395
|
+
const actualCount = calls.filter((c) => c.method === method).length
|
|
396
|
+
|
|
397
|
+
if (actualCount !== count) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Expected ${realToken.toString()}.${method}() to be called ${count} times, but was called ${actualCount} times`,
|
|
400
|
+
)
|
|
401
|
+
}
|
|
68
402
|
}
|
|
69
403
|
|
|
70
404
|
/**
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
405
|
+
* Gets all recorded method calls for a service.
|
|
406
|
+
*/
|
|
407
|
+
getMethodCalls(token: AnyToken): MethodCallRecord[] {
|
|
408
|
+
const realToken = this.resolveToken(token)
|
|
409
|
+
return this.methodCalls.get(realToken.id) || []
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Gets statistics about a mocked service.
|
|
414
|
+
*/
|
|
415
|
+
getServiceStats(token: AnyToken): MockServiceStats {
|
|
416
|
+
const realToken = this.resolveToken(token)
|
|
417
|
+
return {
|
|
418
|
+
instanceCount: this.instanceCounts.get(realToken.id) || 0,
|
|
419
|
+
methodCalls: this.methodCalls.get(realToken.id) || [],
|
|
420
|
+
lifecycleEvents: this.lifecycleEvents.get(realToken.id) || [],
|
|
82
421
|
}
|
|
83
|
-
return new TestBindingBuilder(this, realToken)
|
|
84
422
|
}
|
|
85
423
|
|
|
86
424
|
/**
|
|
87
|
-
*
|
|
88
|
-
* This is a convenience method equivalent to bind(token).toValue(value).
|
|
89
|
-
* @param token The injection token to bind
|
|
90
|
-
* @param value The value to bind to the token
|
|
91
|
-
* @returns The TestContainer instance for chaining
|
|
425
|
+
* Clears all recorded method calls.
|
|
92
426
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
bindValue(token: any, value: any): TestContainer {
|
|
96
|
-
return this.bind(token).toValue(value)
|
|
427
|
+
clearMethodCalls(): void {
|
|
428
|
+
this.methodCalls.clear()
|
|
97
429
|
}
|
|
98
430
|
|
|
431
|
+
// ============================================================================
|
|
432
|
+
// DEPENDENCY GRAPH
|
|
433
|
+
// ============================================================================
|
|
434
|
+
|
|
99
435
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* @param token The injection token to bind
|
|
103
|
-
* @param target The class constructor to bind to
|
|
104
|
-
* @returns The TestContainer instance for chaining
|
|
436
|
+
* Gets the dependency graph for snapshot testing.
|
|
437
|
+
* Returns a serializable structure that can be used with vitest snapshots.
|
|
105
438
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
439
|
+
getDependencyGraph(): DependencyGraph {
|
|
440
|
+
const storage = this.getStorage()
|
|
441
|
+
const nodes: Record<string, DependencyNode> = {}
|
|
442
|
+
const rootTokens: string[] = []
|
|
443
|
+
|
|
444
|
+
storage.forEach((name, holder) => {
|
|
445
|
+
const tokenMatch = name.match(/^([^:]+)/)
|
|
446
|
+
const tokenId = tokenMatch ? tokenMatch[1] : name
|
|
447
|
+
|
|
448
|
+
nodes[name] = {
|
|
449
|
+
token: tokenId,
|
|
450
|
+
instanceName: name,
|
|
451
|
+
scope: holder.scope,
|
|
452
|
+
dependencies: Array.from(holder.deps),
|
|
453
|
+
dependents: storage.findDependents(name),
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Root tokens have no dependents
|
|
457
|
+
if (storage.findDependents(name).length === 0) {
|
|
458
|
+
rootTokens.push(name)
|
|
459
|
+
}
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
return { nodes, rootTokens }
|
|
110
463
|
}
|
|
111
464
|
|
|
112
465
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @returns A new TestContainer instance
|
|
466
|
+
* Gets a simplified dependency graph showing only token relationships.
|
|
467
|
+
* Useful for cleaner snapshot comparisons.
|
|
116
468
|
*/
|
|
117
|
-
|
|
118
|
-
|
|
469
|
+
getSimplifiedDependencyGraph(): Record<string, string[]> {
|
|
470
|
+
const storage = this.getStorage()
|
|
471
|
+
const graph: Record<string, string[]> = {}
|
|
472
|
+
|
|
473
|
+
storage.forEach((name, holder) => {
|
|
474
|
+
const tokenMatch = name.match(/^([^:]+)/)
|
|
475
|
+
const tokenId = tokenMatch ? tokenMatch[1] : name
|
|
476
|
+
|
|
477
|
+
if (!graph[tokenId]) {
|
|
478
|
+
graph[tokenId] = []
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
for (const dep of holder.deps) {
|
|
482
|
+
const depTokenMatch = dep.match(/^([^:]+)/)
|
|
483
|
+
const depTokenId = depTokenMatch ? depTokenMatch[1] : dep
|
|
484
|
+
if (!graph[tokenId].includes(depTokenId)) {
|
|
485
|
+
graph[tokenId].push(depTokenId)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
// Sort for consistent snapshots
|
|
491
|
+
for (const key of Object.keys(graph)) {
|
|
492
|
+
graph[key].sort()
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return graph
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ============================================================================
|
|
499
|
+
// INTERNAL HELPERS
|
|
500
|
+
// ============================================================================
|
|
501
|
+
|
|
502
|
+
private resolveToken(token: AnyToken): InjectionToken<any, any> {
|
|
503
|
+
if (typeof token === 'function') {
|
|
504
|
+
return getInjectableToken(token)
|
|
505
|
+
}
|
|
506
|
+
return token
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private registerValueBinding<T>(
|
|
510
|
+
token: InjectionToken<T, any>,
|
|
511
|
+
value: T,
|
|
512
|
+
): void {
|
|
513
|
+
// Create a simple class that returns the value
|
|
514
|
+
const ValueHolder = class {
|
|
515
|
+
static instance = value
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
this.testRegistry.set(
|
|
519
|
+
token,
|
|
520
|
+
InjectableScope.Singleton,
|
|
521
|
+
ValueHolder,
|
|
522
|
+
InjectableType.Class,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
// Store the instance directly
|
|
526
|
+
const nameResolver = this.getNameResolver()
|
|
527
|
+
const instanceName = nameResolver.generateInstanceName(
|
|
528
|
+
token,
|
|
529
|
+
undefined,
|
|
530
|
+
undefined,
|
|
531
|
+
InjectableScope.Singleton,
|
|
532
|
+
)
|
|
533
|
+
this.getStorage().storeInstance(instanceName, value)
|
|
534
|
+
this.recordLifecycleEvent(token, 'created', instanceName)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private registerClassBinding<T>(
|
|
538
|
+
token: InjectionToken<T, any>,
|
|
539
|
+
cls: new (...args: any[]) => T,
|
|
540
|
+
): void {
|
|
541
|
+
this.testRegistry.set(
|
|
542
|
+
token,
|
|
543
|
+
InjectableScope.Singleton,
|
|
544
|
+
cls,
|
|
545
|
+
InjectableType.Class,
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private registerFactoryBinding<T>(
|
|
550
|
+
token: InjectionToken<T, any>,
|
|
551
|
+
factory: () => T | Promise<T>,
|
|
552
|
+
): void {
|
|
553
|
+
// Create a factory class wrapper
|
|
554
|
+
const FactoryWrapper = class {
|
|
555
|
+
static factory = factory
|
|
556
|
+
async create(): Promise<T> {
|
|
557
|
+
return await factory()
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
this.testRegistry.set(
|
|
562
|
+
token,
|
|
563
|
+
InjectableScope.Singleton,
|
|
564
|
+
FactoryWrapper,
|
|
565
|
+
InjectableType.Factory,
|
|
566
|
+
)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private argsMatch(actual: unknown[], expected: unknown[]): boolean {
|
|
570
|
+
if (actual.length !== expected.length) {
|
|
571
|
+
return false
|
|
572
|
+
}
|
|
573
|
+
return actual.every((arg, index) => {
|
|
574
|
+
const exp = expected[index]
|
|
575
|
+
if (typeof exp === 'object' && exp !== null) {
|
|
576
|
+
return JSON.stringify(arg) === JSON.stringify(exp)
|
|
577
|
+
}
|
|
578
|
+
return arg === exp
|
|
579
|
+
})
|
|
119
580
|
}
|
|
120
581
|
}
|