@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.
Files changed (264) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +117 -17
  3. package/lib/browser/container/abstract-container.d.mts +112 -0
  4. package/lib/browser/container/abstract-container.d.mts.map +1 -0
  5. package/lib/browser/container/abstract-container.mjs +100 -0
  6. package/lib/browser/container/abstract-container.mjs.map +1 -0
  7. package/lib/browser/container/container.d.mts +100 -0
  8. package/lib/browser/container/container.d.mts.map +1 -0
  9. package/lib/browser/container/container.mjs +424 -0
  10. package/lib/browser/container/container.mjs.map +1 -0
  11. package/lib/browser/container/scoped-container.d.mts +93 -0
  12. package/lib/browser/container/scoped-container.d.mts.map +1 -0
  13. package/lib/browser/container/scoped-container.mjs +119 -0
  14. package/lib/browser/container/scoped-container.mjs.map +1 -0
  15. package/lib/browser/decorators/factory.decorator.d.mts +26 -0
  16. package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
  17. package/lib/browser/decorators/factory.decorator.mjs +20 -0
  18. package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
  19. package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
  20. package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
  21. package/lib/browser/decorators/injectable.decorator.mjs +21 -0
  22. package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
  23. package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
  24. package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
  25. package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
  26. package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
  27. package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
  28. package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
  29. package/lib/browser/enums/injectable-type.enum.mjs +10 -0
  30. package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
  31. package/lib/browser/errors/di-error.d.mts +43 -0
  32. package/lib/browser/errors/di-error.d.mts.map +1 -0
  33. package/lib/browser/errors/di-error.mjs +98 -0
  34. package/lib/browser/errors/di-error.mjs.map +1 -0
  35. package/lib/browser/event-emitter.d.mts +16 -0
  36. package/lib/browser/event-emitter.d.mts.map +1 -0
  37. package/lib/browser/event-emitter.mjs +320 -0
  38. package/lib/browser/event-emitter.mjs.map +1 -0
  39. package/lib/browser/index.d.mts +37 -1558
  40. package/lib/browser/index.mjs +29 -2749
  41. package/lib/browser/interfaces/container.interface.d.mts +59 -0
  42. package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
  43. package/lib/browser/interfaces/factory.interface.d.mts +14 -0
  44. package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
  45. package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
  46. package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
  47. package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
  48. package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
  49. package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
  50. package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
  51. package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
  52. package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
  53. package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
  54. package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
  55. package/lib/browser/internal/context/factory-context.d.mts +23 -0
  56. package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
  57. package/lib/browser/internal/context/resolution-context.d.mts +43 -0
  58. package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
  59. package/lib/browser/internal/context/resolution-context.mjs +56 -0
  60. package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
  61. package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
  62. package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
  63. package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
  64. package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
  65. package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
  66. package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
  67. package/lib/browser/internal/core/instance-resolver.mjs +306 -0
  68. package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
  69. package/lib/browser/internal/core/name-resolver.d.mts +52 -0
  70. package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
  71. package/lib/browser/internal/core/name-resolver.mjs +118 -0
  72. package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
  73. package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
  74. package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
  75. package/lib/browser/internal/core/scope-tracker.mjs +120 -0
  76. package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
  77. package/lib/browser/internal/core/service-initializer.d.mts +44 -0
  78. package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
  79. package/lib/browser/internal/core/service-initializer.mjs +109 -0
  80. package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
  81. package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
  82. package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
  83. package/lib/browser/internal/core/service-invalidator.mjs +142 -0
  84. package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
  85. package/lib/browser/internal/core/token-resolver.d.mts +54 -0
  86. package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
  87. package/lib/browser/internal/core/token-resolver.mjs +77 -0
  88. package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
  89. package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
  90. package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
  91. package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
  92. package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
  93. package/lib/browser/internal/holder/instance-holder.mjs +19 -0
  94. package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
  95. package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
  96. package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
  97. package/lib/browser/internal/holder/unified-storage.mjs +144 -0
  98. package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
  99. package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
  100. package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
  101. package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
  102. package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
  103. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
  104. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
  105. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
  106. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
  107. package/lib/browser/internal/stub-factory-class.d.mts +14 -0
  108. package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
  109. package/lib/browser/internal/stub-factory-class.mjs +18 -0
  110. package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
  111. package/lib/browser/symbols/injectable-token.d.mts +5 -0
  112. package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
  113. package/lib/browser/symbols/injectable-token.mjs +6 -0
  114. package/lib/browser/symbols/injectable-token.mjs.map +1 -0
  115. package/lib/browser/token/injection-token.d.mts +55 -0
  116. package/lib/browser/token/injection-token.d.mts.map +1 -0
  117. package/lib/browser/token/injection-token.mjs +100 -0
  118. package/lib/browser/token/injection-token.mjs.map +1 -0
  119. package/lib/browser/token/registry.d.mts +37 -0
  120. package/lib/browser/token/registry.d.mts.map +1 -0
  121. package/lib/browser/token/registry.mjs +86 -0
  122. package/lib/browser/token/registry.mjs.map +1 -0
  123. package/lib/browser/utils/default-injectors.d.mts +12 -0
  124. package/lib/browser/utils/default-injectors.d.mts.map +1 -0
  125. package/lib/browser/utils/default-injectors.mjs +13 -0
  126. package/lib/browser/utils/default-injectors.mjs.map +1 -0
  127. package/lib/browser/utils/get-injectable-token.d.mts +9 -0
  128. package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
  129. package/lib/browser/utils/get-injectable-token.mjs +13 -0
  130. package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
  131. package/lib/browser/utils/get-injectors.d.mts +55 -0
  132. package/lib/browser/utils/get-injectors.d.mts.map +1 -0
  133. package/lib/browser/utils/get-injectors.mjs +121 -0
  134. package/lib/browser/utils/get-injectors.mjs.map +1 -0
  135. package/lib/browser/utils/types.d.mts +23 -0
  136. package/lib/browser/utils/types.d.mts.map +1 -0
  137. package/lib/{container-DAKOvAgr.mjs → container-8-z89TyQ.mjs} +1325 -1462
  138. package/lib/container-8-z89TyQ.mjs.map +1 -0
  139. package/lib/{container-Bp1W-pWJ.d.mts → container-CNiqesCL.d.mts} +598 -617
  140. package/lib/container-CNiqesCL.d.mts.map +1 -0
  141. package/lib/{container-DENMeJ87.cjs → container-CaY2fDuk.cjs} +1369 -1512
  142. package/lib/container-CaY2fDuk.cjs.map +1 -0
  143. package/lib/{container-YPwvmlK2.d.cts → container-D-0Ho3qL.d.cts} +598 -612
  144. package/lib/container-D-0Ho3qL.d.cts.map +1 -0
  145. package/lib/index.cjs +13 -15
  146. package/lib/index.cjs.map +1 -1
  147. package/lib/index.d.cts +58 -223
  148. package/lib/index.d.cts.map +1 -1
  149. package/lib/index.d.mts +62 -222
  150. package/lib/index.d.mts.map +1 -1
  151. package/lib/index.mjs +5 -6
  152. package/lib/index.mjs.map +1 -1
  153. package/lib/testing/index.cjs +569 -311
  154. package/lib/testing/index.cjs.map +1 -1
  155. package/lib/testing/index.d.cts +370 -41
  156. package/lib/testing/index.d.cts.map +1 -1
  157. package/lib/testing/index.d.mts +370 -41
  158. package/lib/testing/index.d.mts.map +1 -1
  159. package/lib/testing/index.mjs +568 -305
  160. package/lib/testing/index.mjs.map +1 -1
  161. package/package.json +2 -1
  162. package/src/__tests__/circular-detector.spec.mts +193 -0
  163. package/src/__tests__/concurrent.spec.mts +368 -0
  164. package/src/__tests__/container.spec.mts +32 -30
  165. package/src/__tests__/di-error.spec.mts +351 -0
  166. package/src/__tests__/e2e.browser.spec.mts +0 -4
  167. package/src/__tests__/e2e.spec.mts +10 -19
  168. package/src/__tests__/event-emitter.spec.mts +232 -109
  169. package/src/__tests__/get-injectors.spec.mts +250 -39
  170. package/src/__tests__/injection-token.spec.mts +293 -349
  171. package/src/__tests__/library-findings.spec.mts +8 -8
  172. package/src/__tests__/registry.spec.mts +358 -210
  173. package/src/__tests__/resolution-context.spec.mts +255 -0
  174. package/src/__tests__/scope-tracker.spec.mts +598 -0
  175. package/src/__tests__/scope-upgrade.spec.mts +808 -0
  176. package/src/__tests__/scoped-container.spec.mts +595 -0
  177. package/src/__tests__/test-container.spec.mts +293 -0
  178. package/src/__tests__/token-resolver.spec.mts +207 -0
  179. package/src/__tests__/unified-storage.spec.mts +535 -0
  180. package/src/__tests__/unit-test-container.spec.mts +405 -0
  181. package/src/__type-tests__/container.spec-d.mts +180 -0
  182. package/src/__type-tests__/factory.spec-d.mts +15 -3
  183. package/src/__type-tests__/inject.spec-d.mts +115 -20
  184. package/src/__type-tests__/injectable.spec-d.mts +69 -52
  185. package/src/__type-tests__/injection-token.spec-d.mts +176 -0
  186. package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
  187. package/src/container/abstract-container.mts +327 -0
  188. package/src/container/container.mts +142 -170
  189. package/src/container/scoped-container.mts +126 -208
  190. package/src/decorators/factory.decorator.mts +16 -11
  191. package/src/decorators/injectable.decorator.mts +20 -16
  192. package/src/enums/index.mts +2 -2
  193. package/src/enums/injectable-scope.enum.mts +1 -0
  194. package/src/enums/injectable-type.enum.mts +1 -0
  195. package/src/errors/di-error.mts +96 -0
  196. package/src/event-emitter.mts +3 -27
  197. package/src/index.mts +6 -153
  198. package/src/interfaces/container.interface.mts +13 -0
  199. package/src/interfaces/factory.interface.mts +1 -1
  200. package/src/interfaces/index.mts +1 -1
  201. package/src/internal/context/async-local-storage.mts +3 -2
  202. package/src/internal/context/async-local-storage.types.mts +1 -0
  203. package/src/internal/context/factory-context.mts +1 -0
  204. package/src/internal/context/index.mts +3 -1
  205. package/src/internal/context/resolution-context.mts +1 -0
  206. package/src/internal/context/service-initialization-context.mts +43 -0
  207. package/src/internal/core/index.mts +5 -4
  208. package/src/internal/core/instance-resolver.mts +460 -302
  209. package/src/internal/core/name-resolver.mts +196 -0
  210. package/src/internal/core/scope-tracker.mts +242 -0
  211. package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
  212. package/src/internal/core/service-invalidator.mts +290 -0
  213. package/src/internal/core/token-resolver.mts +122 -0
  214. package/src/internal/holder/holder-storage.interface.mts +11 -5
  215. package/src/internal/holder/index.mts +2 -5
  216. package/src/internal/holder/instance-holder.mts +1 -3
  217. package/src/internal/holder/unified-storage.mts +245 -0
  218. package/src/internal/index.mts +2 -1
  219. package/src/internal/lifecycle/circular-detector.mts +1 -0
  220. package/src/internal/lifecycle/index.mts +1 -1
  221. package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
  222. package/src/internal/stub-factory-class.mts +16 -0
  223. package/src/symbols/injectable-token.mts +3 -1
  224. package/src/testing/index.mts +2 -0
  225. package/src/testing/test-container.mts +546 -85
  226. package/src/testing/types.mts +117 -0
  227. package/src/testing/unit-test-container.mts +509 -0
  228. package/src/token/injection-token.mts +41 -4
  229. package/src/token/registry.mts +75 -9
  230. package/src/utils/default-injectors.mts +16 -0
  231. package/src/utils/get-injectable-token.mts +2 -3
  232. package/src/utils/get-injectors.mts +26 -15
  233. package/src/utils/index.mts +3 -1
  234. package/src/utils/types.mts +1 -0
  235. package/tsdown.config.mts +11 -1
  236. package/lib/browser/index.d.mts.map +0 -1
  237. package/lib/browser/index.mjs.map +0 -1
  238. package/lib/container-Bp1W-pWJ.d.mts.map +0 -1
  239. package/lib/container-DAKOvAgr.mjs.map +0 -1
  240. package/lib/container-DENMeJ87.cjs.map +0 -1
  241. package/lib/container-YPwvmlK2.d.cts.map +0 -1
  242. package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
  243. package/src/__tests__/async-local-storage.spec.mts +0 -333
  244. package/src/__tests__/errors.spec.mts +0 -87
  245. package/src/__tests__/factory.spec.mts +0 -137
  246. package/src/__tests__/injectable.spec.mts +0 -246
  247. package/src/__tests__/request-scope.spec.mts +0 -416
  248. package/src/__tests__/service-instantiator.spec.mts +0 -410
  249. package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
  250. package/src/__tests__/service-locator-manager.spec.mts +0 -300
  251. package/src/__tests__/service-locator.spec.mts +0 -966
  252. package/src/__tests__/unified-api.spec.mts +0 -130
  253. package/src/browser.mts +0 -11
  254. package/src/injectors.mts +0 -18
  255. package/src/internal/context/request-context.mts +0 -225
  256. package/src/internal/core/invalidator.mts +0 -437
  257. package/src/internal/core/service-locator.mts +0 -202
  258. package/src/internal/core/token-processor.mts +0 -252
  259. package/src/internal/holder/base-holder-manager.mts +0 -334
  260. package/src/internal/holder/holder-manager.mts +0 -85
  261. package/src/internal/holder/request-storage.mts +0 -127
  262. package/src/internal/holder/singleton-storage.mts +0 -92
  263. package/src/testing/README.md +0 -80
  264. 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('TestTokenWithSchema', schema)
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
- // Type tests
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 }