@navios/di 0.7.1 → 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 +110 -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 -1508
- package/lib/browser/index.mjs +29 -2650
- 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-Pb_Y4Z4x.mjs → container-8-z89TyQ.mjs} +1269 -1305
- package/lib/container-8-z89TyQ.mjs.map +1 -0
- package/lib/{container-BuAutHGg.d.mts → container-CNiqesCL.d.mts} +600 -569
- package/lib/container-CNiqesCL.d.mts.map +1 -0
- package/lib/{container-DnzgpfBe.cjs → container-CaY2fDuk.cjs} +1287 -1329
- package/lib/container-CaY2fDuk.cjs.map +1 -0
- package/lib/{container-oGTgX2iX.d.cts → container-D-0Ho3qL.d.cts} +601 -565
- 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 +461 -292
- 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-processor.mts → token-resolver.mts} +17 -88
- 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-BuAutHGg.d.mts.map +0 -1
- package/lib/container-DnzgpfBe.cjs.map +0 -1
- package/lib/container-Pb_Y4Z4x.mjs.map +0 -1
- package/lib/container-oGTgX2iX.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 -214
- package/src/internal/core/invalidator.mts +0 -437
- package/src/internal/core/service-locator.mts +0 -202
- package/src/internal/holder/base-holder-manager.mts +0 -238
- package/src/internal/holder/holder-manager.mts +0 -85
- package/src/internal/holder/request-storage.mts +0 -134
- package/src/internal/holder/singleton-storage.mts +0 -105
- package/src/testing/README.md +0 -80
- package/src/testing/__tests__/test-container.spec.mts +0 -173
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { Injectable } from '../decorators/injectable.decorator.mjs'
|
|
4
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
5
|
+
import { UnitTestContainer } from '../testing/unit-test-container.mjs'
|
|
6
|
+
|
|
7
|
+
describe('UnitTestContainer', () => {
|
|
8
|
+
describe('Strict Mode (default)', () => {
|
|
9
|
+
it('should resolve services from providers list', async () => {
|
|
10
|
+
@Injectable()
|
|
11
|
+
class UserService {
|
|
12
|
+
getUser(): string {
|
|
13
|
+
return 'user'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const container = new UnitTestContainer({
|
|
18
|
+
providers: [{ token: UserService }],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const service = await container.get(UserService)
|
|
22
|
+
expect(service.getUser()).toBe('user')
|
|
23
|
+
|
|
24
|
+
await container.dispose()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should throw when resolving unregistered service', async () => {
|
|
28
|
+
@Injectable()
|
|
29
|
+
class NotProvided {}
|
|
30
|
+
|
|
31
|
+
const container = new UnitTestContainer({
|
|
32
|
+
providers: [],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
await expect(container.get(NotProvided)).rejects.toThrow(/not in the providers list/)
|
|
36
|
+
|
|
37
|
+
await container.dispose()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should use provided value', async () => {
|
|
41
|
+
const TOKEN = InjectionToken.create<{ value: string }>('config')
|
|
42
|
+
|
|
43
|
+
const container = new UnitTestContainer({
|
|
44
|
+
providers: [{ token: TOKEN, useValue: { value: 'test-config' } }],
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const config = await container.get(TOKEN)
|
|
48
|
+
expect(config.value).toBe('test-config')
|
|
49
|
+
|
|
50
|
+
await container.dispose()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should use provided class', async () => {
|
|
54
|
+
const TOKEN = InjectionToken.create<{ getName(): string }>('service')
|
|
55
|
+
|
|
56
|
+
class MockService {
|
|
57
|
+
getName(): string {
|
|
58
|
+
return 'mock'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const container = new UnitTestContainer({
|
|
63
|
+
providers: [{ token: TOKEN, useClass: MockService }],
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const service = await container.get(TOKEN)
|
|
67
|
+
expect(service.getName()).toBe('mock')
|
|
68
|
+
|
|
69
|
+
await container.dispose()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should use provided factory', async () => {
|
|
73
|
+
const TOKEN = InjectionToken.create<{ id: number }>('factory')
|
|
74
|
+
|
|
75
|
+
const container = new UnitTestContainer({
|
|
76
|
+
providers: [{ token: TOKEN, useFactory: () => ({ id: 42 }) }],
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const result = await container.get(TOKEN)
|
|
80
|
+
expect(result.id).toBe(42)
|
|
81
|
+
|
|
82
|
+
await container.dispose()
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('Auto-Mock Mode', () => {
|
|
87
|
+
it('should auto-mock unregistered services when enabled', async () => {
|
|
88
|
+
@Injectable()
|
|
89
|
+
class UnregisteredService {}
|
|
90
|
+
|
|
91
|
+
const container = new UnitTestContainer({
|
|
92
|
+
providers: [],
|
|
93
|
+
allowUnregistered: true,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const service = await container.get(UnregisteredService)
|
|
97
|
+
expect(service).toBeDefined()
|
|
98
|
+
|
|
99
|
+
container.expectAutoMocked(UnregisteredService)
|
|
100
|
+
|
|
101
|
+
await container.dispose()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should throw when accessing methods on auto-mocked service', async () => {
|
|
105
|
+
@Injectable()
|
|
106
|
+
class AutoMocked {
|
|
107
|
+
doSomething(): void {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const container = new UnitTestContainer({
|
|
111
|
+
providers: [],
|
|
112
|
+
allowUnregistered: true,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const service = await container.get(AutoMocked) as any
|
|
116
|
+
|
|
117
|
+
expect(() => service.doSomething()).toThrow(/auto-mocked service/)
|
|
118
|
+
|
|
119
|
+
await container.dispose()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should enable auto-mocking dynamically', async () => {
|
|
123
|
+
@Injectable()
|
|
124
|
+
class DynamicService {}
|
|
125
|
+
|
|
126
|
+
const container = new UnitTestContainer({
|
|
127
|
+
providers: [],
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Should throw initially
|
|
131
|
+
await expect(container.get(DynamicService)).rejects.toThrow()
|
|
132
|
+
|
|
133
|
+
// Enable auto-mocking
|
|
134
|
+
container.enableAutoMocking()
|
|
135
|
+
|
|
136
|
+
// Should work now
|
|
137
|
+
const service = await container.get(DynamicService)
|
|
138
|
+
expect(service).toBeDefined()
|
|
139
|
+
|
|
140
|
+
await container.dispose()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should disable auto-mocking dynamically', async () => {
|
|
144
|
+
@Injectable()
|
|
145
|
+
class AnotherService {}
|
|
146
|
+
|
|
147
|
+
const container = new UnitTestContainer({
|
|
148
|
+
providers: [],
|
|
149
|
+
allowUnregistered: true,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
container.disableAutoMocking()
|
|
153
|
+
|
|
154
|
+
await expect(container.get(AnotherService)).rejects.toThrow()
|
|
155
|
+
|
|
156
|
+
await container.dispose()
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('Automatic Call Tracking', () => {
|
|
161
|
+
let container: UnitTestContainer
|
|
162
|
+
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
container = new UnitTestContainer({
|
|
165
|
+
providers: [],
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
afterEach(async () => {
|
|
170
|
+
await container.dispose()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should automatically track method calls via proxy', async () => {
|
|
174
|
+
@Injectable()
|
|
175
|
+
class TrackedService {
|
|
176
|
+
process(data: string): string {
|
|
177
|
+
return `processed: ${data}`
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const localContainer = new UnitTestContainer({
|
|
182
|
+
providers: [{ token: TrackedService }],
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const service = await localContainer.get(TrackedService)
|
|
186
|
+
const result = service.process('test')
|
|
187
|
+
|
|
188
|
+
expect(result).toBe('processed: test')
|
|
189
|
+
localContainer.expectCalled(TrackedService, 'process')
|
|
190
|
+
localContainer.expectCalledWith(TrackedService, 'process', ['test'])
|
|
191
|
+
|
|
192
|
+
await localContainer.dispose()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('should track multiple calls', async () => {
|
|
196
|
+
@Injectable()
|
|
197
|
+
class MultiCallService {
|
|
198
|
+
action(): void {}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const localContainer = new UnitTestContainer({
|
|
202
|
+
providers: [{ token: MultiCallService }],
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const service = await localContainer.get(MultiCallService)
|
|
206
|
+
service.action()
|
|
207
|
+
service.action()
|
|
208
|
+
service.action()
|
|
209
|
+
|
|
210
|
+
localContainer.expectCallCount(MultiCallService, 'action', 3)
|
|
211
|
+
|
|
212
|
+
await localContainer.dispose()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('should track async method calls', async () => {
|
|
216
|
+
@Injectable()
|
|
217
|
+
class AsyncService {
|
|
218
|
+
async fetchData(id: string): Promise<{ id: string }> {
|
|
219
|
+
return { id }
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const localContainer = new UnitTestContainer({
|
|
224
|
+
providers: [{ token: AsyncService }],
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const service = await localContainer.get(AsyncService)
|
|
228
|
+
const result = await service.fetchData('123')
|
|
229
|
+
|
|
230
|
+
expect(result.id).toBe('123')
|
|
231
|
+
localContainer.expectCalled(AsyncService, 'fetchData')
|
|
232
|
+
localContainer.expectCalledWith(AsyncService, 'fetchData', ['123'])
|
|
233
|
+
|
|
234
|
+
await localContainer.dispose()
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should assert method was NOT called', async () => {
|
|
238
|
+
@Injectable()
|
|
239
|
+
class SomeService {
|
|
240
|
+
neverCalled(): void {}
|
|
241
|
+
called(): void {}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const localContainer = new UnitTestContainer({
|
|
245
|
+
providers: [{ token: SomeService }],
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const service = await localContainer.get(SomeService)
|
|
249
|
+
service.called()
|
|
250
|
+
|
|
251
|
+
localContainer.expectNotCalled(SomeService, 'neverCalled')
|
|
252
|
+
localContainer.expectCalled(SomeService, 'called')
|
|
253
|
+
|
|
254
|
+
await localContainer.dispose()
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
describe('Assertion Helpers', () => {
|
|
259
|
+
it('should assert service is resolved', async () => {
|
|
260
|
+
@Injectable()
|
|
261
|
+
class ResolvedService {}
|
|
262
|
+
|
|
263
|
+
const container = new UnitTestContainer({
|
|
264
|
+
providers: [{ token: ResolvedService }],
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
await container.get(ResolvedService)
|
|
268
|
+
|
|
269
|
+
expect(() => container.expectResolved(ResolvedService)).not.toThrow()
|
|
270
|
+
|
|
271
|
+
await container.dispose()
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('should assert service is NOT resolved', async () => {
|
|
275
|
+
@Injectable()
|
|
276
|
+
class NotResolvedService {}
|
|
277
|
+
|
|
278
|
+
const container = new UnitTestContainer({
|
|
279
|
+
providers: [{ token: NotResolvedService }],
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
expect(() => container.expectNotResolved(NotResolvedService)).not.toThrow()
|
|
283
|
+
|
|
284
|
+
await container.dispose()
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('should assert service is auto-mocked', async () => {
|
|
288
|
+
@Injectable()
|
|
289
|
+
class AutoMockedService {}
|
|
290
|
+
|
|
291
|
+
const container = new UnitTestContainer({
|
|
292
|
+
providers: [],
|
|
293
|
+
allowUnregistered: true,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await container.get(AutoMockedService)
|
|
297
|
+
|
|
298
|
+
expect(() => container.expectAutoMocked(AutoMockedService)).not.toThrow()
|
|
299
|
+
|
|
300
|
+
await container.dispose()
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('should assert service is NOT auto-mocked', async () => {
|
|
304
|
+
@Injectable()
|
|
305
|
+
class RealService {}
|
|
306
|
+
|
|
307
|
+
const container = new UnitTestContainer({
|
|
308
|
+
providers: [{ token: RealService }],
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
await container.get(RealService)
|
|
312
|
+
|
|
313
|
+
expect(() => container.expectNotAutoMocked(RealService)).not.toThrow()
|
|
314
|
+
|
|
315
|
+
await container.dispose()
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
describe('Service Stats', () => {
|
|
320
|
+
it('should get service stats', async () => {
|
|
321
|
+
@Injectable()
|
|
322
|
+
class StatsService {
|
|
323
|
+
method1(): void {}
|
|
324
|
+
method2(arg: string): string {
|
|
325
|
+
return arg
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const container = new UnitTestContainer({
|
|
330
|
+
providers: [{ token: StatsService }],
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
const service = await container.get(StatsService)
|
|
334
|
+
service.method1()
|
|
335
|
+
service.method2('test')
|
|
336
|
+
|
|
337
|
+
const stats = container.getServiceStats(StatsService)
|
|
338
|
+
expect(stats.methodCalls.length).toBe(2)
|
|
339
|
+
|
|
340
|
+
await container.dispose()
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('should get registered token IDs', async () => {
|
|
344
|
+
@Injectable()
|
|
345
|
+
class Service1 {}
|
|
346
|
+
|
|
347
|
+
@Injectable()
|
|
348
|
+
class Service2 {}
|
|
349
|
+
|
|
350
|
+
const container = new UnitTestContainer({
|
|
351
|
+
providers: [{ token: Service1 }, { token: Service2 }],
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const tokenIds = container.getRegisteredTokenIds()
|
|
355
|
+
expect(tokenIds.size).toBe(2)
|
|
356
|
+
|
|
357
|
+
await container.dispose()
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should get auto-mocked token IDs', async () => {
|
|
361
|
+
@Injectable()
|
|
362
|
+
class Mock1 {}
|
|
363
|
+
|
|
364
|
+
@Injectable()
|
|
365
|
+
class Mock2 {}
|
|
366
|
+
|
|
367
|
+
const container = new UnitTestContainer({
|
|
368
|
+
providers: [],
|
|
369
|
+
allowUnregistered: true,
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
await container.get(Mock1)
|
|
373
|
+
await container.get(Mock2)
|
|
374
|
+
|
|
375
|
+
const autoMocked = container.getAutoMockedTokenIds()
|
|
376
|
+
expect(autoMocked.size).toBe(2)
|
|
377
|
+
|
|
378
|
+
await container.dispose()
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
describe('Clear', () => {
|
|
383
|
+
it('should clear all state', async () => {
|
|
384
|
+
@Injectable()
|
|
385
|
+
class ClearableService {
|
|
386
|
+
action(): void {}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const container = new UnitTestContainer({
|
|
390
|
+
providers: [{ token: ClearableService }],
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
const service = await container.get(ClearableService)
|
|
394
|
+
service.action()
|
|
395
|
+
|
|
396
|
+
container.expectResolved(ClearableService)
|
|
397
|
+
container.expectCalled(ClearableService, 'action')
|
|
398
|
+
|
|
399
|
+
await container.clear()
|
|
400
|
+
|
|
401
|
+
container.expectNotResolved(ClearableService)
|
|
402
|
+
expect(container.getMethodCalls(ClearableService)).toHaveLength(0)
|
|
403
|
+
})
|
|
404
|
+
})
|
|
405
|
+
})
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { assertType, describe, test } from 'vitest'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import type { Factorable } from '../interfaces/index.mjs'
|
|
5
|
+
|
|
6
|
+
import { Container } from '../container/container.mjs'
|
|
7
|
+
import { ScopedContainer } from '../container/scoped-container.mjs'
|
|
8
|
+
import { Injectable, Factory } from '../decorators/index.mjs'
|
|
9
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
10
|
+
|
|
11
|
+
interface FooService {
|
|
12
|
+
makeFoo(): string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const simpleObjectSchema = z.object({
|
|
16
|
+
foo: z.string(),
|
|
17
|
+
})
|
|
18
|
+
const simpleOptionalObjectSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
foo: z.string(),
|
|
21
|
+
})
|
|
22
|
+
.optional()
|
|
23
|
+
|
|
24
|
+
const typelessObjectToken = InjectionToken.create(
|
|
25
|
+
Symbol.for('Typeless object token'),
|
|
26
|
+
simpleObjectSchema,
|
|
27
|
+
)
|
|
28
|
+
const typelessOptionalObjectToken = InjectionToken.create(
|
|
29
|
+
Symbol.for('Typeless optional object token'),
|
|
30
|
+
simpleOptionalObjectSchema,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const typedObjectToken = InjectionToken.create<
|
|
34
|
+
FooService,
|
|
35
|
+
typeof simpleObjectSchema
|
|
36
|
+
>(Symbol.for('Typed object token'), simpleObjectSchema)
|
|
37
|
+
const typedOptionalObjectToken = InjectionToken.create<
|
|
38
|
+
FooService,
|
|
39
|
+
typeof simpleOptionalObjectSchema
|
|
40
|
+
>(Symbol.for('Typed optional object token'), simpleOptionalObjectSchema)
|
|
41
|
+
|
|
42
|
+
const typedToken = InjectionToken.create<FooService>(Symbol.for('Typed token'))
|
|
43
|
+
|
|
44
|
+
describe('Container.get', () => {
|
|
45
|
+
describe('#1 Classes', () => {
|
|
46
|
+
test('simple class', async () => {
|
|
47
|
+
@Injectable()
|
|
48
|
+
class Foo {
|
|
49
|
+
makeFoo() {
|
|
50
|
+
return 'foo'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const container = new Container()
|
|
55
|
+
assertType<Foo>(await container.get(Foo))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('class with required argument', async () => {
|
|
59
|
+
@Injectable({
|
|
60
|
+
schema: simpleObjectSchema,
|
|
61
|
+
})
|
|
62
|
+
class Foo {
|
|
63
|
+
constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const container = new Container()
|
|
67
|
+
assertType<Foo>(await container.get(Foo, { foo: 'bar' }))
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('should fail if not compatible', async () => {
|
|
71
|
+
@Injectable({
|
|
72
|
+
schema: simpleObjectSchema,
|
|
73
|
+
})
|
|
74
|
+
class Foo {
|
|
75
|
+
constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const container = new Container()
|
|
79
|
+
// @ts-expect-error Should fail if not compatible
|
|
80
|
+
await container.get(Foo, { test: 'bar' })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('factory class returns unwrapped type', async () => {
|
|
84
|
+
@Factory()
|
|
85
|
+
class FooFactory implements Factorable<string> {
|
|
86
|
+
create() {
|
|
87
|
+
return 'created'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const container = new Container()
|
|
92
|
+
// When getting a Factorable class, we get the created type, not the factory
|
|
93
|
+
assertType<string>(await container.get(FooFactory))
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('#2 Token with required Schema', async () => {
|
|
98
|
+
const container = new Container()
|
|
99
|
+
|
|
100
|
+
const result = await container.get(typelessObjectToken, { foo: 'bar' })
|
|
101
|
+
assertType<unknown>(result)
|
|
102
|
+
|
|
103
|
+
const result2 = await container.get(typedObjectToken, { foo: 'bar' })
|
|
104
|
+
assertType<FooService>(result2)
|
|
105
|
+
|
|
106
|
+
// @ts-expect-error We show error when we pass the wrong type
|
|
107
|
+
await container.get(typedObjectToken, undefined)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('#3 Token with optional Schema', async () => {
|
|
111
|
+
const container = new Container()
|
|
112
|
+
|
|
113
|
+
const result = await container.get(typelessOptionalObjectToken)
|
|
114
|
+
assertType<unknown>(result)
|
|
115
|
+
|
|
116
|
+
const result2 = await container.get(typedOptionalObjectToken)
|
|
117
|
+
assertType<FooService>(result2)
|
|
118
|
+
|
|
119
|
+
const result3 = await container.get(typedObjectToken)
|
|
120
|
+
// Special case when we pass the token without args
|
|
121
|
+
// We can only return an error string
|
|
122
|
+
assertType<'Error: Your token requires args: foo'>(result3)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('#4 Token with no Schema', async () => {
|
|
126
|
+
const container = new Container()
|
|
127
|
+
|
|
128
|
+
const result = await container.get(typedToken)
|
|
129
|
+
assertType<FooService>(result)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('#5 BoundInjectionToken', async () => {
|
|
133
|
+
const container = new Container()
|
|
134
|
+
|
|
135
|
+
const boundToken = InjectionToken.bound(typedObjectToken, { foo: 'bar' })
|
|
136
|
+
const result = await container.get(boundToken)
|
|
137
|
+
assertType<FooService>(result)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('#6 FactoryInjectionToken', async () => {
|
|
141
|
+
const container = new Container()
|
|
142
|
+
|
|
143
|
+
const factoryToken = InjectionToken.factory(
|
|
144
|
+
typedObjectToken,
|
|
145
|
+
async () => ({ foo: 'bar' }),
|
|
146
|
+
)
|
|
147
|
+
const result = await container.get(factoryToken)
|
|
148
|
+
assertType<FooService>(result)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('Container methods', () => {
|
|
153
|
+
test('beginRequest returns ScopedContainer', () => {
|
|
154
|
+
const container = new Container()
|
|
155
|
+
const scopedContainer = container.beginRequest('request-1')
|
|
156
|
+
assertType<ScopedContainer>(scopedContainer)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('getActiveRequestIds returns ReadonlySet<string>', () => {
|
|
160
|
+
const container = new Container()
|
|
161
|
+
const activeIds = container.getActiveRequestIds()
|
|
162
|
+
assertType<ReadonlySet<string>>(activeIds)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('hasActiveRequest returns boolean', () => {
|
|
166
|
+
const container = new Container()
|
|
167
|
+
const hasRequest = container.hasActiveRequest('request-1')
|
|
168
|
+
assertType<boolean>(hasRequest)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('dispose returns Promise<void>', async () => {
|
|
172
|
+
const container = new Container()
|
|
173
|
+
assertType<Promise<void>>(container.dispose())
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('invalidate returns Promise<void>', async () => {
|
|
177
|
+
const container = new Container()
|
|
178
|
+
assertType<Promise<void>>(container.invalidate({}))
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -34,7 +34,10 @@ class TestFactory3 implements Factorable<boolean> {
|
|
|
34
34
|
|
|
35
35
|
// Test factory with token and schema
|
|
36
36
|
const schema = z.object({ name: z.string() })
|
|
37
|
-
const tokenWithSchema = InjectionToken.create
|
|
37
|
+
const tokenWithSchema = InjectionToken.create<{ name: string }, typeof schema>(
|
|
38
|
+
'TestTokenWithSchema',
|
|
39
|
+
schema,
|
|
40
|
+
)
|
|
38
41
|
@Factory({ token: tokenWithSchema })
|
|
39
42
|
class TestFactory4
|
|
40
43
|
implements FactorableWithArgs<{ name: string }, typeof schema>
|
|
@@ -53,7 +56,15 @@ class TestFactory5 implements Factorable<object> {
|
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
//
|
|
59
|
+
// Test factory with async create
|
|
60
|
+
@Factory()
|
|
61
|
+
class TestFactory6 implements Factorable<string[]> {
|
|
62
|
+
async create() {
|
|
63
|
+
return ['async', 'result']
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Type tests - verify return types
|
|
57
68
|
const test1: string = new TestFactory1().create()
|
|
58
69
|
const test2: number = new TestFactory2().create()
|
|
59
70
|
const test3: boolean = new TestFactory3().create()
|
|
@@ -61,5 +72,6 @@ const test4: { name: string } = new TestFactory4().create(undefined, {
|
|
|
61
72
|
name: 'test',
|
|
62
73
|
})
|
|
63
74
|
const test5: object = new TestFactory5().create()
|
|
75
|
+
const test6: Promise<string[]> = new TestFactory6().create()
|
|
64
76
|
|
|
65
|
-
export { test1, test2, test3, test4, test5 }
|
|
77
|
+
export { test1, test2, test3, test4, test5, test6 }
|