repluggable-core 0.0.0-pr280.413 → 0.0.0-pr282.418

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 (328) hide show
  1. package/__tests__/spec-setup.js +3 -0
  2. package/dist/es/{API.d.ts → src/API.d.ts} +31 -10
  3. package/dist/es/src/API.d.ts.map +1 -0
  4. package/dist/es/src/API.js +2 -0
  5. package/dist/es/src/API.js.map +1 -0
  6. package/dist/es/src/IterableWeakMap.d.ts +20 -0
  7. package/dist/es/src/IterableWeakMap.d.ts.map +1 -0
  8. package/dist/es/src/IterableWeakMap.js +92 -0
  9. package/dist/es/src/IterableWeakMap.js.map +1 -0
  10. package/dist/es/src/appHost.d.ts +7 -0
  11. package/dist/es/src/appHost.d.ts.map +1 -0
  12. package/dist/es/src/appHost.js +848 -0
  13. package/dist/es/src/appHost.js.map +1 -0
  14. package/dist/es/src/appHostServices.d.ts +12 -0
  15. package/dist/es/src/appHostServices.d.ts.map +1 -0
  16. package/dist/es/src/appHostServices.js +25 -0
  17. package/dist/es/src/appHostServices.js.map +1 -0
  18. package/dist/es/src/appHostUtils.d.ts +4 -0
  19. package/dist/es/src/appHostUtils.d.ts.map +1 -0
  20. package/dist/es/src/appHostUtils.js +8 -0
  21. package/dist/es/src/appHostUtils.js.map +1 -0
  22. package/dist/es/src/extensionSlot.d.ts +8 -0
  23. package/dist/es/src/extensionSlot.d.ts.map +1 -0
  24. package/dist/es/src/extensionSlot.js +59 -0
  25. package/dist/es/src/extensionSlot.js.map +1 -0
  26. package/dist/es/src/hot.d.ts +14 -0
  27. package/dist/es/src/hot.d.ts.map +1 -0
  28. package/dist/es/src/hot.js +52 -0
  29. package/dist/es/src/hot.js.map +1 -0
  30. package/dist/es/src/index.d.ts +11 -0
  31. package/dist/es/src/index.d.ts.map +1 -0
  32. package/dist/es/src/index.js +10 -0
  33. package/dist/es/src/index.js.map +1 -0
  34. package/dist/es/src/installedShellsState.d.ts +19 -0
  35. package/dist/es/src/installedShellsState.d.ts.map +1 -0
  36. package/dist/es/src/installedShellsState.js +30 -0
  37. package/dist/es/src/installedShellsState.js.map +1 -0
  38. package/dist/es/src/interceptAnyObject.d.ts +7 -0
  39. package/dist/es/src/interceptAnyObject.d.ts.map +1 -0
  40. package/dist/es/src/interceptAnyObject.js +21 -0
  41. package/dist/es/src/interceptAnyObject.js.map +1 -0
  42. package/dist/es/src/interceptEntryPoints.d.ts +6 -0
  43. package/dist/es/src/interceptEntryPoints.d.ts.map +1 -0
  44. package/dist/es/src/interceptEntryPoints.js +22 -0
  45. package/dist/es/src/interceptEntryPoints.js.map +1 -0
  46. package/dist/es/src/invokeSlotCallbacks.d.ts +3 -0
  47. package/dist/es/src/invokeSlotCallbacks.d.ts.map +1 -0
  48. package/dist/es/src/invokeSlotCallbacks.js +20 -0
  49. package/dist/es/src/invokeSlotCallbacks.js.map +1 -0
  50. package/dist/es/src/loggers.d.ts +4 -0
  51. package/dist/es/src/loggers.d.ts.map +1 -0
  52. package/dist/es/src/loggers.js +100 -0
  53. package/dist/es/src/loggers.js.map +1 -0
  54. package/dist/es/src/monitorAPI.d.ts +3 -0
  55. package/dist/es/src/monitorAPI.d.ts.map +1 -0
  56. package/dist/es/src/monitorAPI.js +53 -0
  57. package/dist/es/src/monitorAPI.js.map +1 -0
  58. package/dist/es/src/repluggableAppDebug/index.d.ts +2 -0
  59. package/dist/es/src/repluggableAppDebug/index.d.ts.map +1 -0
  60. package/dist/es/src/repluggableAppDebug/index.js +2 -0
  61. package/dist/es/src/repluggableAppDebug/index.js.map +1 -0
  62. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.d.ts +15 -0
  63. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.d.ts.map +1 -0
  64. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.js +94 -0
  65. package/dist/es/src/repluggableAppDebug/performanceDebugInfo.js.map +1 -0
  66. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.d.ts +24 -0
  67. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.d.ts.map +1 -0
  68. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.js +132 -0
  69. package/dist/es/src/repluggableAppDebug/repluggableAppDebug.js.map +1 -0
  70. package/dist/es/src/tarjanGraph.d.ts +27 -0
  71. package/dist/es/src/tarjanGraph.d.ts.map +1 -0
  72. package/dist/es/src/tarjanGraph.js +94 -0
  73. package/dist/es/src/tarjanGraph.js.map +1 -0
  74. package/dist/es/src/throttledStore.d.ts +32 -0
  75. package/dist/es/src/throttledStore.d.ts.map +1 -0
  76. package/dist/es/src/throttledStore.js +240 -0
  77. package/dist/es/src/throttledStore.js.map +1 -0
  78. package/dist/es/test/IterableWeakMap.spec.d.ts +2 -0
  79. package/dist/es/test/IterableWeakMap.spec.d.ts.map +1 -0
  80. package/dist/es/test/IterableWeakMap.spec.js +218 -0
  81. package/dist/es/test/IterableWeakMap.spec.js.map +1 -0
  82. package/dist/es/test/appHost.mock.d.ts +4 -0
  83. package/dist/es/test/appHost.mock.d.ts.map +1 -0
  84. package/dist/es/test/appHost.mock.js +54 -0
  85. package/dist/es/test/appHost.mock.js.map +1 -0
  86. package/dist/es/test/appHost.spec.d.ts +2 -0
  87. package/dist/es/test/appHost.spec.d.ts.map +1 -0
  88. package/dist/es/test/appHost.spec.js +1323 -0
  89. package/dist/es/test/appHost.spec.js.map +1 -0
  90. package/dist/es/test/customExtensionSlot.spec.d.ts +2 -0
  91. package/dist/es/test/customExtensionSlot.spec.d.ts.map +1 -0
  92. package/dist/es/test/customExtensionSlot.spec.js +112 -0
  93. package/dist/es/test/customExtensionSlot.spec.js.map +1 -0
  94. package/dist/es/test/extensionSlot.spec.d.ts +2 -0
  95. package/dist/es/test/extensionSlot.spec.d.ts.map +1 -0
  96. package/dist/es/test/extensionSlot.spec.js +73 -0
  97. package/dist/es/test/extensionSlot.spec.js.map +1 -0
  98. package/dist/es/test/installedShellsState.spec.d.ts +2 -0
  99. package/dist/es/test/installedShellsState.spec.d.ts.map +1 -0
  100. package/dist/es/test/installedShellsState.spec.js +28 -0
  101. package/dist/es/test/installedShellsState.spec.js.map +1 -0
  102. package/dist/es/test/interceptAnyObject.spec.d.ts +2 -0
  103. package/dist/es/test/interceptAnyObject.spec.d.ts.map +1 -0
  104. package/dist/es/test/interceptAnyObject.spec.js +166 -0
  105. package/dist/es/test/interceptAnyObject.spec.js.map +1 -0
  106. package/dist/es/test/interceptEntryPoints.spec.d.ts +2 -0
  107. package/dist/es/test/interceptEntryPoints.spec.d.ts.map +1 -0
  108. package/dist/es/test/interceptEntryPoints.spec.js +244 -0
  109. package/dist/es/test/interceptEntryPoints.spec.js.map +1 -0
  110. package/dist/es/test/loggers.spec.d.ts +2 -0
  111. package/dist/es/test/loggers.spec.d.ts.map +1 -0
  112. package/dist/es/test/loggers.spec.js +218 -0
  113. package/dist/es/test/loggers.spec.js.map +1 -0
  114. package/dist/es/test/repluggableAppDebug.spec.d.ts +2 -0
  115. package/dist/es/test/repluggableAppDebug.spec.d.ts.map +1 -0
  116. package/dist/es/test/repluggableAppDebug.spec.js +107 -0
  117. package/dist/es/test/repluggableAppDebug.spec.js.map +1 -0
  118. package/dist/es/test/testKit.spec.d.ts +2 -0
  119. package/dist/es/test/testKit.spec.d.ts.map +1 -0
  120. package/dist/es/test/testKit.spec.js +129 -0
  121. package/dist/es/test/testKit.spec.js.map +1 -0
  122. package/dist/es/testKit/emptyLoggerOptions.d.ts +3 -0
  123. package/dist/es/testKit/emptyLoggerOptions.d.ts.map +1 -0
  124. package/dist/es/testKit/emptyLoggerOptions.js +15 -0
  125. package/dist/es/testKit/emptyLoggerOptions.js.map +1 -0
  126. package/dist/es/testKit/index.d.ts +25 -0
  127. package/dist/es/testKit/index.d.ts.map +1 -0
  128. package/dist/es/testKit/index.js +130 -0
  129. package/dist/es/testKit/index.js.map +1 -0
  130. package/dist/es/testKit/mockPackage.d.ts +21 -0
  131. package/dist/es/testKit/mockPackage.d.ts.map +1 -0
  132. package/dist/es/testKit/mockPackage.js +85 -0
  133. package/dist/es/testKit/mockPackage.js.map +1 -0
  134. package/dist/es/testKit/withConsoleErrors.d.ts +2 -0
  135. package/dist/es/testKit/withConsoleErrors.d.ts.map +1 -0
  136. package/dist/es/testKit/withConsoleErrors.js +12 -0
  137. package/dist/es/testKit/withConsoleErrors.js.map +1 -0
  138. package/dist/es/testKit/withThrowOnError.d.ts +3 -0
  139. package/dist/es/testKit/withThrowOnError.d.ts.map +1 -0
  140. package/dist/es/testKit/withThrowOnError.js +27 -0
  141. package/dist/es/testKit/withThrowOnError.js.map +1 -0
  142. package/dist/src/API.d.ts +31 -10
  143. package/dist/src/API.d.ts.map +1 -1
  144. package/dist/src/API.js +3 -0
  145. package/dist/src/API.js.map +1 -1
  146. package/dist/src/IterableWeakMap.d.ts +20 -0
  147. package/dist/src/IterableWeakMap.d.ts.map +1 -0
  148. package/dist/src/IterableWeakMap.js +96 -0
  149. package/dist/src/IterableWeakMap.js.map +1 -0
  150. package/dist/src/appHost.d.ts +7 -0
  151. package/dist/src/appHost.d.ts.map +1 -0
  152. package/dist/src/appHost.js +856 -0
  153. package/dist/src/appHost.js.map +1 -0
  154. package/dist/src/appHostServices.d.ts +12 -0
  155. package/dist/src/appHostServices.d.ts.map +1 -0
  156. package/dist/src/appHostServices.js +29 -0
  157. package/dist/src/appHostServices.js.map +1 -0
  158. package/dist/src/appHostUtils.d.ts +4 -0
  159. package/dist/src/appHostUtils.d.ts.map +1 -0
  160. package/dist/src/appHostUtils.js +16 -0
  161. package/dist/src/appHostUtils.js.map +1 -0
  162. package/dist/src/extensionSlot.d.ts +8 -0
  163. package/dist/src/extensionSlot.d.ts.map +1 -0
  164. package/dist/src/extensionSlot.js +67 -0
  165. package/dist/src/extensionSlot.js.map +1 -0
  166. package/dist/src/hot.d.ts +14 -0
  167. package/dist/src/hot.d.ts.map +1 -0
  168. package/dist/src/hot.js +56 -0
  169. package/dist/src/hot.js.map +1 -0
  170. package/dist/src/index.d.ts +10 -1
  171. package/dist/src/index.d.ts.map +1 -1
  172. package/dist/src/index.js +23 -0
  173. package/dist/src/index.js.map +1 -1
  174. package/dist/src/installedShellsState.d.ts +19 -0
  175. package/dist/src/installedShellsState.d.ts.map +1 -0
  176. package/dist/src/installedShellsState.js +38 -0
  177. package/dist/src/installedShellsState.js.map +1 -0
  178. package/dist/src/interceptAnyObject.d.ts +7 -0
  179. package/dist/src/interceptAnyObject.d.ts.map +1 -0
  180. package/dist/src/interceptAnyObject.js +28 -0
  181. package/dist/src/interceptAnyObject.js.map +1 -0
  182. package/dist/src/interceptEntryPoints.d.ts +6 -0
  183. package/dist/src/interceptEntryPoints.d.ts.map +1 -0
  184. package/dist/src/interceptEntryPoints.js +30 -0
  185. package/dist/src/interceptEntryPoints.js.map +1 -0
  186. package/dist/src/invokeSlotCallbacks.d.ts +3 -0
  187. package/dist/src/invokeSlotCallbacks.d.ts.map +1 -0
  188. package/dist/src/invokeSlotCallbacks.js +24 -0
  189. package/dist/src/invokeSlotCallbacks.js.map +1 -0
  190. package/dist/src/loggers.d.ts +4 -0
  191. package/dist/src/loggers.d.ts.map +1 -0
  192. package/dist/src/loggers.js +104 -0
  193. package/dist/src/loggers.js.map +1 -0
  194. package/dist/src/monitorAPI.d.ts +3 -0
  195. package/dist/src/monitorAPI.d.ts.map +1 -0
  196. package/dist/src/monitorAPI.js +57 -0
  197. package/dist/src/monitorAPI.js.map +1 -0
  198. package/dist/src/repluggableAppDebug/index.d.ts +2 -0
  199. package/dist/src/repluggableAppDebug/index.d.ts.map +1 -0
  200. package/dist/src/repluggableAppDebug/index.js +6 -0
  201. package/dist/src/repluggableAppDebug/index.js.map +1 -0
  202. package/dist/src/repluggableAppDebug/performanceDebugInfo.d.ts +15 -0
  203. package/dist/src/repluggableAppDebug/performanceDebugInfo.d.ts.map +1 -0
  204. package/dist/src/repluggableAppDebug/performanceDebugInfo.js +101 -0
  205. package/dist/src/repluggableAppDebug/performanceDebugInfo.js.map +1 -0
  206. package/dist/src/repluggableAppDebug/repluggableAppDebug.d.ts +24 -0
  207. package/dist/src/repluggableAppDebug/repluggableAppDebug.d.ts.map +1 -0
  208. package/dist/src/repluggableAppDebug/repluggableAppDebug.js +139 -0
  209. package/dist/src/repluggableAppDebug/repluggableAppDebug.js.map +1 -0
  210. package/dist/src/tarjanGraph.d.ts +27 -0
  211. package/dist/src/tarjanGraph.d.ts.map +1 -0
  212. package/dist/src/tarjanGraph.js +100 -0
  213. package/dist/src/tarjanGraph.js.map +1 -0
  214. package/dist/src/throttledStore.d.ts +32 -0
  215. package/dist/src/throttledStore.d.ts.map +1 -0
  216. package/dist/src/throttledStore.js +249 -0
  217. package/dist/src/throttledStore.js.map +1 -0
  218. package/dist/test/IterableWeakMap.spec.d.ts +2 -0
  219. package/dist/test/IterableWeakMap.spec.d.ts.map +1 -0
  220. package/dist/test/IterableWeakMap.spec.js +220 -0
  221. package/dist/test/IterableWeakMap.spec.js.map +1 -0
  222. package/dist/test/appHost.mock.d.ts +4 -0
  223. package/dist/test/appHost.mock.d.ts.map +1 -0
  224. package/dist/test/appHost.mock.js +59 -0
  225. package/dist/test/appHost.mock.js.map +1 -0
  226. package/dist/test/appHost.spec.d.ts +2 -0
  227. package/dist/test/appHost.spec.d.ts.map +1 -0
  228. package/dist/test/appHost.spec.js +1328 -0
  229. package/dist/test/appHost.spec.js.map +1 -0
  230. package/dist/test/customExtensionSlot.spec.d.ts +2 -0
  231. package/dist/test/customExtensionSlot.spec.d.ts.map +1 -0
  232. package/dist/test/customExtensionSlot.spec.js +117 -0
  233. package/dist/test/customExtensionSlot.spec.js.map +1 -0
  234. package/dist/test/extensionSlot.spec.d.ts +2 -0
  235. package/dist/test/extensionSlot.spec.d.ts.map +1 -0
  236. package/dist/test/extensionSlot.spec.js +75 -0
  237. package/dist/test/extensionSlot.spec.js.map +1 -0
  238. package/dist/test/installedShellsState.spec.d.ts +2 -0
  239. package/dist/test/installedShellsState.spec.d.ts.map +1 -0
  240. package/dist/test/installedShellsState.spec.js +30 -0
  241. package/dist/test/installedShellsState.spec.js.map +1 -0
  242. package/dist/test/interceptAnyObject.spec.d.ts +2 -0
  243. package/dist/test/interceptAnyObject.spec.d.ts.map +1 -0
  244. package/dist/test/interceptAnyObject.spec.js +171 -0
  245. package/dist/test/interceptAnyObject.spec.js.map +1 -0
  246. package/dist/test/interceptEntryPoints.spec.d.ts +2 -0
  247. package/dist/test/interceptEntryPoints.spec.d.ts.map +1 -0
  248. package/dist/test/interceptEntryPoints.spec.js +249 -0
  249. package/dist/test/interceptEntryPoints.spec.js.map +1 -0
  250. package/dist/test/loggers.spec.d.ts +2 -0
  251. package/dist/test/loggers.spec.d.ts.map +1 -0
  252. package/dist/test/loggers.spec.js +220 -0
  253. package/dist/test/loggers.spec.js.map +1 -0
  254. package/dist/test/repluggableAppDebug.spec.d.ts +2 -0
  255. package/dist/test/repluggableAppDebug.spec.d.ts.map +1 -0
  256. package/dist/test/repluggableAppDebug.spec.js +109 -0
  257. package/dist/test/repluggableAppDebug.spec.js.map +1 -0
  258. package/dist/test/testKit.spec.d.ts +2 -0
  259. package/dist/test/testKit.spec.d.ts.map +1 -0
  260. package/dist/test/testKit.spec.js +134 -0
  261. package/dist/test/testKit.spec.js.map +1 -0
  262. package/dist/testKit/emptyLoggerOptions.d.ts +3 -0
  263. package/dist/testKit/emptyLoggerOptions.d.ts.map +1 -0
  264. package/dist/testKit/emptyLoggerOptions.js +18 -0
  265. package/dist/testKit/emptyLoggerOptions.js.map +1 -0
  266. package/dist/testKit/index.d.ts +25 -0
  267. package/dist/testKit/index.d.ts.map +1 -0
  268. package/dist/testKit/index.js +158 -0
  269. package/dist/testKit/index.js.map +1 -0
  270. package/dist/testKit/mockPackage.d.ts +21 -0
  271. package/dist/testKit/mockPackage.d.ts.map +1 -0
  272. package/dist/testKit/mockPackage.js +91 -0
  273. package/dist/testKit/mockPackage.js.map +1 -0
  274. package/dist/testKit/withConsoleErrors.d.ts +2 -0
  275. package/dist/testKit/withConsoleErrors.d.ts.map +1 -0
  276. package/dist/testKit/withConsoleErrors.js +16 -0
  277. package/dist/testKit/withConsoleErrors.js.map +1 -0
  278. package/dist/testKit/withThrowOnError.d.ts +3 -0
  279. package/dist/testKit/withThrowOnError.d.ts.map +1 -0
  280. package/dist/testKit/withThrowOnError.js +31 -0
  281. package/dist/testKit/withThrowOnError.js.map +1 -0
  282. package/jest.config.js +16 -0
  283. package/package.json +5 -3
  284. package/src/API.ts +474 -378
  285. package/src/IterableWeakMap.ts +121 -0
  286. package/src/appHost.ts +1383 -0
  287. package/src/appHostServices.ts +42 -0
  288. package/src/appHostUtils.ts +11 -0
  289. package/src/extensionSlot.ts +96 -0
  290. package/src/hot.ts +53 -0
  291. package/src/index.ts +55 -3
  292. package/src/installedShellsState.ts +54 -0
  293. package/src/interceptAnyObject.ts +39 -0
  294. package/src/interceptEntryPoints.ts +25 -0
  295. package/src/invokeSlotCallbacks.ts +20 -0
  296. package/src/loggers.ts +112 -0
  297. package/src/monitorAPI.ts +71 -0
  298. package/src/repluggableAppDebug/debug.d.ts +37 -0
  299. package/src/repluggableAppDebug/index.ts +1 -0
  300. package/src/repluggableAppDebug/performanceDebugInfo.ts +96 -0
  301. package/src/repluggableAppDebug/repluggableAppDebug.ts +192 -0
  302. package/src/tarjanGraph.ts +96 -0
  303. package/src/throttledStore.ts +355 -0
  304. package/test/IterableWeakMap.spec.ts +284 -0
  305. package/test/appHost.mock.ts +58 -0
  306. package/test/appHost.spec.ts +1629 -0
  307. package/test/customExtensionSlot.spec.ts +153 -0
  308. package/test/extensionSlot.spec.ts +94 -0
  309. package/test/installedShellsState.spec.ts +39 -0
  310. package/test/interceptAnyObject.spec.ts +217 -0
  311. package/test/interceptEntryPoints.spec.ts +291 -0
  312. package/test/loggers.spec.ts +288 -0
  313. package/test/repluggableAppDebug.spec.ts +118 -0
  314. package/test/testKit.spec.tsx +153 -0
  315. package/testKit/emptyLoggerOptions.ts +16 -0
  316. package/testKit/index.ts +172 -0
  317. package/testKit/mockPackage.ts +99 -0
  318. package/testKit/package.json +3 -0
  319. package/testKit/withConsoleErrors.ts +12 -0
  320. package/testKit/withThrowOnError.ts +34 -0
  321. package/tsconfig.json +11 -1
  322. package/dist/es/API.d.ts.map +0 -1
  323. package/dist/es/API.js +0 -2
  324. package/dist/es/API.js.map +0 -1
  325. package/dist/es/index.d.ts +0 -2
  326. package/dist/es/index.d.ts.map +0 -1
  327. package/dist/es/index.js +0 -2
  328. package/dist/es/index.js.map +0 -1
@@ -0,0 +1,1629 @@
1
+ import _ from 'lodash'
2
+
3
+ import { createAppHost, mainViewSlotKey, makeLazyEntryPoint, stateSlotKey } from '../src/appHost'
4
+
5
+ import {
6
+ AnySlotKey,
7
+ AppHost,
8
+ EntryPoint,
9
+ Shell,
10
+ SlotKey,
11
+ AppHostOptions,
12
+ HostLogger,
13
+ PrivateShell,
14
+ ObservableState,
15
+ PrivateAppHost
16
+ } from '../src/API'
17
+ import {
18
+ MockAPI,
19
+ mockPackage,
20
+ mockPackageWithPublicAPI,
21
+ MockPublicAPI,
22
+ mockShellInitialState,
23
+ mockShellStateKey,
24
+ addMockShell,
25
+ emptyLoggerOptions
26
+ } from '../testKit'
27
+
28
+ import { AppHostAPI, AppHostServicesEntryPointName, AppHostServicesProvider } from '../src/appHostServices'
29
+ import { createCircularEntryPoints, createDirectCircularEntryPoints } from './appHost.mock'
30
+ import { ConsoleHostLogger } from '../src/loggers'
31
+
32
+ const testHostOptions: AppHostOptions = {
33
+ monitoring: { disableMonitoring: true }
34
+ }
35
+
36
+ const createHostWithDependantPackages = (DependencyAPI: AnySlotKey) => {
37
+ const MockAPI2: SlotKey<{}> = { name: 'Mock-API-2' }
38
+ const dependentPackage: EntryPoint[] = [
39
+ {
40
+ name: 'DEPENDENT_MOCK_ENTRY_POINT_1',
41
+ getDependencyAPIs() {
42
+ return [DependencyAPI]
43
+ }
44
+ },
45
+ {
46
+ name: 'DEPENDENT_MOCK_ENTRY_POINT_2',
47
+ getDependencyAPIs() {
48
+ return [DependencyAPI]
49
+ },
50
+ declareAPIs() {
51
+ return [MockAPI2]
52
+ },
53
+ attach(shell: Shell) {
54
+ shell.contributeAPI(MockAPI2, () => ({}))
55
+ }
56
+ }
57
+ ]
58
+
59
+ const deeplyDependentPackage: EntryPoint[] = [
60
+ {
61
+ name: 'DEPENDENT_MOCK_ENTRY_POINT_3',
62
+ getDependencyAPIs() {
63
+ return [MockAPI2]
64
+ }
65
+ }
66
+ ]
67
+
68
+ let getHelperShell: () => Shell = () => {
69
+ throw new Error()
70
+ }
71
+ const helperEntryPoint: EntryPoint = {
72
+ name: 'TEST_HELPER',
73
+ declareAPIs() {
74
+ return [DependencyAPI]
75
+ },
76
+ attach(shell: Shell) {
77
+ getHelperShell = () => shell
78
+ }
79
+ }
80
+
81
+ return {
82
+ host: createAppHost([dependentPackage, deeplyDependentPackage, helperEntryPoint], testHostOptions),
83
+ dependentPackage,
84
+ deeplyDependentPackage,
85
+ helperShell: getHelperShell()
86
+ }
87
+ }
88
+
89
+ interface ObservableValueState {
90
+ stateValue: number
91
+ }
92
+ interface ObservableValueSelector {
93
+ getStateValue(): number
94
+ }
95
+
96
+ interface EntryPointStateSnapshot {
97
+ canUseStore: boolean
98
+ canUseAPIs: boolean
99
+ wasInitializationCompleted: boolean
100
+ }
101
+
102
+ describe('App Host', () => {
103
+ beforeEach(() => {
104
+ spyOn(ConsoleHostLogger, 'log')
105
+ })
106
+
107
+ it('should create an app host', () => {
108
+ const host = createAppHost([], testHostOptions)
109
+ expect(host).toBeInstanceOf(Object)
110
+ })
111
+
112
+ describe('AppHost Options', () => {
113
+ it('should use ConsoleHostLogger by default', () => {
114
+ const host = createAppHost([], testHostOptions)
115
+ expect(host.log).toBe(ConsoleHostLogger)
116
+ })
117
+ it('should use custom host logger if specified', () => {
118
+ const logger: HostLogger = {
119
+ log() {},
120
+ spanRoot() {
121
+ return {
122
+ end() {}
123
+ } as any
124
+ },
125
+ spanChild() {
126
+ return {
127
+ end() {}
128
+ } as any
129
+ }
130
+ }
131
+ const options: AppHostOptions = {
132
+ logger,
133
+ monitoring: {}
134
+ }
135
+
136
+ const host = createAppHost([], options)
137
+
138
+ expect(host.log).toBe(logger)
139
+ })
140
+ })
141
+
142
+ describe('Packages Installation', () => {
143
+ it('should NOT throw on circular dependency if check is disabled in host options', () => {
144
+ const circularPackages = createDirectCircularEntryPoints()
145
+ const hostOptionsWithDisabledCircularCheck: AppHostOptions = {
146
+ monitoring: {},
147
+ disableCheckCircularDependencies: true
148
+ }
149
+ expect(() => createAppHost(circularPackages, hostOptionsWithDisabledCircularCheck)).not.toThrow()
150
+ })
151
+
152
+ it('should throw on direct circular API dependency (private keys)', () => {
153
+ const circularPackages = createDirectCircularEntryPoints()
154
+ expect(() => createAppHost(circularPackages, testHostOptions)).toThrowError()
155
+ })
156
+
157
+ it('should throw on direct circular API dependency (public keys)', () => {
158
+ const circularPackages = createDirectCircularEntryPoints(true)
159
+ expect(() => createAppHost(circularPackages, testHostOptions)).toThrowError()
160
+ })
161
+ it('should throw on circular API dependency (private keys)', () => {
162
+ const circularPackages = createCircularEntryPoints()
163
+ expect(() => createAppHost(circularPackages, testHostOptions)).toThrowError()
164
+ })
165
+
166
+ it('should throw on circular API dependency (public keys)', () => {
167
+ const circularPackages = createCircularEntryPoints(true)
168
+ expect(() => createAppHost(circularPackages, testHostOptions)).toThrowError()
169
+ })
170
+
171
+ it('should throw when dynamically adding a shell with circular dependency', () => {
172
+ const circularPackages = createCircularEntryPoints(true)
173
+ const nonCircular = circularPackages.slice(0, 3)
174
+ const circularEP = _.last(circularPackages) as EntryPoint
175
+ const host = createAppHost(nonCircular, testHostOptions)
176
+
177
+ expect(() => host.addShells([circularEP])).toThrow()
178
+ })
179
+
180
+ it('should install initial packages', () => {
181
+ const host = createAppHost([mockPackage], testHostOptions)
182
+ expect(host.hasShell(mockPackage.name)).toBe(true)
183
+ })
184
+
185
+ it('should install packages after initial installations', async () => {
186
+ const host = createAppHost([], testHostOptions)
187
+ expect(host.hasShell(mockPackage.name)).toBe(false)
188
+
189
+ await host.addShells([mockPackage])
190
+
191
+ expect(host.hasShell(mockPackage.name)).toBe(true)
192
+ })
193
+
194
+ it('should uninstall shell', async () => {
195
+ const host = createAppHost([mockPackage], testHostOptions)
196
+
197
+ await host.removeShells([mockPackage.name])
198
+
199
+ expect(host.hasShell(mockPackage.name)).toBe(false)
200
+ })
201
+
202
+ it('should not install multiple shells with the same name', () => {
203
+ expect(() => createAppHost([mockPackage, _.pick(mockPackage, 'name')], testHostOptions)).toThrow()
204
+ })
205
+
206
+ it('should install lazy shells', () => {
207
+ const lazyEntryPoint = makeLazyEntryPoint(mockPackage.name, async () => mockPackage)
208
+ const host = createAppHost([lazyEntryPoint], testHostOptions)
209
+ expect(host.hasShell(lazyEntryPoint.name)).toBe(true)
210
+ })
211
+ })
212
+
213
+ describe('EntryPoint lifecycle state', () => {
214
+ const takeEntryPointStateSnapshot = (shell: Shell): EntryPointStateSnapshot => {
215
+ return {
216
+ canUseStore: shell.canUseStore(),
217
+ canUseAPIs: shell.canUseAPIs(),
218
+ wasInitializationCompleted: shell.wasInitializationCompleted()
219
+ }
220
+ }
221
+
222
+ it('should be incomplete during appHost initialization', () => {
223
+ let stateInAttach: EntryPointStateSnapshot | undefined
224
+ let stateInExtend: EntryPointStateSnapshot | undefined
225
+
226
+ const entryPoint: EntryPoint = {
227
+ name: 'TEST_EP',
228
+ attach(shell) {
229
+ stateInAttach = takeEntryPointStateSnapshot(shell)
230
+ },
231
+ extend(shell) {
232
+ stateInExtend = takeEntryPointStateSnapshot(shell)
233
+ }
234
+ }
235
+
236
+ createAppHost([entryPoint], testHostOptions)
237
+
238
+ expect(stateInAttach).toMatchObject({
239
+ wasInitializationCompleted: false,
240
+ canUseAPIs: false,
241
+ canUseStore: false
242
+ })
243
+ expect(stateInExtend).toMatchObject({
244
+ wasInitializationCompleted: false,
245
+ canUseAPIs: true,
246
+ canUseStore: true
247
+ })
248
+ })
249
+
250
+ it('should be complete after appHost initialization', () => {
251
+ let shell: Shell | undefined
252
+
253
+ const entryPoint: EntryPoint = {
254
+ name: 'TEST_EP',
255
+ attach(_shell) {
256
+ shell = _shell
257
+ }
258
+ }
259
+
260
+ createAppHost([entryPoint], testHostOptions)
261
+ const stateAfter = shell && takeEntryPointStateSnapshot(shell)
262
+
263
+ expect(stateAfter).toMatchObject({
264
+ wasInitializationCompleted: true,
265
+ canUseAPIs: true,
266
+ canUseStore: true
267
+ })
268
+ })
269
+
270
+ it('should be incomplete during lifecycle of added entry point', () => {
271
+ let stateInAttach: EntryPointStateSnapshot | undefined
272
+ let stateInExtend: EntryPointStateSnapshot | undefined
273
+
274
+ const host = createAppHost([], testHostOptions)
275
+ addMockShell(host, {
276
+ attach(shell) {
277
+ stateInAttach = takeEntryPointStateSnapshot(shell)
278
+ },
279
+ extend(shell) {
280
+ stateInExtend = takeEntryPointStateSnapshot(shell)
281
+ }
282
+ })
283
+
284
+ expect(stateInAttach).toMatchObject({
285
+ wasInitializationCompleted: false,
286
+ canUseAPIs: false,
287
+ canUseStore: false
288
+ })
289
+ expect(stateInExtend).toMatchObject({
290
+ wasInitializationCompleted: false,
291
+ canUseAPIs: true,
292
+ canUseStore: true
293
+ })
294
+ })
295
+
296
+ it('should be complete after lifecycle of added entry point', () => {
297
+ const host = createAppHost([], testHostOptions)
298
+
299
+ const shell = addMockShell(host)
300
+
301
+ const stateAfter = takeEntryPointStateSnapshot(shell)
302
+ expect(stateAfter).toMatchObject({
303
+ wasInitializationCompleted: true,
304
+ canUseAPIs: true,
305
+ canUseStore: true
306
+ })
307
+ })
308
+
309
+ it('should be incomplete during execution of late initializer', () => {
310
+ let state: EntryPointStateSnapshot | undefined
311
+
312
+ const host = createAppHost([], testHostOptions)
313
+ const shell = addMockShell(host)
314
+ expect(shell.wasInitializationCompleted()).toBe(true)
315
+
316
+ shell.runLateInitializer(() => {
317
+ state = takeEntryPointStateSnapshot(shell)
318
+ })
319
+
320
+ expect(state).toMatchObject({
321
+ canUseStore: true,
322
+ canUseAPIs: true,
323
+ wasInitializationCompleted: false
324
+ })
325
+ })
326
+
327
+ it('should be complete after execution of late initializer', () => {
328
+ const host = createAppHost([], testHostOptions)
329
+ const shell = addMockShell(host)
330
+ expect(shell.wasInitializationCompleted()).toBe(true)
331
+
332
+ shell.runLateInitializer(() => {})
333
+
334
+ const stateAfter = takeEntryPointStateSnapshot(shell)
335
+ expect(stateAfter).toMatchObject({
336
+ canUseStore: true,
337
+ canUseAPIs: true,
338
+ wasInitializationCompleted: true
339
+ })
340
+ })
341
+
342
+ it('should be complete after execution of late initializer that throws', () => {
343
+ const host = createAppHost([], testHostOptions)
344
+ const shell = addMockShell(host)
345
+ expect(shell.wasInitializationCompleted()).toBe(true)
346
+
347
+ expect(() => {
348
+ shell.runLateInitializer(() => {
349
+ throw new Error('TEST-ERROR')
350
+ })
351
+ }).toThrow('TEST-ERROR')
352
+
353
+ const stateAfter = takeEntryPointStateSnapshot(shell)
354
+ expect(stateAfter).toMatchObject({
355
+ canUseStore: true,
356
+ canUseAPIs: true,
357
+ wasInitializationCompleted: true
358
+ })
359
+ })
360
+ })
361
+
362
+ _.forEach(
363
+ [
364
+ {
365
+ testCase: 'private API keys',
366
+ dependencyAPI: MockAPI,
367
+ providerPackage: mockPackage
368
+ },
369
+ {
370
+ testCase: 'public API keys',
371
+ dependencyAPI: { name: MockPublicAPI.name, public: true },
372
+ providerPackage: mockPackageWithPublicAPI
373
+ }
374
+ ],
375
+ ({ testCase, dependencyAPI, providerPackage }) => {
376
+ describe(`Dependency entry point installation (${testCase})`, () => {
377
+ it('should not install dependent entry point until dependency is installed', async () => {
378
+ const { host, dependentPackage } = createHostWithDependantPackages(dependencyAPI)
379
+
380
+ expect(host.hasShell(dependentPackage[0].name)).toBe(false)
381
+
382
+ await host.addShells([providerPackage])
383
+
384
+ expect(host.hasShell(dependentPackage[0].name)).toBe(true)
385
+ })
386
+
387
+ it('should install all dependent entry points chain when dependencies are installed from entry point', async () => {
388
+ const { host, dependentPackage, deeplyDependentPackage } = createHostWithDependantPackages(dependencyAPI)
389
+
390
+ expect(host.hasShell(dependentPackage[0].name)).toBe(false)
391
+ expect(host.hasShell(dependentPackage[1].name)).toBe(false)
392
+ expect(host.hasShell(deeplyDependentPackage[0].name)).toBe(false)
393
+
394
+ await host.addShells([providerPackage])
395
+
396
+ expect(host.hasShell(dependentPackage[0].name)).toBe(true)
397
+ expect(host.hasShell(dependentPackage[1].name)).toBe(true)
398
+ expect(host.hasShell(deeplyDependentPackage[0].name)).toBe(true)
399
+ })
400
+
401
+ it('should install all dependent entry points chain when dependencies are installed outside of entry point', async () => {
402
+ const { host, dependentPackage, deeplyDependentPackage, helperShell } = createHostWithDependantPackages(dependencyAPI)
403
+
404
+ expect(host.hasShell(dependentPackage[0].name)).toBe(false)
405
+ expect(host.hasShell(dependentPackage[1].name)).toBe(false)
406
+ expect(host.hasShell(deeplyDependentPackage[0].name)).toBe(false)
407
+
408
+ helperShell.contributeAPI(dependencyAPI, () => ({
409
+ stubTrue: () => true,
410
+ getNewObject: () => ({})
411
+ }))
412
+
413
+ expect(host.hasShell(dependentPackage[0].name)).toBe(true)
414
+ expect(host.hasShell(dependentPackage[1].name)).toBe(true)
415
+ expect(host.hasShell(deeplyDependentPackage[0].name)).toBe(true)
416
+ })
417
+
418
+ it('should uninstall all dependent entry points chain when dependencies are uninstalled', async () => {
419
+ const { host, dependentPackage, deeplyDependentPackage } = createHostWithDependantPackages(dependencyAPI)
420
+
421
+ await host.addShells([providerPackage])
422
+ await host.removeShells([providerPackage.name])
423
+
424
+ expect(host.hasShell(dependentPackage[0].name)).toBe(false)
425
+ expect(host.hasShell(dependentPackage[1].name)).toBe(false)
426
+ expect(host.hasShell(deeplyDependentPackage[0].name)).toBe(false)
427
+ })
428
+ })
429
+ }
430
+ )
431
+
432
+ describe('Host extension slots', () => {
433
+ it('should have a state extension slot', () => {
434
+ const host = createAppHost([], testHostOptions)
435
+ expect(host.getSlot(stateSlotKey)).toBeTruthy()
436
+ })
437
+
438
+ it('should have a main view extension slot', () => {
439
+ const host = createAppHost([], testHostOptions)
440
+ expect(host.getSlot(mainViewSlotKey)).toBeTruthy()
441
+ })
442
+
443
+ it('should retrieve all slot keys', () => {
444
+ const sortSlotKeys = (slotKeys: AnySlotKey[]) => _.sortBy(slotKeys, 'name')
445
+
446
+ const host = createAppHost([mockPackage], testHostOptions)
447
+
448
+ const actual = sortSlotKeys(host.getAllSlotKeys())
449
+ const expected = sortSlotKeys([AppHostAPI, mainViewSlotKey, stateSlotKey, MockAPI])
450
+
451
+ expect(actual).toEqual(expected)
452
+ })
453
+
454
+ describe('private API slot key', () => {
455
+ it('should equal itself', () => {
456
+ const host = createAppHost([mockPackage], testHostOptions)
457
+
458
+ const API = host.getAPI(MockAPI)
459
+
460
+ expect(API).toBeTruthy()
461
+ })
462
+
463
+ it('should not equal another key with same name', () => {
464
+ const host = createAppHost([mockPackage], testHostOptions)
465
+ const fakeKey: SlotKey<MockAPI> = { name: MockAPI.name }
466
+
467
+ expect(() => {
468
+ host.getAPI(fakeKey)
469
+ }).toThrowError(new RegExp(MockAPI.name))
470
+ })
471
+
472
+ it('should not equal another key with same name that claims it is public', () => {
473
+ const host = createAppHost([mockPackage], testHostOptions)
474
+ const fakeKey1: SlotKey<MockAPI> = {
475
+ name: MockAPI.name,
476
+ public: true
477
+ }
478
+ const fakeKey2: SlotKey<MockAPI> = {
479
+ name: MockAPI.name,
480
+ public: false
481
+ }
482
+ const fakeKey3: any = {
483
+ name: MockAPI.name,
484
+ public: 'zzz'
485
+ }
486
+
487
+ expect(() => host.getAPI(fakeKey1)).toThrowError(new RegExp(MockAPI.name))
488
+ expect(() => host.getAPI(fakeKey2)).toThrowError(new RegExp(MockAPI.name))
489
+ expect(() => host.getAPI(fakeKey3)).toThrowError(new RegExp(MockAPI.name))
490
+ })
491
+ })
492
+
493
+ describe('public API slot key', () => {
494
+ it('should equal itself', () => {
495
+ const host = createAppHost([mockPackageWithPublicAPI], testHostOptions)
496
+
497
+ const API = host.getAPI(MockPublicAPI)
498
+
499
+ expect(API).toBeTruthy()
500
+ })
501
+
502
+ it('should equal another key with same name that claims it is public', () => {
503
+ const host = createAppHost([mockPackageWithPublicAPI], testHostOptions)
504
+ const anotherKey: SlotKey<MockAPI> = {
505
+ name: MockPublicAPI.name,
506
+ public: true
507
+ }
508
+
509
+ const API = host.getAPI(anotherKey)
510
+
511
+ expect(API).toBeTruthy()
512
+ })
513
+
514
+ it('should not equal another key with same name than does not claim it is public', () => {
515
+ const host = createAppHost([mockPackageWithPublicAPI], testHostOptions)
516
+ const anotherKey1: SlotKey<MockAPI> = {
517
+ name: MockPublicAPI.name
518
+ }
519
+ const anotherKey2: SlotKey<MockAPI> = {
520
+ name: MockPublicAPI.name,
521
+ public: false
522
+ }
523
+ const anotherKey3: any = {
524
+ name: MockPublicAPI.name,
525
+ public: 'zzz'
526
+ }
527
+
528
+ expect(() => host.getAPI(anotherKey1)).toThrowError(new RegExp(MockPublicAPI.name))
529
+ expect(() => host.getAPI(anotherKey2)).toThrowError(new RegExp(MockPublicAPI.name))
530
+ expect(() => host.getAPI(anotherKey3)).toThrowError(new RegExp(MockPublicAPI.name))
531
+ })
532
+ })
533
+ })
534
+
535
+ describe('Shell extension slots', () => {
536
+ it('should allow contribution', async () => {
537
+ const host = createAppHost([], testHostOptions)
538
+ interface SlotItem {
539
+ value: string
540
+ }
541
+ interface MockAPIA {
542
+ contributeItem(fromShell: Shell, item: SlotItem): void
543
+ }
544
+ const slotKey: SlotKey<SlotItem> = {
545
+ name: 'MOCK_SLOT'
546
+ }
547
+ const MockAPIA: SlotKey<MockAPIA> = {
548
+ name: 'MOCK_API_A'
549
+ }
550
+ const contributedItemA = { value: 'A' }
551
+ const contributedItemB = { value: 'B' }
552
+ const entryPointA: EntryPoint = {
553
+ name: 'MOCK_A',
554
+ declareAPIs() {
555
+ return [MockAPIA]
556
+ },
557
+ attach(shell) {
558
+ shell.declareSlot(slotKey)
559
+ shell.contributeAPI(MockAPIA, () => ({
560
+ contributeItem(fromShell, item) {
561
+ shell.getSlot(slotKey).contribute(fromShell, item)
562
+ }
563
+ }))
564
+ },
565
+ extend(shell) {
566
+ shell.getAPI(MockAPIA).contributeItem(shell, contributedItemA)
567
+ }
568
+ }
569
+
570
+ const entryPointB: EntryPoint = {
571
+ name: 'MOCK_B',
572
+ getDependencyAPIs() {
573
+ return [MockAPIA]
574
+ },
575
+ extend(shell) {
576
+ shell.getAPI(MockAPIA).contributeItem(shell, contributedItemB)
577
+ }
578
+ }
579
+ await host.addShells([entryPointA, entryPointB])
580
+
581
+ const getItems = () =>
582
+ host
583
+ .getSlot(slotKey)
584
+ .getItems()
585
+ .map(({ contribution }) => contribution)
586
+
587
+ expect(getItems()).toEqual([contributedItemA, contributedItemB])
588
+
589
+ await host.removeShells(['MOCK_B'])
590
+ expect(getItems()).toEqual([contributedItemA])
591
+
592
+ await host.addShells([entryPointB])
593
+ expect(getItems()).toEqual([contributedItemA, contributedItemB])
594
+ })
595
+
596
+ it('should not allow direct access to slots from other shells', async () => {
597
+ const host = createAppHost([], testHostOptions)
598
+ interface SlotItem {
599
+ value: string
600
+ }
601
+ interface MockAPIA {
602
+ contributeItem(fromShell: Shell, item: SlotItem): void
603
+ }
604
+ const slotKey: SlotKey<SlotItem> = {
605
+ name: 'MOCK_SLOT'
606
+ }
607
+ const MockAPIA: SlotKey<MockAPIA> = {
608
+ name: 'MOCK_API_A'
609
+ }
610
+ const entryPointA: EntryPoint = {
611
+ name: 'MOCK_A',
612
+ declareAPIs() {
613
+ return [MockAPIA]
614
+ },
615
+ attach(shell) {
616
+ shell.declareSlot(slotKey)
617
+ }
618
+ }
619
+ const entryPointB: EntryPoint = {
620
+ name: 'MOCK_B',
621
+ extend(shell) {
622
+ const errorString = `Shell '${entryPointB.name}' is trying to get slot '${slotKey.name}' that is owned by '${entryPointA.name}'`
623
+ expect(() => shell.getSlot(slotKey)).toThrowError(errorString)
624
+ }
625
+ }
626
+ await host.addShells([entryPointA])
627
+ })
628
+ })
629
+
630
+ describe('Host State', () => {
631
+ it('should have a store with initial state', () => {
632
+ const host = createAppHost([], testHostOptions)
633
+ expect(host.getStore().getState()).toEqual({
634
+ $installedShells: {
635
+ installedShells: {
636
+ [AppHostServicesEntryPointName]: true
637
+ }
638
+ }
639
+ })
640
+ })
641
+ })
642
+
643
+ describe('Entry Point Contributions', () => {
644
+ it('should contribute API', () => {
645
+ const host = createAppHost([mockPackage], testHostOptions)
646
+ expect(host.getAPI(MockAPI)).toBeTruthy()
647
+ })
648
+
649
+ it('should contribute API after initial installations', async () => {
650
+ const host = createAppHost([], testHostOptions)
651
+ expect(() => host.getAPI(MockAPI)).toThrow()
652
+
653
+ await host.addShells([mockPackage])
654
+ expect(host.getAPI(MockAPI)).toBeTruthy()
655
+ })
656
+
657
+ it('should execute detach and attach sequence according to dependencies', async () => {
658
+ const MockAPI2: SlotKey<MockAPI> = { name: 'MOCK' }
659
+ const dependantEntryPoint: EntryPoint = {
660
+ name: 'EP1',
661
+ getDependencyAPIs: () => [MockAPI],
662
+ declareAPIs: () => [MockAPI2],
663
+ attach(shell) {
664
+ shell.contributeAPI(MockAPI2, () => shell.getAPI(MockAPI))
665
+ },
666
+ detach(shell) {
667
+ shell.getAPI(MockAPI).stubTrue()
668
+ }
669
+ }
670
+ const dependantEntryPoint2: EntryPoint = {
671
+ name: 'EP2',
672
+ getDependencyAPIs: () => [MockAPI, MockAPI2],
673
+ detach(shell) {
674
+ shell.getAPI(MockAPI).stubTrue()
675
+ shell.getAPI(MockAPI2).stubTrue()
676
+ }
677
+ }
678
+ const host = createAppHost([dependantEntryPoint2, mockPackage, dependantEntryPoint], testHostOptions)
679
+ expect(() => host.removeShells([mockPackage.name])).not.toThrow()
680
+ expect(host.hasShell(mockPackage.name)).toBe(false)
681
+ expect(host.hasShell(dependantEntryPoint.name)).toBe(false)
682
+ expect(host.hasShell(dependantEntryPoint2.name)).toBe(false)
683
+
684
+ await host.addShells([mockPackage])
685
+ expect(host.hasShell(dependantEntryPoint.name)).toBe(true)
686
+ expect(host.hasShell(dependantEntryPoint2.name)).toBe(true)
687
+ })
688
+
689
+ it('should contribute state', async () => {
690
+ const getMockShellState = (host: AppHost) => _.get(host.getStore().getState(), [mockPackage.name, mockShellStateKey], null)
691
+
692
+ const appHost = createAppHost([], testHostOptions)
693
+ expect(getMockShellState(appHost)).toBeNull()
694
+
695
+ await appHost.addShells([mockPackage])
696
+ expect(getMockShellState(appHost)).toEqual(mockShellInitialState)
697
+ })
698
+
699
+ describe('memoizeForState', () => {
700
+ interface MemoizedAPIInterface {
701
+ getNewObject(): object
702
+ }
703
+ const memoizedAPI: SlotKey<MemoizedAPIInterface> = {
704
+ name: 'memoizedAPI'
705
+ }
706
+
707
+ function contributeMemoizedAPI(host: AppHost, shouldClear?: () => boolean): Shell {
708
+ const createAPI = (shell: Shell): MemoizedAPIInterface => ({
709
+ getNewObject: shell.memoizeForState(() => ({}), _.stubTrue, shouldClear)
710
+ })
711
+ const mockShell = addMockShell(host, {
712
+ declareAPIs: () => [memoizedAPI],
713
+ attach(shell) {
714
+ shell.contributeAPI(memoizedAPI, () => createAPI(shell))
715
+ }
716
+ })
717
+
718
+ return mockShell
719
+ }
720
+
721
+ const createMockShell = (host: AppHost) => {
722
+ let observableState: ObservableState<ObservableValueSelector> = {} as any
723
+ const mockShell = addMockShell(host, {
724
+ declareAPIs: () => [memoizedAPI],
725
+ attach(shell) {
726
+ observableState = shell.contributeObservableState<ObservableValueState, ObservableValueSelector>(
727
+ () => ({
728
+ stateValue: (state = 1, action) => {
729
+ return action.type === 'increase' ? state + 1 : state
730
+ }
731
+ }),
732
+ state => {
733
+ return {
734
+ getStateValue: () => state.stateValue
735
+ }
736
+ }
737
+ )
738
+
739
+ shell.contributeAPI(memoizedAPI, () => ({
740
+ getNewObject: shell.memoizeForState(
741
+ () => ({
742
+ value: observableState.current(true).getStateValue()
743
+ }),
744
+ _.stubTrue
745
+ )
746
+ }))
747
+ }
748
+ })
749
+
750
+ return {
751
+ mockShell,
752
+ observableState
753
+ }
754
+ }
755
+
756
+ it('should memoize functions upon demand', () => {
757
+ const host = createAppHost([mockPackage], testHostOptions)
758
+ const getObj = () => host.getAPI(MockAPI).getNewObject()
759
+ expect(getObj()).not.toBe(getObj())
760
+
761
+ contributeMemoizedAPI(host)
762
+
763
+ const objForStateA = host.getAPI(memoizedAPI).getNewObject()
764
+ expect(objForStateA).toBe(host.getAPI(memoizedAPI).getNewObject())
765
+ })
766
+
767
+ it('should clear memoized functions on store dispatch', () => {
768
+ const host = createAppHost([mockPackage], testHostOptions)
769
+
770
+ contributeMemoizedAPI(host)
771
+
772
+ const objForStateA = host.getAPI(memoizedAPI).getNewObject()
773
+ expect(objForStateA).toBe(host.getAPI(memoizedAPI).getNewObject())
774
+
775
+ host.getStore().dispatch({ type: 'MOCK_ACTION' })
776
+ host.getStore().flush()
777
+
778
+ expect(objForStateA).not.toBe(host.getAPI(memoizedAPI).getNewObject())
779
+ })
780
+
781
+ it('should clear memoized functions on store dispatch when synchronous code is executing', () => {
782
+ const host = createAppHost([mockPackage], testHostOptions)
783
+
784
+ contributeMemoizedAPI(host)
785
+
786
+ const res1 = host.getAPI(memoizedAPI).getNewObject()
787
+ expect(res1).toBe(host.getAPI(memoizedAPI).getNewObject())
788
+
789
+ let res3
790
+ host.getStore().subscribe(() => {
791
+ // cache was flushing, so call memoized API in order to create new cache
792
+ const res2 = host.getAPI(memoizedAPI).getNewObject()
793
+ expect(res1).not.toBe(res2)
794
+ expect(res2).toBe(host.getAPI(memoizedAPI).getNewObject())
795
+ // dispatch new action for sync cache flushing
796
+ host.getStore().dispatch({ type: 'MOCK_ACTION' })
797
+
798
+ res3 = host.getAPI(memoizedAPI).getNewObject()
799
+ expect(res2).not.toBe(res3)
800
+ })
801
+
802
+ host.getStore().flush()
803
+ expect(res3).toBe(host.getAPI(memoizedAPI).getNewObject())
804
+ })
805
+
806
+ it('should clear memoized functions on observable dispatch', () => {
807
+ const host = createAppHost([mockPackage], testHostOptions)
808
+ const { mockShell } = createMockShell(host)
809
+
810
+ const objForStateA = host.getAPI(memoizedAPI).getNewObject()
811
+ expect(objForStateA).toBe(host.getAPI(memoizedAPI).getNewObject())
812
+
813
+ const { dispatch, flush } = mockShell.getStore<ObservableValueState>()
814
+ dispatch({ type: 'increase' })
815
+ flush()
816
+
817
+ expect(objForStateA).not.toBe(host.getAPI(memoizedAPI).getNewObject())
818
+ })
819
+
820
+ it('should clear memoized functions on observable dispatch when synchronous code is executing', () => {
821
+ const host = createAppHost([mockPackage], testHostOptions)
822
+ const { mockShell, observableState } = createMockShell(host)
823
+
824
+ const { dispatch, flush } = mockShell.getStore<ObservableValueState>()
825
+
826
+ const res1 = host.getAPI(memoizedAPI).getNewObject()
827
+ expect(res1).toBe(host.getAPI(memoizedAPI).getNewObject())
828
+
829
+ let res3
830
+ observableState.subscribe(mockShell, () => {
831
+ // cache was flushing, so call memoized API in order to create new cache
832
+ const res2 = host.getAPI(memoizedAPI).getNewObject()
833
+ expect(res1).not.toBe(res2)
834
+ expect(res2).toBe(host.getAPI(memoizedAPI).getNewObject())
835
+ // dispatch new action for sync cache flushing
836
+ dispatch({ type: 'increase' })
837
+
838
+ res3 = host.getAPI(memoizedAPI).getNewObject()
839
+ expect(res2).not.toBe(res3)
840
+ })
841
+
842
+ dispatch({ type: 'increase' })
843
+ flush()
844
+ expect(res3).toBe(host.getAPI(memoizedAPI).getNewObject())
845
+ })
846
+
847
+ it('should not clear memoized functions if not needed', () => {
848
+ const host = createAppHost([], testHostOptions)
849
+
850
+ contributeMemoizedAPI(host, _.stubFalse)
851
+
852
+ const objForStateA = host.getAPI(memoizedAPI).getNewObject()
853
+
854
+ host.getStore().dispatch({ type: 'MOCK_ACTION' })
855
+ host.getStore().flush()
856
+
857
+ expect(objForStateA).toBe(host.getAPI(memoizedAPI).getNewObject())
858
+ })
859
+ })
860
+
861
+ describe('memory cleanup:', () => {
862
+ let originalFinalizationRegistry: FinalizationRegistry<any>
863
+ let cleanupMemory = (ref: any) => {}
864
+
865
+ beforeEach(() => {
866
+ originalFinalizationRegistry = FinalizationRegistry as any
867
+ globalThis.FinalizationRegistry = function (cleanupCb: (heldValue: any) => void) {
868
+ const heldValueSet = new Map()
869
+
870
+ cleanupMemory = ref => {
871
+ const heldValue = heldValueSet.get(ref)
872
+
873
+ cleanupCb(heldValue)
874
+ }
875
+
876
+ return {
877
+ register(target: object, heldValue: any, unregisterToken?: object) {
878
+ heldValueSet.set(target, heldValue)
879
+ },
880
+ unregister() {}
881
+ } as unknown as FinalizationRegistry<any>
882
+ } as any
883
+ })
884
+
885
+ afterEach(() => {
886
+ globalThis.FinalizationRegistry = originalFinalizationRegistry as any
887
+ })
888
+
889
+ it('should remove memoized function from memory when there is no ref to memoized function', () => {
890
+ const host = createAppHost([], testHostOptions)
891
+
892
+ interface NewAPI {
893
+ getNewObject(): object
894
+ getNewObject2(): object
895
+ }
896
+ const newAPI: SlotKey<NewAPI> = { name: 'newAPI' }
897
+ let memFn1: _.MemoizedFunction | null = null
898
+ let memFn2: _.MemoizedFunction | null = null
899
+ const createAPI = (shell: Shell): NewAPI => {
900
+ memFn1 = shell.memoizeForState(() => ({}), _.stubTrue, _.stubTrue) as _.MemoizedFunction
901
+ memFn2 = shell.memoizeForState(() => ({}), _.stubTrue, _.stubTrue) as _.MemoizedFunction
902
+
903
+ memFn1.cache.clear = jest.fn()
904
+ memFn2.cache.clear = jest.fn()
905
+
906
+ return {
907
+ getNewObject: memFn1 as any,
908
+ getNewObject2: memFn2 as any
909
+ }
910
+ }
911
+ addMockShell(host, {
912
+ declareAPIs: () => [newAPI],
913
+ attach(shell) {
914
+ shell.contributeAPI(newAPI, () => createAPI(shell))
915
+ }
916
+ })
917
+
918
+ if (memFn1 && memFn2) {
919
+ const cacheFnMock1 = (memFn1 as any).cache.clear
920
+ const cacheFnMock2 = (memFn2 as any).cache.clear
921
+
922
+ host.getStore().dispatch({ type: 'MOCK_ACTION' })
923
+ host.getStore().flush()
924
+
925
+ expect(cacheFnMock1).toHaveBeenCalledTimes(1)
926
+ expect(cacheFnMock2).toHaveBeenCalledTimes(1)
927
+
928
+ cleanupMemory(memFn1)
929
+
930
+ host.getStore().dispatch({ type: 'MOCK_ACTION' })
931
+ host.getStore().flush()
932
+
933
+ expect(cacheFnMock1).toHaveBeenCalledTimes(1)
934
+ expect(cacheFnMock2).toHaveBeenCalledTimes(2)
935
+ } else {
936
+ throw Error('memFn1 or memFn2 is not defined')
937
+ }
938
+ })
939
+ })
940
+
941
+ describe('lazyEvaluator', () => {
942
+ it('should return a getter that is evaluated only once', () => {
943
+ const { helperShell } = createHostWithDependantPackages(MockAPI)
944
+ const func = jest.fn(() => 42)
945
+ const lazyEval = helperShell.lazyEvaluator(func)
946
+
947
+ expect(lazyEval.get()).toBe(42)
948
+ expect(lazyEval.get()).toBe(42)
949
+ expect(func).toHaveBeenCalledTimes(1)
950
+ })
951
+ })
952
+ })
953
+
954
+ describe('Entry Point Shell Scoping', () => {
955
+ it('should be able to call an API declared in dependencies', () => {
956
+ const entryPointThatCallsAPI: EntryPoint = {
957
+ name: 'ENTRY_POINT_WITH_API_CALL',
958
+ getDependencyAPIs() {
959
+ return [MockAPI]
960
+ },
961
+ extend(shell: Shell) {
962
+ shell.getAPI(MockAPI).stubTrue()
963
+ }
964
+ }
965
+ const appHost = createAppHost([mockPackage], testHostOptions)
966
+ expect(() => appHost.addShells([entryPointThatCallsAPI])).not.toThrow()
967
+ })
968
+
969
+ it('should not be able to call an API not declared in dependencies', () => {
970
+ const entryPointThatCallsAPI: EntryPoint = {
971
+ name: 'ENTRY_POINT_WITH_API_CALL',
972
+ extend(shell: Shell) {
973
+ shell.getAPI(MockAPI).stubTrue()
974
+ }
975
+ }
976
+ const appHost = createAppHost([mockPackage], testHostOptions)
977
+ expect(() => appHost.addShells([entryPointThatCallsAPI])).toThrow()
978
+ })
979
+
980
+ it('should get scoped state', done => {
981
+ const state = {}
982
+ const MOCK_STATE_KEY = 'mockStateKey'
983
+ const entryPointWithState: EntryPoint = {
984
+ name: 'ENTRY_POINT_WITH_STATE',
985
+ attach(shell: Shell) {
986
+ shell.contributeState(() => ({
987
+ [MOCK_STATE_KEY]: () => state
988
+ }))
989
+ },
990
+ extend(shell: Shell) {
991
+ expect(_.get(shell.getStore().getState(), MOCK_STATE_KEY)).toBe(state)
992
+ done()
993
+ }
994
+ }
995
+ createAppHost([entryPointWithState], testHostOptions)
996
+ })
997
+
998
+ it('should be able to uninstall own installed packages', async () => {
999
+ // note: this test assumes that addShells and removeShells complete synchronously
1000
+ const packageThatInstallsAPackage: EntryPoint = {
1001
+ name: 'ENTRY_POINT_THAT_INSTALLS_A_PACKAGE',
1002
+ extend(shell: Shell) {
1003
+ shell.addShells([mockPackage])
1004
+ shell.removeShells([mockPackage.name])
1005
+ }
1006
+ }
1007
+ createAppHost([packageThatInstallsAPackage], testHostOptions)
1008
+ expect(() => createAppHost([packageThatInstallsAPackage], testHostOptions)).not.toThrow()
1009
+ })
1010
+
1011
+ it('should not be able to uninstall not own installed packages', () => {
1012
+ const packageThatTriesToUninstallAPackage: EntryPoint = {
1013
+ name: 'ENTRY_POINT_THAT_TRYIES_TO_UNINSTALL_A_PACKAGE',
1014
+ extend(shell: Shell) {
1015
+ shell.removeShells([mockPackage.name])
1016
+ }
1017
+ }
1018
+ expect(() => createAppHost([mockPackage, packageThatTriesToUninstallAPackage], testHostOptions)).toThrow()
1019
+ })
1020
+
1021
+ it('should not trigger other reducers by scoped dispatch', () => {
1022
+ interface ScopedState {
1023
+ value: number
1024
+ }
1025
+ interface State1 {
1026
+ state1: ScopedState
1027
+ }
1028
+ const INITIAL_STATE: ScopedState = { value: 0 }
1029
+ const SET_VALUE = 'SET_VALUE'
1030
+ const setValue = (value: number) => ({ type: SET_VALUE, value })
1031
+
1032
+ const host = createAppHost([], {
1033
+ shouldScopeReducers: true,
1034
+ monitoring: {}
1035
+ })
1036
+ const shell1 = addMockShell(host, {
1037
+ attach(shell) {
1038
+ shell.contributeState<State1>(() => ({
1039
+ state1: (state: ScopedState = INITIAL_STATE, action): ScopedState => {
1040
+ switch (action.type) {
1041
+ case SET_VALUE:
1042
+ return { value: action.value }
1043
+ }
1044
+ return state
1045
+ }
1046
+ }))
1047
+ }
1048
+ })
1049
+
1050
+ // Initial state
1051
+ const getState = () => shell1.getStore<State1>().getState().state1.value
1052
+ expect(getState()).toBe(0)
1053
+
1054
+ // Scoped dispatch
1055
+ shell1.getStore<State1>().dispatch(setValue(73))
1056
+ expect(getState()).toBe(73)
1057
+
1058
+ // Unscoped dispatch
1059
+ host.getStore().dispatch(setValue(1337))
1060
+ expect(getState()).toBe(1337)
1061
+
1062
+ addMockShell(host, {
1063
+ extend(shell) {
1064
+ // Scoped dispatch in a different scope
1065
+ shell.getStore().dispatch(setValue(42))
1066
+ }
1067
+ })
1068
+ expect(getState()).toBe(1337)
1069
+ })
1070
+ })
1071
+
1072
+ describe('Entry Point HMR support', () => {
1073
+ const LowLevelSlotKey: SlotKey<string> = { name: 'LOW-LEVEL-SLOT' }
1074
+ const HighLevelSlotKey: SlotKey<string> = { name: 'HIGH-LEVEL-SLOT' }
1075
+ const ConsumerSlotKey: SlotKey<string> = { name: 'CONSUMER-SLOT' }
1076
+ const LowLevelAPI: SlotKey<{ lowLevelFunc(s: string): void }> = {
1077
+ name: 'LOW-LEVEL-API'
1078
+ }
1079
+ const HighLevelAPI: SlotKey<{ highLevelFunc(s: string): void }> = {
1080
+ name: 'HIGH-LEVEL-API'
1081
+ }
1082
+ const hmrTestPackage: EntryPoint[] = [
1083
+ {
1084
+ name: 'LOW_LEVEL_API_ENTRY_POINT',
1085
+ declareAPIs() {
1086
+ return [LowLevelAPI]
1087
+ },
1088
+ attach(shell: Shell) {
1089
+ shell.declareSlot(LowLevelSlotKey)
1090
+ shell.contributeAPI(LowLevelAPI, () => ({
1091
+ lowLevelFunc: jest.fn()
1092
+ }))
1093
+ }
1094
+ },
1095
+ {
1096
+ name: 'HIGH_LEVEL_API_ENTRY_POINT',
1097
+ getDependencyAPIs() {
1098
+ return [LowLevelAPI]
1099
+ },
1100
+ declareAPIs() {
1101
+ return [HighLevelAPI]
1102
+ },
1103
+ attach(shell: Shell) {
1104
+ shell.declareSlot(HighLevelSlotKey)
1105
+ shell.contributeAPI(HighLevelAPI, () => ({
1106
+ highLevelFunc: jest.fn
1107
+ }))
1108
+ },
1109
+ extend(shell: Shell) {
1110
+ shell.getAPI(LowLevelAPI).lowLevelFunc('HIGH')
1111
+ }
1112
+ },
1113
+ {
1114
+ name: 'CONSUMER_ENTRY_POINT',
1115
+ getDependencyAPIs() {
1116
+ return [HighLevelAPI]
1117
+ },
1118
+ extend(shell: Shell) {
1119
+ shell.declareSlot(ConsumerSlotKey)
1120
+ shell.getAPI(HighLevelAPI).highLevelFunc('CONSUMER')
1121
+ }
1122
+ }
1123
+ ]
1124
+
1125
+ it('should be able to reload entry points', async () => {
1126
+ const appHost = createAppHost(hmrTestPackage, testHostOptions)
1127
+
1128
+ await appHost.removeShells(['LOW_LEVEL_API_ENTRY_POINT'])
1129
+ await appHost.addShells([hmrTestPackage[0]])
1130
+
1131
+ expect(appHost.getAPI(HighLevelAPI)).toBeDefined()
1132
+ })
1133
+ })
1134
+
1135
+ describe('API layer', () => {
1136
+ it('should allow dependency from high to lower level API', async () => {
1137
+ const MockAPI1: SlotKey<{}> = { name: 'Mock-API', layer: 'INFRA' }
1138
+ const layers = [
1139
+ {
1140
+ level: 0,
1141
+ name: 'INFRA'
1142
+ },
1143
+ {
1144
+ level: 1,
1145
+ name: 'PRODUCT'
1146
+ }
1147
+ ]
1148
+
1149
+ const host = createAppHost([], { ...emptyLoggerOptions, layers })
1150
+ const EntryPoint1: EntryPoint = {
1151
+ name: 'MOCK_ENTRY_POINT_1',
1152
+ layer: 'PRODUCT',
1153
+ getDependencyAPIs: () => [MockAPI1]
1154
+ }
1155
+ const EntryPoint2: EntryPoint = {
1156
+ name: 'MOCK_ENTRY_POINT_2',
1157
+ layer: 'INFRA',
1158
+ declareAPIs: () => [MockAPI1],
1159
+ attach(shell) {
1160
+ shell.contributeAPI(MockAPI1, () => ({}))
1161
+ }
1162
+ }
1163
+ await host.addShells([EntryPoint2])
1164
+
1165
+ expect(() => host.addShells([EntryPoint1])).not.toThrow()
1166
+ })
1167
+
1168
+ it('should not allow dependency from low to higher level API', async () => {
1169
+ const MockAPI1: SlotKey<{}> = { name: 'Mock-API', layer: 'PRODUCT' }
1170
+ const layers = [
1171
+ {
1172
+ level: 0,
1173
+ name: 'INFRA'
1174
+ },
1175
+ {
1176
+ level: 1,
1177
+ name: 'PRODUCT'
1178
+ }
1179
+ ]
1180
+
1181
+ const host = createAppHost([], { ...emptyLoggerOptions, layers })
1182
+ const EntryPoint1: EntryPoint = {
1183
+ name: 'MOCK_ENTRY_POINT_1',
1184
+ layer: 'INFRA',
1185
+ getDependencyAPIs: () => [MockAPI1]
1186
+ }
1187
+ const EntryPoint2: EntryPoint = {
1188
+ name: 'MOCK_ENTRY_POINT_2',
1189
+ layer: 'PRODUCT',
1190
+ declareAPIs: () => [MockAPI1],
1191
+ attach(shell) {
1192
+ shell.contributeAPI(MockAPI1, () => ({}))
1193
+ }
1194
+ }
1195
+ await host.addShells([EntryPoint2])
1196
+
1197
+ expect(() => host.addShells([EntryPoint1])).toThrowError(
1198
+ `Entry point ${EntryPoint1.name} of layer ${layers[0].name} cannot depend on API ${MockAPI1.name} of layer ${EntryPoint2.layer}`
1199
+ )
1200
+ })
1201
+
1202
+ it('should not allow adding shell of unknown layer', () => {
1203
+ const MockAPI1: SlotKey<{}> = {
1204
+ name: 'Mock-API',
1205
+ layer: 'NON_EXIXTING_layer'
1206
+ }
1207
+ const layers = [
1208
+ {
1209
+ level: 0,
1210
+ name: 'INFRA'
1211
+ },
1212
+ {
1213
+ level: 1,
1214
+ name: 'PRODUCT'
1215
+ }
1216
+ ]
1217
+
1218
+ const host = createAppHost([], { ...emptyLoggerOptions, layers })
1219
+ const EntryPoint1: EntryPoint = {
1220
+ name: 'MOCK_ENTRY_POINT_1',
1221
+ layer: 'NON_EXIXTING_layer',
1222
+ getDependencyAPIs: () => [MockAPI1]
1223
+ }
1224
+ expect(() => host.addShells([EntryPoint1])).toThrowError(`Cannot find layer ${EntryPoint1.layer}`)
1225
+ })
1226
+
1227
+ it('should not allow contribution of API with non-matching entry point layer', () => {
1228
+ const MockAPI1: SlotKey<{}> = { name: 'Mock-API', layer: 'PRODUCT' }
1229
+ const layers = [
1230
+ {
1231
+ level: 0,
1232
+ name: 'INFRA'
1233
+ },
1234
+ {
1235
+ level: 1,
1236
+ name: 'PRODUCT'
1237
+ }
1238
+ ]
1239
+
1240
+ const host = createAppHost([], { ...emptyLoggerOptions, layers })
1241
+ const EntryPoint1: EntryPoint = {
1242
+ name: 'MOCK_ENTRY_POINT_1',
1243
+ layer: 'INFRA',
1244
+ declareAPIs: () => [MockAPI1],
1245
+ attach(shell) {
1246
+ shell.contributeAPI(MockAPI1, () => ({}))
1247
+ }
1248
+ }
1249
+ expect(() => host.addShells([EntryPoint1])).toThrowError(
1250
+ `Cannot contribute API ${MockAPI1.name} of layer ${MockAPI1.layer} from entry point ${EntryPoint1.name} of layer ${EntryPoint1.layer}`
1251
+ )
1252
+ })
1253
+
1254
+ it('should support multi dimensional layers definition', () => {
1255
+ const MockAPI1: SlotKey<{}> = {
1256
+ name: 'Mock-API',
1257
+ layer: ['COMMON', 'INFRA']
1258
+ }
1259
+ const layersDimension1 = [
1260
+ {
1261
+ level: 0,
1262
+ name: 'INFRA'
1263
+ },
1264
+ {
1265
+ level: 1,
1266
+ name: 'PRODUCT'
1267
+ }
1268
+ ]
1269
+ const layersDimension2 = [
1270
+ {
1271
+ level: 0,
1272
+ name: 'COMMON'
1273
+ },
1274
+ {
1275
+ level: 1,
1276
+ name: 'SPECIFIC'
1277
+ }
1278
+ ]
1279
+
1280
+ const host = createAppHost([], {
1281
+ ...emptyLoggerOptions,
1282
+ layers: [layersDimension1, layersDimension2]
1283
+ })
1284
+
1285
+ const EntryPoint1: EntryPoint = {
1286
+ name: 'MOCK_ENTRY_POINT_1',
1287
+ layer: ['INFRA', 'COMMON'],
1288
+ declareAPIs: () => [MockAPI1],
1289
+ attach(shell) {
1290
+ shell.contributeAPI(MockAPI1, () => ({}))
1291
+ }
1292
+ }
1293
+
1294
+ const EntryPoint2: EntryPoint = {
1295
+ name: 'MOCK_ENTRY_POINT_2',
1296
+ layer: ['PRODUCT', 'SPECIFIC'],
1297
+ getDependencyAPIs: () => [MockAPI1]
1298
+ }
1299
+
1300
+ expect(() => host.addShells([EntryPoint1, EntryPoint2])).not.toThrow()
1301
+ })
1302
+
1303
+ it('should throw for multi dimensional layers violation', () => {
1304
+ const MockAPI1: SlotKey<{}> = {
1305
+ name: 'Mock-API',
1306
+ layer: ['INFRA', 'SPECIFIC']
1307
+ }
1308
+ const layersDimension1 = [
1309
+ {
1310
+ level: 0,
1311
+ name: 'INFRA'
1312
+ },
1313
+ {
1314
+ level: 1,
1315
+ name: 'PRODUCT'
1316
+ }
1317
+ ]
1318
+ const layersDimension2 = [
1319
+ {
1320
+ level: 0,
1321
+ name: 'COMMON'
1322
+ },
1323
+ {
1324
+ level: 1,
1325
+ name: 'SPECIFIC'
1326
+ }
1327
+ ]
1328
+
1329
+ const host = createAppHost([], {
1330
+ ...emptyLoggerOptions,
1331
+ layers: [layersDimension1, layersDimension2]
1332
+ })
1333
+
1334
+ const EntryPoint1: EntryPoint = {
1335
+ name: 'MOCK_ENTRY_POINT_1',
1336
+ layer: ['INFRA', 'SPECIFIC'],
1337
+ declareAPIs: () => [MockAPI1],
1338
+ attach(shell) {
1339
+ shell.contributeAPI(MockAPI1, () => ({}))
1340
+ }
1341
+ }
1342
+
1343
+ const EntryPoint2: EntryPoint = {
1344
+ name: 'MOCK_ENTRY_POINT_2',
1345
+ layer: ['COMMON', 'PRODUCT'],
1346
+ getDependencyAPIs: () => [MockAPI1]
1347
+ }
1348
+
1349
+ expect(() => host.addShells([EntryPoint1, EntryPoint2])).toThrowError(
1350
+ `Entry point ${EntryPoint2.name} of layer COMMON cannot depend on API ${MockAPI1.name} of layer SPECIFIC`
1351
+ )
1352
+ })
1353
+
1354
+ it('should enforce cross-multi-dimensional-layers name uniqueness', () => {
1355
+ const layersDimension1 = [
1356
+ {
1357
+ level: 0,
1358
+ name: 'INFRA'
1359
+ },
1360
+ {
1361
+ level: 1,
1362
+ name: 'NOT_UNIQUE'
1363
+ }
1364
+ ]
1365
+ const layersDimension2 = [
1366
+ {
1367
+ level: 0,
1368
+ name: 'COMMON'
1369
+ },
1370
+ {
1371
+ level: 1,
1372
+ name: 'NOT_UNIQUE'
1373
+ }
1374
+ ]
1375
+
1376
+ expect(() =>
1377
+ createAppHost([], {
1378
+ ...emptyLoggerOptions,
1379
+ layers: [layersDimension1, layersDimension2]
1380
+ })
1381
+ ).toThrowError(`Cannot initialize host with non unique layers: NOT_UNIQUE`)
1382
+ })
1383
+
1384
+ it('should allow single layered API for multi dimensional layers host', () => {
1385
+ const MockAPI1: SlotKey<{}> = { name: 'Mock-API', layer: 'INFRA' }
1386
+ const layersDimension1 = [
1387
+ {
1388
+ level: 0,
1389
+ name: 'INFRA'
1390
+ },
1391
+ {
1392
+ level: 1,
1393
+ name: 'PRODUCT'
1394
+ }
1395
+ ]
1396
+ const layersDimension2 = [
1397
+ {
1398
+ level: 0,
1399
+ name: 'COMMON'
1400
+ },
1401
+ {
1402
+ level: 1,
1403
+ name: 'SPECIFIC'
1404
+ }
1405
+ ]
1406
+
1407
+ const EntryPoint1: EntryPoint = {
1408
+ name: 'MOCK_ENTRY_POINT_1',
1409
+ layer: 'PRODUCT',
1410
+ getDependencyAPIs: () => [MockAPI1]
1411
+ }
1412
+ const EntryPoint2: EntryPoint = {
1413
+ name: 'MOCK_ENTRY_POINT_2',
1414
+ layer: 'INFRA',
1415
+ declareAPIs: () => [MockAPI1],
1416
+ attach(shell) {
1417
+ shell.contributeAPI(MockAPI1, () => ({}))
1418
+ }
1419
+ }
1420
+
1421
+ expect(() =>
1422
+ createAppHost([EntryPoint1, EntryPoint2], {
1423
+ ...emptyLoggerOptions,
1424
+ layers: [layersDimension1, layersDimension2]
1425
+ })
1426
+ ).not.toThrow()
1427
+ })
1428
+ })
1429
+
1430
+ describe('API version', () => {
1431
+ it('should provide API of matching version', async () => {
1432
+ const MockAPIv0: SlotKey<{ f1(): void }> = { name: 'Mock-API' }
1433
+ const MockAPIv2: SlotKey<{ f2(): void }> = {
1434
+ name: 'Mock-API',
1435
+ version: 2
1436
+ }
1437
+ const host = createAppHost([])
1438
+ const entryPoint: EntryPoint = {
1439
+ name: 'MOCK_ENTRY_POINT',
1440
+ declareAPIs: () => [MockAPIv0, MockAPIv2],
1441
+ attach(shell) {
1442
+ shell.contributeAPI(MockAPIv0, () => ({ f1() {} }))
1443
+ shell.contributeAPI(MockAPIv2, () => ({ f2() {} }))
1444
+ }
1445
+ }
1446
+ await host.addShells([entryPoint])
1447
+
1448
+ expect(host.getAPI(MockAPIv0).f1).toBeDefined()
1449
+ expect((host.getAPI(MockAPIv0) as any).f2).not.toBeDefined()
1450
+
1451
+ expect(host.getAPI(MockAPIv2).f2).toBeDefined()
1452
+ expect((host.getAPI(MockAPIv2) as any).f1).not.toBeDefined()
1453
+
1454
+ const SecondMockAPIv2: SlotKey<{ f2(): void }> = {
1455
+ name: 'Mock-API',
1456
+ version: 2
1457
+ }
1458
+ expect(() => {
1459
+ addMockShell(host, {
1460
+ declareAPIs: () => [SecondMockAPIv2],
1461
+ attach(shell) {
1462
+ shell.contributeAPI(SecondMockAPIv2, () => ({ f2() {} }))
1463
+ }
1464
+ })
1465
+ }).toThrowError(
1466
+ new RegExp(`Extension slot with key '${SecondMockAPIv2.name}\\\(v${SecondMockAPIv2.version}\\\)' already exists`)
1467
+ )
1468
+ })
1469
+ })
1470
+
1471
+ describe('Host API', () => {
1472
+ it('should get all entry points', async () => {
1473
+ const host = createAppHost([mockPackage], testHostOptions) as AppHost & AppHostServicesProvider
1474
+ await host.addShells([mockPackageWithPublicAPI])
1475
+
1476
+ const allEntryPoints = host.getAPI(AppHostAPI).getAllEntryPoints()
1477
+
1478
+ expect(_.sortBy(allEntryPoints, 'name')).toEqual(
1479
+ _.sortBy([mockPackage, mockPackageWithPublicAPI, (host.getAppHostServicesShell() as PrivateShell).entryPoint], 'name')
1480
+ )
1481
+ })
1482
+
1483
+ it('should get host options', () => {
1484
+ const host = createAppHost([mockPackage], testHostOptions)
1485
+
1486
+ expect(host.getAPI(AppHostAPI).getAppHostOptions()).toEqual(testHostOptions)
1487
+ })
1488
+ })
1489
+
1490
+ describe('Cyclic Mode', () => {
1491
+ it('should load cyclic dependencies groups if all other dependencies are ready', () => {
1492
+ const API1: SlotKey<{}> = { name: 'API1' }
1493
+ const API2: SlotKey<{}> = { name: 'API2' }
1494
+ const API3: SlotKey<{}> = { name: 'API3' }
1495
+ const entryPoints: EntryPoint[] = [
1496
+ {
1497
+ name: 'Package1',
1498
+ getDependencyAPIs: () => [API2],
1499
+ declareAPIs: () => [API1],
1500
+ attach(shell) {
1501
+ shell.contributeAPI(API1, () => ({}))
1502
+ }
1503
+ },
1504
+ {
1505
+ name: 'Package2',
1506
+ getDependencyAPIs: () => [API3],
1507
+ declareAPIs: () => [API2],
1508
+ attach(shell) {
1509
+ shell.contributeAPI(API2, () => ({}))
1510
+ }
1511
+ },
1512
+ {
1513
+ name: 'Package3',
1514
+ getDependencyAPIs: () => [API1],
1515
+ declareAPIs: () => [API3],
1516
+ attach(shell) {
1517
+ shell.contributeAPI(API3, () => ({}))
1518
+ }
1519
+ }
1520
+ ]
1521
+ const host = createAppHost(entryPoints, {
1522
+ ...testHostOptions,
1523
+ experimentalCyclicMode: true
1524
+ })
1525
+
1526
+ expect(host.hasShell(entryPoints[0].name)).toBe(true)
1527
+ expect(host.hasShell(entryPoints[1].name)).toBe(true)
1528
+ expect(host.hasShell(entryPoints[2].name)).toBe(true)
1529
+ })
1530
+
1531
+ it('should not load cyclic dependencies groups if some other dependencies are not ready', () => {
1532
+ const API1: SlotKey<{}> = { name: 'API1' }
1533
+ const API2: SlotKey<{}> = { name: 'API2' }
1534
+ const API3: SlotKey<{}> = { name: 'API3' }
1535
+ const API4: SlotKey<{}> = { name: 'API4' }
1536
+ const entryPoints: EntryPoint[] = [
1537
+ {
1538
+ name: 'Package1',
1539
+ getDependencyAPIs: () => [API2],
1540
+ declareAPIs: () => [API1],
1541
+ attach(shell) {
1542
+ shell.contributeAPI(API1, () => ({}))
1543
+ }
1544
+ },
1545
+ {
1546
+ name: 'Package2',
1547
+ getDependencyAPIs: () => [API3],
1548
+ declareAPIs: () => [API2],
1549
+ attach(shell) {
1550
+ shell.contributeAPI(API2, () => ({}))
1551
+ }
1552
+ },
1553
+ {
1554
+ name: 'Package3',
1555
+ getDependencyAPIs: () => [API1, API4],
1556
+ declareAPIs: () => [API3],
1557
+ attach(shell) {
1558
+ shell.contributeAPI(API3, () => ({}))
1559
+ }
1560
+ }
1561
+ ]
1562
+ const host = createAppHost(entryPoints, {
1563
+ ...testHostOptions,
1564
+ experimentalCyclicMode: true
1565
+ })
1566
+
1567
+ expect(host.hasShell(entryPoints[0].name)).toBe(false)
1568
+ expect(host.hasShell(entryPoints[1].name)).toBe(false)
1569
+ expect(host.hasShell(entryPoints[2].name)).toBe(false)
1570
+ })
1571
+ })
1572
+
1573
+ describe('Host.executeWhenFree', () => {
1574
+ it('should be invoked immediately', () => {
1575
+ const host = createAppHost([], testHostOptions) as PrivateAppHost
1576
+ const spy = jest.fn()
1577
+ host.executeWhenFree('1', spy)
1578
+ expect(spy).toBeCalledTimes(1)
1579
+ })
1580
+
1581
+ it('when invoked during entryPoints installation, should run once installation resumes ', async () => {
1582
+ const host = createAppHost([], testHostOptions) as PrivateAppHost
1583
+ const spy = jest.fn()
1584
+
1585
+ const API1: SlotKey<{}> = { name: 'API1' }
1586
+ const API2: SlotKey<{}> = { name: 'API2' }
1587
+ const API3: SlotKey<{}> = { name: 'API3' }
1588
+
1589
+ const entryPoints: EntryPoint[] = [
1590
+ {
1591
+ name: 'Package1',
1592
+ declareAPIs: () => [API1],
1593
+ attach(shell) {
1594
+ host.executeWhenFree('1', spy)
1595
+ shell.contributeAPI(API1, () => ({}))
1596
+ },
1597
+ extend(shell: Shell) {
1598
+ expect(spy).toBeCalledTimes(0)
1599
+ }
1600
+ },
1601
+ {
1602
+ name: 'Package2',
1603
+ getDependencyAPIs: () => [API1],
1604
+ declareAPIs: () => [API2],
1605
+ attach(shell) {
1606
+ shell.contributeAPI(API2, () => ({}))
1607
+ },
1608
+ extend(shell: Shell) {
1609
+ expect(spy).toBeCalledTimes(0)
1610
+ }
1611
+ },
1612
+ {
1613
+ name: 'Package3',
1614
+ getDependencyAPIs: () => [API2],
1615
+ declareAPIs: () => [API3],
1616
+ attach(shell) {
1617
+ shell.contributeAPI(API3, () => ({}))
1618
+ },
1619
+ extend(shell: Shell) {
1620
+ expect(spy).toBeCalledTimes(0)
1621
+ }
1622
+ }
1623
+ ]
1624
+
1625
+ await host.addShells(entryPoints)
1626
+ expect(spy).toBeCalledTimes(1)
1627
+ })
1628
+ })
1629
+ })