@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,293 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
2
+
3
+ import { Injectable } from '../decorators/injectable.decorator.mjs'
4
+ import { InjectableScope } from '../enums/index.mjs'
5
+ import { TestContainer } from '../testing/test-container.mjs'
6
+ import { InjectionToken } from '../token/injection-token.mjs'
7
+
8
+ describe('TestContainer', () => {
9
+ let container: TestContainer
10
+
11
+ beforeEach(() => {
12
+ container = new TestContainer()
13
+ })
14
+
15
+ afterEach(async () => {
16
+ await container.dispose()
17
+ })
18
+
19
+ describe('Binding API', () => {
20
+ it('should bind a value to a token', async () => {
21
+ const TOKEN = InjectionToken.create<string>('test-token')
22
+ const testValue = 'hello world'
23
+
24
+ container.bind(TOKEN).toValue(testValue)
25
+
26
+ const result = await container.get(TOKEN)
27
+ expect(result).toBe(testValue)
28
+ })
29
+
30
+ it('should bind a class to a token', async () => {
31
+ const TOKEN = InjectionToken.create<{ getValue(): string }>(
32
+ 'service-token',
33
+ )
34
+
35
+ class MockService {
36
+ getValue(): string {
37
+ return 'mock value'
38
+ }
39
+ }
40
+
41
+ container.bind(TOKEN).toClass(MockService)
42
+
43
+ const result = await container.get(TOKEN)
44
+ expect(result.getValue()).toBe('mock value')
45
+ })
46
+
47
+ it('should bind a factory to a token', async () => {
48
+ const TOKEN = InjectionToken.create<{ id: number }>('factory-token')
49
+
50
+ let counter = 0
51
+ container.bind(TOKEN).toFactory(() => ({ id: ++counter }))
52
+
53
+ const result = await container.get(TOKEN)
54
+ expect(result.id).toBe(1)
55
+ })
56
+
57
+ it('should clear all bindings', async () => {
58
+ const TOKEN = InjectionToken.create<string>('clear-token')
59
+ container.bind(TOKEN).toValue('test')
60
+
61
+ await container.get(TOKEN)
62
+ container.expectResolved(TOKEN)
63
+
64
+ await container.clear()
65
+
66
+ container.expectNotResolved(TOKEN)
67
+ })
68
+ })
69
+
70
+ describe('Assertion Helpers', () => {
71
+ @Injectable()
72
+ class SimpleService {
73
+ getValue(): string {
74
+ return 'simple'
75
+ }
76
+ }
77
+
78
+ it('should assert service is resolved', async () => {
79
+ await container.get(SimpleService)
80
+
81
+ expect(() => container.expectResolved(SimpleService)).not.toThrow()
82
+ })
83
+
84
+ it('should assert service is NOT resolved', () => {
85
+ @Injectable()
86
+ class UnresolvedService {}
87
+
88
+ expect(() => container.expectNotResolved(UnresolvedService)).not.toThrow()
89
+ })
90
+
91
+ it('should throw when expecting resolved but not resolved', () => {
92
+ @Injectable()
93
+ class NeverResolved {}
94
+
95
+ expect(() => container.expectResolved(NeverResolved)).toThrow(
96
+ /to be resolved/,
97
+ )
98
+ })
99
+
100
+ it('should throw when expecting not resolved but was resolved', async () => {
101
+ await container.get(SimpleService)
102
+
103
+ expect(() => container.expectNotResolved(SimpleService)).toThrow(
104
+ /to NOT be resolved/,
105
+ )
106
+ })
107
+
108
+ it('should assert singleton scope', async () => {
109
+ await container.get(SimpleService)
110
+
111
+ expect(() => container.expectSingleton(SimpleService)).not.toThrow()
112
+ })
113
+
114
+ it('should assert transient scope', async () => {
115
+ @Injectable({ scope: InjectableScope.Transient })
116
+ class TransientService {}
117
+
118
+ await container.get(TransientService)
119
+
120
+ expect(() => container.expectTransient(TransientService)).not.toThrow()
121
+ })
122
+
123
+ it('should assert request scope', async () => {
124
+ @Injectable({ scope: InjectableScope.Request })
125
+ class RequestService {}
126
+
127
+ expect(() => container.expectRequestScoped(RequestService)).not.toThrow()
128
+ })
129
+ })
130
+
131
+ describe('Instance Assertions', () => {
132
+ it('should assert same instance for singleton', async () => {
133
+ @Injectable()
134
+ class SingletonService {}
135
+
136
+ await expect(
137
+ container.expectSameInstance(SingletonService),
138
+ ).resolves.not.toThrow()
139
+ })
140
+
141
+ it('should assert different instances for transient', async () => {
142
+ @Injectable({ scope: InjectableScope.Transient })
143
+ class TransientService {}
144
+
145
+ await expect(
146
+ container.expectDifferentInstances(TransientService),
147
+ ).resolves.not.toThrow()
148
+ })
149
+ })
150
+
151
+ describe('Method Call Tracking', () => {
152
+ it('should record method calls', async () => {
153
+ const TOKEN = InjectionToken.create<{ doWork(x: number): number }>(
154
+ 'work-token',
155
+ )
156
+
157
+ class MockWorker {
158
+ doWork(x: number): number {
159
+ return x * 2
160
+ }
161
+ }
162
+
163
+ container.bind(TOKEN).toClass(MockWorker)
164
+ const worker = await container.get(TOKEN)
165
+
166
+ // Record the call manually (TestContainer requires manual recording)
167
+ const result = worker.doWork(5)
168
+ container.recordMethodCall(TOKEN, 'doWork', [5], result)
169
+
170
+ expect(() => container.expectCalled(TOKEN, 'doWork')).not.toThrow()
171
+ expect(() =>
172
+ container.expectCalledWith(TOKEN, 'doWork', [5]),
173
+ ).not.toThrow()
174
+ })
175
+
176
+ it('should track call count', async () => {
177
+ const TOKEN = InjectionToken.create<{ process(): void }>('process-token')
178
+
179
+ class MockProcessor {
180
+ process(): void {}
181
+ }
182
+
183
+ container.bind(TOKEN).toClass(MockProcessor)
184
+ const processor = await container.get(TOKEN)
185
+
186
+ processor.process()
187
+ container.recordMethodCall(TOKEN, 'process', [])
188
+ processor.process()
189
+ container.recordMethodCall(TOKEN, 'process', [])
190
+ processor.process()
191
+ container.recordMethodCall(TOKEN, 'process', [])
192
+
193
+ expect(() => container.expectCallCount(TOKEN, 'process', 3)).not.toThrow()
194
+ })
195
+
196
+ it('should get all method calls', async () => {
197
+ const TOKEN = InjectionToken.create<{ action(s: string): void }>(
198
+ 'action-token',
199
+ )
200
+
201
+ class MockAction {
202
+ // oxlint-disable-next-line no-unused-vars
203
+ action(s: string): void {}
204
+ }
205
+
206
+ container.bind(TOKEN).toClass(MockAction)
207
+ const action = await container.get(TOKEN)
208
+
209
+ action.action('first')
210
+ container.recordMethodCall(TOKEN, 'action', ['first'])
211
+ action.action('second')
212
+ container.recordMethodCall(TOKEN, 'action', ['second'])
213
+
214
+ const calls = container.getMethodCalls(TOKEN)
215
+ expect(calls).toHaveLength(2)
216
+ expect(calls[0].args).toEqual(['first'])
217
+ expect(calls[1].args).toEqual(['second'])
218
+ })
219
+
220
+ it('should clear method calls', async () => {
221
+ const TOKEN = InjectionToken.create<{ foo(): void }>('foo-token')
222
+
223
+ class MockFoo {
224
+ foo(): void {}
225
+ }
226
+
227
+ container.bind(TOKEN).toClass(MockFoo)
228
+ await container.get(TOKEN)
229
+ container.recordMethodCall(TOKEN, 'foo', [])
230
+
231
+ container.clearMethodCalls()
232
+
233
+ expect(container.getMethodCalls(TOKEN)).toHaveLength(0)
234
+ })
235
+ })
236
+
237
+ describe('Lifecycle Tracking', () => {
238
+ it('should track lifecycle events for value bindings', async () => {
239
+ const TOKEN = InjectionToken.create<object>('lifecycle-token')
240
+
241
+ // Value bindings record 'created' event automatically
242
+ container.bind(TOKEN).toValue({ name: 'test' })
243
+
244
+ const stats = container.getServiceStats(TOKEN)
245
+ expect(stats.lifecycleEvents.some((e) => e.event === 'created')).toBe(
246
+ true,
247
+ )
248
+ })
249
+
250
+ it('should get service stats with method calls', async () => {
251
+ const TOKEN = InjectionToken.create<{ method(): void }>('stats-token')
252
+
253
+ const mockService = { method: () => {} }
254
+ container.bind(TOKEN).toValue(mockService)
255
+
256
+ const service = await container.get(TOKEN)
257
+ service.method()
258
+ container.recordMethodCall(TOKEN, 'method', [])
259
+
260
+ const stats = container.getServiceStats(TOKEN)
261
+ expect(stats.instanceCount).toBe(1)
262
+ expect(stats.methodCalls).toHaveLength(1)
263
+ })
264
+ })
265
+
266
+ describe('Dependency Graph', () => {
267
+ it('should get dependency graph', async () => {
268
+ const TOKEN_A = InjectionToken.create<object>('service-a')
269
+ const TOKEN_B = InjectionToken.create<object>('service-b')
270
+
271
+ container.bind(TOKEN_A).toValue({ name: 'A' })
272
+ container.bind(TOKEN_B).toValue({ name: 'B' })
273
+
274
+ await container.get(TOKEN_A)
275
+ await container.get(TOKEN_B)
276
+
277
+ const graph = container.getDependencyGraph()
278
+ expect(graph.nodes).toBeDefined()
279
+ expect(Object.keys(graph.nodes).length).toBeGreaterThan(0)
280
+ })
281
+
282
+ it('should get simplified dependency graph', async () => {
283
+ const TOKEN = InjectionToken.create<object>('independent-service')
284
+
285
+ container.bind(TOKEN).toValue({ independent: true })
286
+ await container.get(TOKEN)
287
+
288
+ const graph = container.getSimplifiedDependencyGraph()
289
+ expect(graph).toBeDefined()
290
+ expect(typeof graph).toBe('object')
291
+ })
292
+ })
293
+ })
@@ -0,0 +1,207 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { z } from 'zod/v4'
3
+
4
+ import { Injectable } from '../decorators/index.mjs'
5
+ import { DIError, DIErrorCode } from '../errors/index.mjs'
6
+ import { TokenResolver } from '../internal/core/token-resolver.mjs'
7
+ import {
8
+ BoundInjectionToken,
9
+ FactoryInjectionToken,
10
+ InjectionToken,
11
+ } from '../token/injection-token.mjs'
12
+ import { getInjectableToken } from '../utils/index.mjs'
13
+
14
+ describe('TokenResolver', () => {
15
+ let resolver: TokenResolver
16
+
17
+ beforeEach(() => {
18
+ resolver = new TokenResolver()
19
+ })
20
+
21
+ describe('normalizeToken', () => {
22
+ it('should return InjectionToken as-is', () => {
23
+ const token = InjectionToken.create<string>('test')
24
+ const result = resolver.normalizeToken(token)
25
+ expect(result).toBe(token)
26
+ })
27
+
28
+ it('should return BoundInjectionToken as-is', () => {
29
+ const schema = z.object({ value: z.string() })
30
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
31
+ const bound = new BoundInjectionToken(token, { value: 'test' })
32
+ const result = resolver.normalizeToken(bound)
33
+ expect(result).toBe(bound)
34
+ })
35
+
36
+ it('should return FactoryInjectionToken as-is', () => {
37
+ const schema = z.object({ value: z.string() })
38
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
39
+ const factory = new FactoryInjectionToken(token, async () => ({
40
+ value: 'test',
41
+ }))
42
+ const result = resolver.normalizeToken(factory)
43
+ expect(result).toBe(factory)
44
+ })
45
+
46
+ it('should extract token from decorated class', () => {
47
+ @Injectable()
48
+ class TestService {}
49
+
50
+ const result = resolver.normalizeToken(TestService)
51
+ expect(result).toBeInstanceOf(InjectionToken)
52
+ expect(result).toBe(getInjectableToken(TestService))
53
+ })
54
+ })
55
+
56
+ describe('getRealToken', () => {
57
+ it('should return InjectionToken as-is', () => {
58
+ const token = InjectionToken.create<string>('test')
59
+ const result = resolver.getRealToken(token)
60
+ expect(result).toBe(token)
61
+ })
62
+
63
+ it('should unwrap BoundInjectionToken', () => {
64
+ const schema = z.object({ value: z.string() })
65
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
66
+ const bound = new BoundInjectionToken(token, { value: 'test' })
67
+ const result = resolver.getRealToken(bound)
68
+ expect(result).toBe(token)
69
+ })
70
+
71
+ it('should unwrap FactoryInjectionToken', () => {
72
+ const schema = z.object({ value: z.string() })
73
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
74
+ const factory = new FactoryInjectionToken(token, async () => ({
75
+ value: 'test',
76
+ }))
77
+ const result = resolver.getRealToken(factory)
78
+ expect(result).toBe(token)
79
+ })
80
+ })
81
+
82
+ describe('getRegistryToken', () => {
83
+ it('should normalize and unwrap class to InjectionToken', () => {
84
+ @Injectable()
85
+ class TestService {}
86
+
87
+ const result = resolver.getRegistryToken(TestService)
88
+ expect(result).toBeInstanceOf(InjectionToken)
89
+ expect(result).toBe(getInjectableToken(TestService))
90
+ })
91
+
92
+ it('should normalize and unwrap BoundInjectionToken to InjectionToken', () => {
93
+ const schema = z.object({ value: z.string() })
94
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
95
+ const bound = new BoundInjectionToken(token, { value: 'test' })
96
+
97
+ const result = resolver.getRegistryToken(bound)
98
+ expect(result).toBe(token)
99
+ })
100
+ })
101
+
102
+ describe('validateAndResolveTokenArgs', () => {
103
+ it('should pass through token without schema', () => {
104
+ const token = InjectionToken.create<string>('test')
105
+ const [error, data] = resolver.validateAndResolveTokenArgs(token)
106
+
107
+ expect(error).toBeUndefined()
108
+ expect(data.actualToken).toBe(token)
109
+ expect(data.validatedArgs).toBeUndefined()
110
+ })
111
+
112
+ it('should pass through token with valid args and schema', () => {
113
+ const schema = z.object({ value: z.string() })
114
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
115
+ const args = { value: 'hello' }
116
+
117
+ const [error, data] = resolver.validateAndResolveTokenArgs(token, args)
118
+
119
+ expect(error).toBeUndefined()
120
+ expect(data.actualToken).toBe(token)
121
+ expect(data.validatedArgs).toEqual({ value: 'hello' })
122
+ })
123
+
124
+ it('should return error for invalid args', () => {
125
+ const schema = z.object({ value: z.string() })
126
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
127
+ const args = { value: 123 } // Invalid: should be string
128
+
129
+ const [error] = resolver.validateAndResolveTokenArgs(token, args)
130
+
131
+ expect(error).toBeInstanceOf(DIError)
132
+ expect(error?.code).toBe(DIErrorCode.TokenValidationError)
133
+ })
134
+
135
+ it('should extract args from BoundInjectionToken', () => {
136
+ const schema = z.object({ value: z.string() })
137
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
138
+ const bound = new BoundInjectionToken(token, { value: 'bound-value' })
139
+
140
+ const [error, data] = resolver.validateAndResolveTokenArgs(bound)
141
+
142
+ expect(error).toBeUndefined()
143
+ expect(data.actualToken).toBe(bound)
144
+ expect(data.validatedArgs).toEqual({ value: 'bound-value' })
145
+ })
146
+
147
+ it('should return error for unresolved FactoryInjectionToken', () => {
148
+ const schema = z.object({ value: z.string() })
149
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
150
+ const factory = new FactoryInjectionToken(token, async () => ({
151
+ value: 'test',
152
+ }))
153
+
154
+ const [error] = resolver.validateAndResolveTokenArgs(factory)
155
+
156
+ expect(error).toBeInstanceOf(DIError)
157
+ expect(error?.code).toBe(DIErrorCode.FactoryTokenNotResolved)
158
+ })
159
+
160
+ it('should extract args from resolved FactoryInjectionToken', async () => {
161
+ const schema = z.object({ value: z.string() })
162
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
163
+ const factory = new FactoryInjectionToken(token, async () => ({
164
+ value: 'factory-value',
165
+ }))
166
+
167
+ // Resolve the factory first
168
+ await factory.resolve({
169
+ inject: vi.fn(),
170
+ container: {} as any,
171
+ addDestroyListener: vi.fn(),
172
+ })
173
+
174
+ const [error, data] = resolver.validateAndResolveTokenArgs(factory)
175
+
176
+ expect(error).toBeUndefined()
177
+ expect(data.actualToken).toBe(factory)
178
+ expect(data.validatedArgs).toEqual({ value: 'factory-value' })
179
+ })
180
+
181
+ it('should handle class constructor token', () => {
182
+ @Injectable()
183
+ class TestService {}
184
+
185
+ const [error, data] = resolver.validateAndResolveTokenArgs(TestService)
186
+
187
+ expect(error).toBeUndefined()
188
+ expect(data.actualToken).toBeInstanceOf(InjectionToken)
189
+ })
190
+
191
+ it('should log validation errors when logger is provided', () => {
192
+ const mockLogger = {
193
+ error: vi.fn(),
194
+ log: vi.fn(),
195
+ warn: vi.fn(),
196
+ } as unknown as Console
197
+
198
+ const resolverWithLogger = new TokenResolver(mockLogger)
199
+ const schema = z.object({ value: z.string() })
200
+ const token = InjectionToken.create<string, typeof schema>('test', schema)
201
+
202
+ resolverWithLogger.validateAndResolveTokenArgs(token, { value: 123 })
203
+
204
+ expect(mockLogger.error).toHaveBeenCalled()
205
+ })
206
+ })
207
+ })