@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
package/lib/testing/index.cjs
CHANGED
|
@@ -1,348 +1,606 @@
|
|
|
1
|
-
const require_container = require('../container-
|
|
1
|
+
const require_container = require('../container-CaY2fDuk.cjs');
|
|
2
2
|
|
|
3
3
|
//#region src/testing/test-container.mts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
/**
|
|
5
|
+
* TestContainer extends Container with testing utilities.
|
|
6
|
+
*
|
|
7
|
+
* Provides simple value/class binding for integration/e2e tests,
|
|
8
|
+
* plus assertion helpers and dependency graph inspection.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const container = new TestContainer()
|
|
13
|
+
*
|
|
14
|
+
* // Bind mock values
|
|
15
|
+
* container.bind(DatabaseToken).toValue(mockDatabase)
|
|
16
|
+
* container.bind(UserService).toClass(MockUserService)
|
|
17
|
+
*
|
|
18
|
+
* // Use container normally
|
|
19
|
+
* const service = await container.get(MyService)
|
|
20
|
+
*
|
|
21
|
+
* // Assert on container state
|
|
22
|
+
* container.expectResolved(MyService)
|
|
23
|
+
* container.expectSingleton(MyService)
|
|
24
|
+
* ```
|
|
25
|
+
*/ var TestContainer = class extends require_container._Container {
|
|
26
|
+
testRegistry;
|
|
27
|
+
methodCalls = /* @__PURE__ */ new Map();
|
|
28
|
+
lifecycleEvents = /* @__PURE__ */ new Map();
|
|
29
|
+
instanceCounts = /* @__PURE__ */ new Map();
|
|
30
|
+
boundTokens = /* @__PURE__ */ new Set();
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new TestContainer.
|
|
33
|
+
*
|
|
34
|
+
* @param options - Configuration options
|
|
35
|
+
* @param options.parentRegistry - Parent registry. Defaults to globalRegistry.
|
|
36
|
+
* Pass `null` for a completely isolated container.
|
|
37
|
+
* @param options.logger - Optional logger for debugging.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* // Uses globalRegistry as parent (default)
|
|
42
|
+
* const container = new TestContainer()
|
|
43
|
+
*
|
|
44
|
+
* // Isolated container (no access to @Injectable classes)
|
|
45
|
+
* const isolated = new TestContainer({ parentRegistry: null })
|
|
46
|
+
*
|
|
47
|
+
* // Custom parent registry
|
|
48
|
+
* const custom = new TestContainer({ parentRegistry: myRegistry })
|
|
49
|
+
* ```
|
|
50
|
+
*/ constructor(options = {}) {
|
|
51
|
+
const { parentRegistry = require_container.globalRegistry, logger = null } = options;
|
|
52
|
+
const testRegistry = parentRegistry ? new require_container.Registry(parentRegistry) : new require_container.Registry();
|
|
53
|
+
super(testRegistry, logger, require_container.defaultInjectors);
|
|
54
|
+
this.testRegistry = testRegistry;
|
|
11
55
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef);
|
|
38
|
-
var get, set;
|
|
39
|
-
if (kind === 0) if (isPrivate) {
|
|
40
|
-
get = desc.get;
|
|
41
|
-
set = desc.set;
|
|
42
|
-
} else {
|
|
43
|
-
get = function() {
|
|
44
|
-
return this[name];
|
|
45
|
-
};
|
|
46
|
-
set = function(v) {
|
|
47
|
-
this[name] = v;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
else if (kind === 2) get = function() {
|
|
51
|
-
return desc.value;
|
|
56
|
+
/**
|
|
57
|
+
* Creates a binding builder for the given token.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* container.bind(UserService).toValue(mockUserService)
|
|
62
|
+
* container.bind(DatabaseToken).toClass(MockDatabase)
|
|
63
|
+
* container.bind(ConfigToken).toFactory(() => ({ apiKey: 'test' }))
|
|
64
|
+
* ```
|
|
65
|
+
*/ bind(token) {
|
|
66
|
+
const realToken = this.resolveToken(token);
|
|
67
|
+
const tokenId = realToken.id;
|
|
68
|
+
return {
|
|
69
|
+
toValue: (value) => {
|
|
70
|
+
this.boundTokens.add(tokenId);
|
|
71
|
+
this.registerValueBinding(realToken, value);
|
|
72
|
+
},
|
|
73
|
+
toClass: (cls) => {
|
|
74
|
+
this.boundTokens.add(tokenId);
|
|
75
|
+
this.registerClassBinding(realToken, cls);
|
|
76
|
+
},
|
|
77
|
+
toFactory: (factory) => {
|
|
78
|
+
this.boundTokens.add(tokenId);
|
|
79
|
+
this.registerFactoryBinding(realToken, factory);
|
|
80
|
+
}
|
|
52
81
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Clears all bindings and resets container state.
|
|
85
|
+
*/ async clear() {
|
|
86
|
+
await this.dispose();
|
|
87
|
+
this.methodCalls.clear();
|
|
88
|
+
this.lifecycleEvents.clear();
|
|
89
|
+
this.instanceCounts.clear();
|
|
90
|
+
this.boundTokens.clear();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Asserts that a service has been resolved at least once.
|
|
94
|
+
*/ expectResolved(token) {
|
|
95
|
+
const realToken = this.resolveToken(token);
|
|
96
|
+
if (!this.getStorage().getAllNames().some((name) => name.includes(realToken.id))) throw new Error(`Expected ${realToken.toString()} to be resolved, but it was not`);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Asserts that a service has NOT been resolved.
|
|
100
|
+
*/ expectNotResolved(token) {
|
|
101
|
+
const realToken = this.resolveToken(token);
|
|
102
|
+
if (this.getStorage().getAllNames().some((name) => name.includes(realToken.id))) throw new Error(`Expected ${realToken.toString()} to NOT be resolved, but it was`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Asserts that a service is registered as singleton scope.
|
|
106
|
+
*/ expectSingleton(token) {
|
|
107
|
+
const realToken = this.resolveToken(token);
|
|
108
|
+
const registry = this.getRegistry();
|
|
109
|
+
if (!registry.has(realToken)) throw new Error(`Expected ${realToken.toString()} to be registered, but it was not`);
|
|
110
|
+
const record = registry.get(realToken);
|
|
111
|
+
if (record.scope !== require_container.InjectableScope.Singleton) throw new Error(`Expected ${realToken.toString()} to be Singleton scope, but it was ${record.scope}`);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Asserts that a service is registered as transient scope.
|
|
115
|
+
*/ expectTransient(token) {
|
|
116
|
+
const realToken = this.resolveToken(token);
|
|
117
|
+
const registry = this.getRegistry();
|
|
118
|
+
if (!registry.has(realToken)) throw new Error(`Expected ${realToken.toString()} to be registered, but it was not`);
|
|
119
|
+
const record = registry.get(realToken);
|
|
120
|
+
if (record.scope !== require_container.InjectableScope.Transient) throw new Error(`Expected ${realToken.toString()} to be Transient scope, but it was ${record.scope}`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Asserts that a service is registered as request scope.
|
|
124
|
+
*/ expectRequestScoped(token) {
|
|
125
|
+
const realToken = this.resolveToken(token);
|
|
126
|
+
const registry = this.getRegistry();
|
|
127
|
+
if (!registry.has(realToken)) throw new Error(`Expected ${realToken.toString()} to be registered, but it was not`);
|
|
128
|
+
const record = registry.get(realToken);
|
|
129
|
+
if (record.scope !== require_container.InjectableScope.Request) throw new Error(`Expected ${realToken.toString()} to be Request scope, but it was ${record.scope}`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Asserts that two service resolutions return the same instance.
|
|
133
|
+
*/ async expectSameInstance(token) {
|
|
134
|
+
if (await this.get(token) !== await this.get(token)) {
|
|
135
|
+
const realToken = this.resolveToken(token);
|
|
136
|
+
throw new Error(`Expected ${realToken.toString()} to return same instance, but got different instances`);
|
|
60
137
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
decoratorFinishedRef.v = true;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Asserts that two service resolutions return different instances.
|
|
141
|
+
*/ async expectDifferentInstances(token) {
|
|
142
|
+
if (await this.get(token) === await this.get(token)) {
|
|
143
|
+
const realToken = this.resolveToken(token);
|
|
144
|
+
throw new Error(`Expected ${realToken.toString()} to return different instances, but got same instance`);
|
|
69
145
|
}
|
|
70
146
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Asserts that a service's onServiceInit was called.
|
|
149
|
+
*/ expectInitialized(token) {
|
|
150
|
+
const realToken = this.resolveToken(token);
|
|
151
|
+
if (!(this.lifecycleEvents.get(realToken.id) || []).some((e) => e.event === "initialized")) throw new Error(`Expected ${realToken.toString()} to be initialized, but onServiceInit was not called`);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Asserts that a service's onServiceDestroy was called.
|
|
155
|
+
*/ expectDestroyed(token) {
|
|
156
|
+
const realToken = this.resolveToken(token);
|
|
157
|
+
if (!(this.lifecycleEvents.get(realToken.id) || []).some((e) => e.event === "destroyed")) throw new Error(`Expected ${realToken.toString()} to be destroyed, but onServiceDestroy was not called`);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Asserts that a service has NOT been destroyed.
|
|
161
|
+
*/ expectNotDestroyed(token) {
|
|
162
|
+
const realToken = this.resolveToken(token);
|
|
163
|
+
if ((this.lifecycleEvents.get(realToken.id) || []).some((e) => e.event === "destroyed")) throw new Error(`Expected ${realToken.toString()} to NOT be destroyed, but onServiceDestroy was called`);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Records a method call for tracking.
|
|
167
|
+
* Call this from your mock implementations.
|
|
168
|
+
*/ recordMethodCall(token, method, args, result, error) {
|
|
169
|
+
const realToken = this.resolveToken(token);
|
|
170
|
+
const calls = this.methodCalls.get(realToken.id) || [];
|
|
171
|
+
calls.push({
|
|
172
|
+
method,
|
|
173
|
+
args,
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
result,
|
|
176
|
+
error
|
|
177
|
+
});
|
|
178
|
+
this.methodCalls.set(realToken.id, calls);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Records a lifecycle event for tracking.
|
|
182
|
+
*/ recordLifecycleEvent(token, event, instanceName) {
|
|
183
|
+
const realToken = this.resolveToken(token);
|
|
184
|
+
const events = this.lifecycleEvents.get(realToken.id) || [];
|
|
185
|
+
events.push({
|
|
186
|
+
event,
|
|
187
|
+
timestamp: Date.now(),
|
|
188
|
+
instanceName
|
|
189
|
+
});
|
|
190
|
+
this.lifecycleEvents.set(realToken.id, events);
|
|
191
|
+
if (event === "created") {
|
|
192
|
+
const count = this.instanceCounts.get(realToken.id) || 0;
|
|
193
|
+
this.instanceCounts.set(realToken.id, count + 1);
|
|
90
194
|
}
|
|
91
195
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Asserts that a method was called on a service.
|
|
198
|
+
*/ expectCalled(token, method) {
|
|
199
|
+
const realToken = this.resolveToken(token);
|
|
200
|
+
if (!(this.methodCalls.get(realToken.id) || []).some((c) => c.method === method)) throw new Error(`Expected ${realToken.toString()}.${method}() to be called, but it was not`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Asserts that a method was called with specific arguments.
|
|
204
|
+
*/ expectCalledWith(token, method, expectedArgs) {
|
|
205
|
+
const realToken = this.resolveToken(token);
|
|
206
|
+
if (!(this.methodCalls.get(realToken.id) || []).some((c) => c.method === method && this.argsMatch(c.args, expectedArgs))) throw new Error(`Expected ${realToken.toString()}.${method}() to be called with ${JSON.stringify(expectedArgs)}, but it was not`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Asserts that a method was called a specific number of times.
|
|
210
|
+
*/ expectCallCount(token, method, count) {
|
|
211
|
+
const realToken = this.resolveToken(token);
|
|
212
|
+
const actualCount = (this.methodCalls.get(realToken.id) || []).filter((c) => c.method === method).length;
|
|
213
|
+
if (actualCount !== count) throw new Error(`Expected ${realToken.toString()}.${method}() to be called ${count} times, but was called ${actualCount} times`);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Gets all recorded method calls for a service.
|
|
217
|
+
*/ getMethodCalls(token) {
|
|
218
|
+
const realToken = this.resolveToken(token);
|
|
219
|
+
return this.methodCalls.get(realToken.id) || [];
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Gets statistics about a mocked service.
|
|
223
|
+
*/ getServiceStats(token) {
|
|
224
|
+
const realToken = this.resolveToken(token);
|
|
225
|
+
return {
|
|
226
|
+
instanceCount: this.instanceCounts.get(realToken.id) || 0,
|
|
227
|
+
methodCalls: this.methodCalls.get(realToken.id) || [],
|
|
228
|
+
lifecycleEvents: this.lifecycleEvents.get(realToken.id) || []
|
|
106
229
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
newValue = memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
|
|
129
|
-
if (newValue !== void 0) {
|
|
130
|
-
assertValidReturnValue(kind, newValue);
|
|
131
|
-
var newInit;
|
|
132
|
-
if (kind === 0) newInit = newValue;
|
|
133
|
-
else if (kind === 1) {
|
|
134
|
-
newInit = newValue.init;
|
|
135
|
-
get = newValue.get || value.get;
|
|
136
|
-
set = newValue.set || value.set;
|
|
137
|
-
value = {
|
|
138
|
-
get,
|
|
139
|
-
set
|
|
140
|
-
};
|
|
141
|
-
} else value = newValue;
|
|
142
|
-
if (newInit !== void 0) if (init === void 0) init = newInit;
|
|
143
|
-
else if (typeof init === "function") init = [init, newInit];
|
|
144
|
-
else init.push(newInit);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (kind === 0 || kind === 1) {
|
|
148
|
-
if (init === void 0) init = function(instance, init$1) {
|
|
149
|
-
return init$1;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Clears all recorded method calls.
|
|
233
|
+
*/ clearMethodCalls() {
|
|
234
|
+
this.methodCalls.clear();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Gets the dependency graph for snapshot testing.
|
|
238
|
+
* Returns a serializable structure that can be used with vitest snapshots.
|
|
239
|
+
*/ getDependencyGraph() {
|
|
240
|
+
const storage = this.getStorage();
|
|
241
|
+
const nodes = {};
|
|
242
|
+
const rootTokens = [];
|
|
243
|
+
storage.forEach((name, holder) => {
|
|
244
|
+
const tokenMatch = name.match(/^([^:]+)/);
|
|
245
|
+
nodes[name] = {
|
|
246
|
+
token: tokenMatch ? tokenMatch[1] : name,
|
|
247
|
+
instanceName: name,
|
|
248
|
+
scope: holder.scope,
|
|
249
|
+
dependencies: Array.from(holder.deps),
|
|
250
|
+
dependents: storage.findDependents(name)
|
|
150
251
|
};
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
};
|
|
158
|
-
} else {
|
|
159
|
-
var originalInitializer = init;
|
|
160
|
-
init = function(instance, init$1) {
|
|
161
|
-
return originalInitializer.call(instance, init$1);
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
ret.push(init);
|
|
165
|
-
}
|
|
166
|
-
if (kind !== 0) {
|
|
167
|
-
if (kind === 1) {
|
|
168
|
-
desc.get = value.get;
|
|
169
|
-
desc.set = value.set;
|
|
170
|
-
} else if (kind === 2) desc.value = value;
|
|
171
|
-
else if (kind === 3) desc.get = value;
|
|
172
|
-
else if (kind === 4) desc.set = value;
|
|
173
|
-
if (isPrivate) if (kind === 1) {
|
|
174
|
-
ret.push(function(instance, args) {
|
|
175
|
-
return value.get.call(instance, args);
|
|
176
|
-
});
|
|
177
|
-
ret.push(function(instance, args) {
|
|
178
|
-
return value.set.call(instance, args);
|
|
179
|
-
});
|
|
180
|
-
} else if (kind === 2) ret.push(value);
|
|
181
|
-
else ret.push(function(instance, args) {
|
|
182
|
-
return value.call(instance, args);
|
|
183
|
-
});
|
|
184
|
-
else Object.defineProperty(base, name, desc);
|
|
185
|
-
}
|
|
252
|
+
if (storage.findDependents(name).length === 0) rootTokens.push(name);
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
nodes,
|
|
256
|
+
rootTokens
|
|
257
|
+
};
|
|
186
258
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
var initializers;
|
|
202
|
-
if (isStatic) {
|
|
203
|
-
base = Class;
|
|
204
|
-
kind = kind - 5;
|
|
205
|
-
staticInitializers = staticInitializers || [];
|
|
206
|
-
initializers = staticInitializers;
|
|
207
|
-
} else {
|
|
208
|
-
base = Class.prototype;
|
|
209
|
-
protoInitializers = protoInitializers || [];
|
|
210
|
-
initializers = protoInitializers;
|
|
211
|
-
}
|
|
212
|
-
if (kind !== 0 && !isPrivate) {
|
|
213
|
-
var existingNonFields = isStatic ? existingStaticNonFields : existingProtoNonFields;
|
|
214
|
-
var existingKind = existingNonFields.get(name) || 0;
|
|
215
|
-
if (existingKind === true || existingKind === 3 && kind !== 4 || existingKind === 4 && kind !== 3) throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: " + name);
|
|
216
|
-
else if (!existingKind && kind > 2) existingNonFields.set(name, kind);
|
|
217
|
-
else existingNonFields.set(name, true);
|
|
259
|
+
/**
|
|
260
|
+
* Gets a simplified dependency graph showing only token relationships.
|
|
261
|
+
* Useful for cleaner snapshot comparisons.
|
|
262
|
+
*/ getSimplifiedDependencyGraph() {
|
|
263
|
+
const storage = this.getStorage();
|
|
264
|
+
const graph = {};
|
|
265
|
+
storage.forEach((name, holder) => {
|
|
266
|
+
const tokenMatch = name.match(/^([^:]+)/);
|
|
267
|
+
const tokenId = tokenMatch ? tokenMatch[1] : name;
|
|
268
|
+
if (!graph[tokenId]) graph[tokenId] = [];
|
|
269
|
+
for (const dep of holder.deps) {
|
|
270
|
+
const depTokenMatch = dep.match(/^([^:]+)/);
|
|
271
|
+
const depTokenId = depTokenMatch ? depTokenMatch[1] : dep;
|
|
272
|
+
if (!graph[tokenId].includes(depTokenId)) graph[tokenId].push(depTokenId);
|
|
218
273
|
}
|
|
219
|
-
applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata);
|
|
220
|
-
}
|
|
221
|
-
pushInitializers(ret, protoInitializers);
|
|
222
|
-
pushInitializers(ret, staticInitializers);
|
|
223
|
-
return ret;
|
|
224
|
-
}
|
|
225
|
-
function pushInitializers(ret, initializers) {
|
|
226
|
-
if (initializers) ret.push(function(instance) {
|
|
227
|
-
for (var i = 0; i < initializers.length; i++) initializers[i].call(instance);
|
|
228
|
-
return instance;
|
|
229
274
|
});
|
|
275
|
+
for (const key of Object.keys(graph)) graph[key].sort();
|
|
276
|
+
return graph;
|
|
277
|
+
}
|
|
278
|
+
resolveToken(token) {
|
|
279
|
+
if (typeof token === "function") return require_container.getInjectableToken(token);
|
|
280
|
+
return token;
|
|
281
|
+
}
|
|
282
|
+
registerValueBinding(token, value) {
|
|
283
|
+
const ValueHolder = class {
|
|
284
|
+
static instance = value;
|
|
285
|
+
};
|
|
286
|
+
this.testRegistry.set(token, require_container.InjectableScope.Singleton, ValueHolder, require_container.InjectableType.Class);
|
|
287
|
+
const instanceName = this.getNameResolver().generateInstanceName(token, void 0, void 0, require_container.InjectableScope.Singleton);
|
|
288
|
+
this.getStorage().storeInstance(instanceName, value);
|
|
289
|
+
this.recordLifecycleEvent(token, "created", instanceName);
|
|
290
|
+
}
|
|
291
|
+
registerClassBinding(token, cls) {
|
|
292
|
+
this.testRegistry.set(token, require_container.InjectableScope.Singleton, cls, require_container.InjectableType.Class);
|
|
230
293
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
for (var i = classDecs.length - 1; i >= 0; i--) {
|
|
237
|
-
var decoratorFinishedRef = { v: false };
|
|
238
|
-
try {
|
|
239
|
-
var nextNewClass = classDecs[i](newClass, {
|
|
240
|
-
kind: "class",
|
|
241
|
-
name,
|
|
242
|
-
addInitializer: createAddInitializerMethod(initializers, decoratorFinishedRef),
|
|
243
|
-
metadata
|
|
244
|
-
});
|
|
245
|
-
} finally {
|
|
246
|
-
decoratorFinishedRef.v = true;
|
|
247
|
-
}
|
|
248
|
-
if (nextNewClass !== void 0) {
|
|
249
|
-
assertValidReturnValue(10, nextNewClass);
|
|
250
|
-
newClass = nextNewClass;
|
|
251
|
-
}
|
|
294
|
+
registerFactoryBinding(token, factory) {
|
|
295
|
+
const FactoryWrapper = class {
|
|
296
|
+
static factory = factory;
|
|
297
|
+
async create() {
|
|
298
|
+
return await factory();
|
|
252
299
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}];
|
|
256
|
-
}
|
|
300
|
+
};
|
|
301
|
+
this.testRegistry.set(token, require_container.InjectableScope.Singleton, FactoryWrapper, require_container.InjectableType.Factory);
|
|
257
302
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
303
|
+
argsMatch(actual, expected) {
|
|
304
|
+
if (actual.length !== expected.length) return false;
|
|
305
|
+
return actual.every((arg, index) => {
|
|
306
|
+
const exp = expected[index];
|
|
307
|
+
if (typeof exp === "object" && exp !== null) return JSON.stringify(arg) === JSON.stringify(exp);
|
|
308
|
+
return arg === exp;
|
|
263
309
|
});
|
|
264
310
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/testing/unit-test-container.mts
|
|
315
|
+
/**
|
|
316
|
+
* Creates a tracking proxy that records method calls.
|
|
317
|
+
*/ function createTrackingProxy(target, tokenId, methodCalls) {
|
|
318
|
+
return new Proxy(target, { get(obj, prop) {
|
|
319
|
+
const value = Reflect.get(obj, prop);
|
|
320
|
+
if (typeof value === "function" && typeof prop === "string") return function(...args) {
|
|
321
|
+
const calls = methodCalls.get(tokenId) || [];
|
|
322
|
+
const record = {
|
|
323
|
+
method: prop,
|
|
324
|
+
args,
|
|
325
|
+
timestamp: Date.now()
|
|
326
|
+
};
|
|
327
|
+
try {
|
|
328
|
+
const result = value.apply(this === void 0 ? obj : this, args);
|
|
329
|
+
if (result instanceof Promise) return result.then((res) => {
|
|
330
|
+
record.result = res;
|
|
331
|
+
calls.push(record);
|
|
332
|
+
methodCalls.set(tokenId, calls);
|
|
333
|
+
return res;
|
|
334
|
+
}).catch((err) => {
|
|
335
|
+
record.error = err;
|
|
336
|
+
calls.push(record);
|
|
337
|
+
methodCalls.set(tokenId, calls);
|
|
338
|
+
throw err;
|
|
339
|
+
});
|
|
340
|
+
record.result = result;
|
|
341
|
+
calls.push(record);
|
|
342
|
+
methodCalls.set(tokenId, calls);
|
|
343
|
+
return result;
|
|
344
|
+
} catch (err) {
|
|
345
|
+
record.error = err;
|
|
346
|
+
calls.push(record);
|
|
347
|
+
methodCalls.set(tokenId, calls);
|
|
348
|
+
throw err;
|
|
274
349
|
}
|
|
275
350
|
};
|
|
276
|
-
|
|
351
|
+
return value;
|
|
352
|
+
} });
|
|
277
353
|
}
|
|
278
|
-
|
|
279
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Creates an auto-mock proxy that throws on method access.
|
|
356
|
+
*/ function createAutoMockProxy(tokenId) {
|
|
357
|
+
return new Proxy({}, { get(_, prop) {
|
|
358
|
+
if (prop === "then" || prop === "catch" || prop === "finally") return;
|
|
359
|
+
if (typeof prop === "symbol") return;
|
|
360
|
+
throw new Error(`[UnitTestContainer] Attempted to access '${prop}' on auto-mocked service '${tokenId}'. This service was not provided in the providers list. Add it to providers or use allowUnregistered: false to catch this earlier.`);
|
|
361
|
+
} });
|
|
280
362
|
}
|
|
281
|
-
var _dec, _initClass, _Container;
|
|
282
363
|
/**
|
|
283
|
-
*
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
364
|
+
* UnitTestContainer for isolated unit testing.
|
|
365
|
+
*
|
|
366
|
+
* Only services explicitly listed in `providers` can be resolved.
|
|
367
|
+
* All method calls are automatically tracked via proxies.
|
|
368
|
+
* Unregistered dependencies throw by default, or can be auto-mocked.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* const container = new UnitTestContainer({
|
|
373
|
+
* providers: [
|
|
374
|
+
* { token: UserService, useClass: MockUserService },
|
|
375
|
+
* { token: ConfigToken, useValue: { apiUrl: 'test' } },
|
|
376
|
+
* ],
|
|
377
|
+
* })
|
|
378
|
+
*
|
|
379
|
+
* const service = await container.get(UserService)
|
|
380
|
+
*
|
|
381
|
+
* // All method calls are automatically tracked
|
|
382
|
+
* await service.findUser('123')
|
|
383
|
+
*
|
|
384
|
+
* container.expectCalled(UserService, 'findUser')
|
|
385
|
+
* container.expectCalledWith(UserService, 'findUser', ['123'])
|
|
386
|
+
* ```
|
|
387
|
+
*/ var UnitTestContainer = class extends require_container._Container {
|
|
388
|
+
testRegistry;
|
|
389
|
+
methodCalls = /* @__PURE__ */ new Map();
|
|
390
|
+
lifecycleEvents = /* @__PURE__ */ new Map();
|
|
391
|
+
instanceCounts = /* @__PURE__ */ new Map();
|
|
392
|
+
registeredTokenIds = /* @__PURE__ */ new Set();
|
|
393
|
+
autoMockedTokenIds = /* @__PURE__ */ new Set();
|
|
394
|
+
allowUnregistered;
|
|
395
|
+
constructor(options) {
|
|
396
|
+
const testRegistry = new require_container.Registry();
|
|
397
|
+
super(testRegistry, options.logger ?? null, require_container.defaultInjectors);
|
|
398
|
+
this.testRegistry = testRegistry;
|
|
399
|
+
this.allowUnregistered = options.allowUnregistered ?? false;
|
|
400
|
+
for (const provider of options.providers) this.registerProvider(provider);
|
|
306
401
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
402
|
+
/**
|
|
403
|
+
* Enables auto-mocking for unregistered dependencies.
|
|
404
|
+
* Call this to switch from strict mode to auto-mock mode.
|
|
405
|
+
*/ enableAutoMocking() {
|
|
406
|
+
this.allowUnregistered = true;
|
|
407
|
+
return this;
|
|
313
408
|
}
|
|
314
|
-
|
|
315
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Disables auto-mocking (strict mode).
|
|
411
|
+
* Unregistered dependencies will throw.
|
|
412
|
+
*/ disableAutoMocking() {
|
|
413
|
+
this.allowUnregistered = false;
|
|
414
|
+
return this;
|
|
316
415
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Override get to wrap instances in tracking proxies.
|
|
418
|
+
*/ async get(token, args) {
|
|
419
|
+
const realToken = this.resolveToken(token);
|
|
420
|
+
if (!this.registeredTokenIds.has(realToken.id)) {
|
|
421
|
+
if (!this.allowUnregistered) throw require_container.DIError.factoryNotFound(`${realToken.toString()} is not in the providers list. Add it to providers or enable allowUnregistered.`);
|
|
422
|
+
if (!this.autoMockedTokenIds.has(realToken.id)) this.autoMockedTokenIds.add(realToken.id);
|
|
423
|
+
return createAutoMockProxy(realToken.id);
|
|
424
|
+
}
|
|
425
|
+
const instance = await super.get(token, args);
|
|
426
|
+
if (instance && typeof instance === "object") return createTrackingProxy(instance, realToken.id, this.methodCalls);
|
|
427
|
+
return instance;
|
|
321
428
|
}
|
|
322
|
-
|
|
323
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Clears all state and disposes the container.
|
|
431
|
+
*/ async clear() {
|
|
432
|
+
await this.dispose();
|
|
433
|
+
this.methodCalls.clear();
|
|
434
|
+
this.lifecycleEvents.clear();
|
|
435
|
+
this.instanceCounts.clear();
|
|
436
|
+
this.autoMockedTokenIds.clear();
|
|
324
437
|
}
|
|
325
|
-
|
|
326
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Asserts that a service has been resolved at least once.
|
|
440
|
+
*/ expectResolved(token) {
|
|
441
|
+
const realToken = this.resolveToken(token);
|
|
442
|
+
if (!this.getStorage().getAllNames().some((name) => name.includes(realToken.id))) throw new Error(`Expected ${realToken.toString()} to be resolved, but it was not`);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Asserts that a service has NOT been resolved.
|
|
446
|
+
*/ expectNotResolved(token) {
|
|
447
|
+
const realToken = this.resolveToken(token);
|
|
448
|
+
if (this.getStorage().getAllNames().some((name) => name.includes(realToken.id))) throw new Error(`Expected ${realToken.toString()} to NOT be resolved, but it was`);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Asserts that a service was auto-mocked (not in providers list).
|
|
452
|
+
*/ expectAutoMocked(token) {
|
|
453
|
+
const realToken = this.resolveToken(token);
|
|
454
|
+
if (!this.autoMockedTokenIds.has(realToken.id)) throw new Error(`Expected ${realToken.toString()} to be auto-mocked, but it was not. Either it's in the providers list or hasn't been resolved.`);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Asserts that a service was NOT auto-mocked (is in providers list).
|
|
458
|
+
*/ expectNotAutoMocked(token) {
|
|
459
|
+
const realToken = this.resolveToken(token);
|
|
460
|
+
if (this.autoMockedTokenIds.has(realToken.id)) throw new Error(`Expected ${realToken.toString()} to NOT be auto-mocked, but it was.`);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Records a lifecycle event for tracking.
|
|
464
|
+
*/ recordLifecycleEvent(token, event, instanceName) {
|
|
465
|
+
const realToken = this.resolveToken(token);
|
|
466
|
+
const events = this.lifecycleEvents.get(realToken.id) || [];
|
|
467
|
+
events.push({
|
|
468
|
+
event,
|
|
469
|
+
timestamp: Date.now(),
|
|
470
|
+
instanceName
|
|
471
|
+
});
|
|
472
|
+
this.lifecycleEvents.set(realToken.id, events);
|
|
473
|
+
if (event === "created") {
|
|
474
|
+
const count = this.instanceCounts.get(realToken.id) || 0;
|
|
475
|
+
this.instanceCounts.set(realToken.id, count + 1);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Asserts that a service's onServiceInit was called.
|
|
480
|
+
*/ expectInitialized(token) {
|
|
481
|
+
const realToken = this.resolveToken(token);
|
|
482
|
+
if (!(this.lifecycleEvents.get(realToken.id) || []).some((e) => e.event === "initialized")) throw new Error(`Expected ${realToken.toString()} to be initialized, but onServiceInit was not called`);
|
|
327
483
|
}
|
|
328
484
|
/**
|
|
329
|
-
*
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return new _TestContainer(this.registry, this.logger, this.injectors);
|
|
485
|
+
* Asserts that a service's onServiceDestroy was called.
|
|
486
|
+
*/ expectDestroyed(token) {
|
|
487
|
+
const realToken = this.resolveToken(token);
|
|
488
|
+
if (!(this.lifecycleEvents.get(realToken.id) || []).some((e) => e.event === "destroyed")) throw new Error(`Expected ${realToken.toString()} to be destroyed, but onServiceDestroy was not called`);
|
|
334
489
|
}
|
|
335
|
-
|
|
336
|
-
|
|
490
|
+
/**
|
|
491
|
+
* Asserts that a service has NOT been destroyed.
|
|
492
|
+
*/ expectNotDestroyed(token) {
|
|
493
|
+
const realToken = this.resolveToken(token);
|
|
494
|
+
if ((this.lifecycleEvents.get(realToken.id) || []).some((e) => e.event === "destroyed")) throw new Error(`Expected ${realToken.toString()} to NOT be destroyed, but onServiceDestroy was called`);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Asserts that a method was called on a service.
|
|
498
|
+
*/ expectCalled(token, method) {
|
|
499
|
+
const realToken = this.resolveToken(token);
|
|
500
|
+
if (!(this.methodCalls.get(realToken.id) || []).some((c) => c.method === method)) throw new Error(`Expected ${realToken.toString()}.${method}() to be called, but it was not`);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Asserts that a method was NOT called on a service.
|
|
504
|
+
*/ expectNotCalled(token, method) {
|
|
505
|
+
const realToken = this.resolveToken(token);
|
|
506
|
+
if ((this.methodCalls.get(realToken.id) || []).some((c) => c.method === method)) throw new Error(`Expected ${realToken.toString()}.${method}() to NOT be called, but it was`);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Asserts that a method was called with specific arguments.
|
|
510
|
+
*/ expectCalledWith(token, method, expectedArgs) {
|
|
511
|
+
const realToken = this.resolveToken(token);
|
|
512
|
+
const calls = this.methodCalls.get(realToken.id) || [];
|
|
513
|
+
if (!calls.some((c) => c.method === method && this.argsMatch(c.args, expectedArgs))) {
|
|
514
|
+
const actualArgs = calls.filter((c) => c.method === method).map((c) => JSON.stringify(c.args)).join(", ");
|
|
515
|
+
throw new Error(`Expected ${realToken.toString()}.${method}() to be called with ${JSON.stringify(expectedArgs)}. Actual calls: [${actualArgs}]`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Asserts that a method was called a specific number of times.
|
|
520
|
+
*/ expectCallCount(token, method, count) {
|
|
521
|
+
const realToken = this.resolveToken(token);
|
|
522
|
+
const actualCount = (this.methodCalls.get(realToken.id) || []).filter((c) => c.method === method).length;
|
|
523
|
+
if (actualCount !== count) throw new Error(`Expected ${realToken.toString()}.${method}() to be called ${count} times, but was called ${actualCount} times`);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Gets all recorded method calls for a service.
|
|
527
|
+
*/ getMethodCalls(token) {
|
|
528
|
+
const realToken = this.resolveToken(token);
|
|
529
|
+
return this.methodCalls.get(realToken.id) || [];
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Gets statistics about a service.
|
|
533
|
+
*/ getServiceStats(token) {
|
|
534
|
+
const realToken = this.resolveToken(token);
|
|
535
|
+
return {
|
|
536
|
+
instanceCount: this.instanceCounts.get(realToken.id) || 0,
|
|
537
|
+
methodCalls: this.methodCalls.get(realToken.id) || [],
|
|
538
|
+
lifecycleEvents: this.lifecycleEvents.get(realToken.id) || []
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Clears all recorded method calls.
|
|
543
|
+
*/ clearMethodCalls() {
|
|
544
|
+
this.methodCalls.clear();
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Gets list of all registered provider token IDs.
|
|
548
|
+
*/ getRegisteredTokenIds() {
|
|
549
|
+
return this.registeredTokenIds;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Gets list of all auto-mocked token IDs.
|
|
553
|
+
*/ getAutoMockedTokenIds() {
|
|
554
|
+
return this.autoMockedTokenIds;
|
|
555
|
+
}
|
|
556
|
+
resolveToken(token) {
|
|
557
|
+
if (typeof token === "function") try {
|
|
558
|
+
return require_container.getInjectableToken(token);
|
|
559
|
+
} catch {
|
|
560
|
+
return require_container.InjectionToken.create(token);
|
|
561
|
+
}
|
|
562
|
+
return token;
|
|
563
|
+
}
|
|
564
|
+
registerProvider(provider) {
|
|
565
|
+
const realToken = this.resolveToken(provider.token);
|
|
566
|
+
this.registeredTokenIds.add(realToken.id);
|
|
567
|
+
if (provider.useValue !== void 0) this.registerValueBinding(realToken, provider.useValue);
|
|
568
|
+
else if (provider.useClass) this.registerClassBinding(realToken, provider.useClass);
|
|
569
|
+
else if (provider.useFactory) this.registerFactoryBinding(realToken, provider.useFactory);
|
|
570
|
+
else if (typeof provider.token === "function") this.testRegistry.set(realToken, require_container.InjectableScope.Singleton, provider.token, require_container.InjectableType.Class);
|
|
571
|
+
}
|
|
572
|
+
registerValueBinding(token, value) {
|
|
573
|
+
const ValueHolder = class {
|
|
574
|
+
static instance = value;
|
|
575
|
+
};
|
|
576
|
+
this.testRegistry.set(token, require_container.InjectableScope.Singleton, ValueHolder, require_container.InjectableType.Class);
|
|
577
|
+
const instanceName = this.getNameResolver().generateInstanceName(token, void 0, void 0, require_container.InjectableScope.Singleton);
|
|
578
|
+
this.getStorage().storeInstance(instanceName, value);
|
|
579
|
+
this.recordLifecycleEvent(token, "created", instanceName);
|
|
580
|
+
}
|
|
581
|
+
registerClassBinding(token, cls) {
|
|
582
|
+
this.testRegistry.set(token, require_container.InjectableScope.Singleton, cls, require_container.InjectableType.Class);
|
|
583
|
+
}
|
|
584
|
+
registerFactoryBinding(token, factory) {
|
|
585
|
+
const FactoryWrapper = class {
|
|
586
|
+
static factory = factory;
|
|
587
|
+
async create() {
|
|
588
|
+
return await factory();
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
this.testRegistry.set(token, require_container.InjectableScope.Singleton, FactoryWrapper, require_container.InjectableType.Factory);
|
|
592
|
+
}
|
|
593
|
+
argsMatch(actual, expected) {
|
|
594
|
+
if (actual.length !== expected.length) return false;
|
|
595
|
+
return actual.every((arg, index) => {
|
|
596
|
+
const exp = expected[index];
|
|
597
|
+
if (typeof exp === "object" && exp !== null) return JSON.stringify(arg) === JSON.stringify(exp);
|
|
598
|
+
return arg === exp;
|
|
599
|
+
});
|
|
337
600
|
}
|
|
338
601
|
};
|
|
339
602
|
|
|
340
603
|
//#endregion
|
|
341
|
-
exports.
|
|
342
|
-
|
|
343
|
-
enumerable: true,
|
|
344
|
-
get: function () {
|
|
345
|
-
return _TestContainer;
|
|
346
|
-
}
|
|
347
|
-
});
|
|
604
|
+
exports.TestContainer = TestContainer;
|
|
605
|
+
exports.UnitTestContainer = UnitTestContainer;
|
|
348
606
|
//# sourceMappingURL=index.cjs.map
|