@qlover/create-app 0.7.15 → 0.8.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 (340) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/configs/_common/.gitignore.template +6 -0
  3. package/dist/configs/_common/.prettierignore +17 -5
  4. package/dist/configs/_common/.vscode/settings.json +6 -1
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/templates/next-app/.env.template +1 -1
  8. package/dist/templates/next-app/README.en.md +0 -1
  9. package/dist/templates/next-app/README.md +0 -1
  10. package/dist/templates/next-app/config/Identifier/api.ts +5 -5
  11. package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
  12. package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
  13. package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
  14. package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
  15. package/dist/templates/next-app/config/Identifier/index.ts +2 -12
  16. package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
  17. package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
  18. package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
  19. package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
  20. package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
  21. package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
  22. package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
  23. package/dist/templates/next-app/config/adminNavs.ts +19 -0
  24. package/dist/templates/next-app/config/common.ts +22 -13
  25. package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
  26. package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
  27. package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
  28. package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
  29. package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
  30. package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
  31. package/dist/templates/next-app/docs/en/index.md +0 -1
  32. package/dist/templates/next-app/docs/en/project-structure.md +0 -1
  33. package/dist/templates/next-app/docs/zh/index.md +0 -1
  34. package/dist/templates/next-app/docs/zh/project-structure.md +0 -1
  35. package/dist/templates/next-app/make/generateLocales.ts +19 -12
  36. package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
  37. package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
  38. package/dist/templates/next-app/package.json +7 -3
  39. package/dist/templates/next-app/public/locales/en.json +172 -207
  40. package/dist/templates/next-app/public/locales/zh.json +172 -207
  41. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
  42. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
  43. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
  44. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
  45. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
  46. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
  47. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
  48. package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
  49. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
  50. package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
  51. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
  52. package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
  53. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
  54. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
  55. package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
  56. package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
  57. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
  58. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
  59. package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
  60. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
  61. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
  62. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
  63. package/dist/templates/next-app/src/i18n/request.ts +30 -1
  64. package/dist/templates/next-app/src/server/PageParams.ts +2 -10
  65. package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
  66. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
  67. package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
  68. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
  69. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
  70. package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
  71. package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
  72. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
  73. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
  74. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
  75. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
  76. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
  77. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
  78. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
  79. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
  80. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
  81. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
  82. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
  83. package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
  84. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
  85. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
  86. package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
  87. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
  88. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
  89. package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
  90. package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
  91. package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
  92. package/dist/templates/react-app/.prettierignore +17 -0
  93. package/dist/templates/react-app/README.en.md +71 -54
  94. package/dist/templates/react-app/README.md +35 -18
  95. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
  96. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
  97. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
  98. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
  99. package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
  100. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
  101. package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
  102. package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
  103. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
  104. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
  105. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
  106. package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
  107. package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
  108. package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
  109. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
  110. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
  111. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
  112. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
  113. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
  114. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
  115. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
  116. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
  117. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
  118. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
  119. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
  120. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
  121. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
  122. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
  123. package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
  124. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
  125. package/dist/templates/react-app/config/IOCIdentifier.ts +8 -8
  126. package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
  127. package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
  128. package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
  129. package/dist/templates/react-app/config/Identifier/index.ts +1 -9
  130. package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -0
  131. package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
  132. package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
  133. package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
  134. package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
  135. package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
  136. package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
  137. package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
  138. package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
  139. package/dist/templates/react-app/config/app.router.ts +66 -69
  140. package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
  141. package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
  142. package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
  143. package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
  144. package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
  145. package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
  146. package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
  147. package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
  148. package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
  149. package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
  150. package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
  151. package/dist/templates/react-app/config/theme.ts +14 -4
  152. package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
  153. package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
  154. package/dist/templates/react-app/docs/en/env.md +1132 -278
  155. package/dist/templates/react-app/docs/en/i18n.md +858 -147
  156. package/dist/templates/react-app/docs/en/index.md +733 -104
  157. package/dist/templates/react-app/docs/en/ioc.md +1228 -287
  158. package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
  159. package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
  160. package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
  161. package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
  162. package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
  163. package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
  164. package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
  165. package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
  166. package/dist/templates/react-app/docs/en/store.md +1194 -184
  167. package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
  168. package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
  169. package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
  170. package/dist/templates/react-app/docs/zh/env.md +1132 -275
  171. package/dist/templates/react-app/docs/zh/i18n.md +858 -147
  172. package/dist/templates/react-app/docs/zh/index.md +717 -104
  173. package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
  174. package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
  175. package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
  176. package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
  177. package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
  178. package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
  179. package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
  180. package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
  181. package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
  182. package/dist/templates/react-app/docs/zh/store.md +1192 -184
  183. package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
  184. package/dist/templates/react-app/e2e/App.spec.ts +319 -0
  185. package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
  186. package/dist/templates/react-app/e2e/main.spec.ts +20 -0
  187. package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
  188. package/dist/templates/react-app/eslint.config.mjs +247 -0
  189. package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
  190. package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
  191. package/dist/templates/react-app/package.json +31 -3
  192. package/dist/templates/react-app/playwright.config.ts +79 -0
  193. package/dist/templates/react-app/public/locales/en/common.json +190 -179
  194. package/dist/templates/react-app/public/locales/zh/common.json +190 -179
  195. package/dist/templates/react-app/src/App.tsx +15 -42
  196. package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
  197. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
  198. package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
  199. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
  200. package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
  201. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
  202. package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
  203. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
  204. package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
  205. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
  206. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
  207. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
  208. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
  209. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
  210. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
  211. package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
  212. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
  213. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
  214. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
  215. package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
  216. package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
  217. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
  218. package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
  219. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
  220. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
  221. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
  222. package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
  223. package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
  224. package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
  225. package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
  226. package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
  227. package/dist/templates/react-app/src/base/types/Page.ts +14 -2
  228. package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
  229. package/dist/templates/react-app/src/core/IOC.ts +5 -46
  230. package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
  231. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
  232. package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
  233. package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
  234. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
  235. package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
  236. package/dist/templates/react-app/src/core/globals.ts +9 -9
  237. package/dist/templates/react-app/src/main.tsx +4 -4
  238. package/dist/templates/react-app/src/pages/404.tsx +6 -3
  239. package/dist/templates/react-app/src/pages/500.tsx +5 -2
  240. package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
  241. package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
  242. package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
  243. package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
  244. package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
  245. package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
  246. package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
  247. package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
  248. package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
  249. package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
  250. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
  251. package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
  252. package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
  253. package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
  254. package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
  255. package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
  256. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
  257. package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
  258. package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
  259. package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
  260. package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
  261. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
  262. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
  263. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
  264. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
  265. package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
  266. package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
  267. package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
  268. package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
  269. package/dist/templates/react-app/src/styles/css/page.css +4 -3
  270. package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
  271. package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
  272. package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
  273. package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
  274. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
  275. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
  276. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
  277. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
  278. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
  279. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
  280. package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
  281. package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
  282. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
  283. package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
  284. package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
  285. package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
  286. package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
  287. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
  288. package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
  289. package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
  290. package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
  291. package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
  292. package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
  293. package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
  294. package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
  295. package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
  296. package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
  297. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
  298. package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
  299. package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
  300. package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
  301. package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
  302. package/dist/templates/react-app/tsconfig.e2e.json +21 -0
  303. package/dist/templates/react-app/tsconfig.json +8 -1
  304. package/dist/templates/react-app/tsconfig.node.json +1 -1
  305. package/dist/templates/react-app/tsconfig.test.json +3 -1
  306. package/dist/templates/react-app/vite.config.ts +50 -34
  307. package/package.json +2 -1
  308. package/dist/configs/react-app/eslint.config.js +0 -94
  309. package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
  310. package/dist/templates/next-app/config/Identifier/common.ts +0 -69
  311. package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
  312. package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
  313. package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
  314. package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
  315. package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
  316. package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
  317. package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
  318. package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
  319. package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
  320. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
  321. package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
  322. package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
  323. package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
  324. package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
  325. package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
  326. package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
  327. package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
  328. package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
  329. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
  330. package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
  331. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
  332. package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
  333. package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
  334. package/dist/templates/react-app/config/i18n.ts +0 -15
  335. package/dist/templates/react-app/docs/en/project-structure.md +0 -434
  336. package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
  337. package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
  338. package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
  339. package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
  340. package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
@@ -1,424 +1,1365 @@
1
- # IOC Container
1
+ # IOC Container (Dependency Injection)
2
2
 
3
- ## What is IOC?
3
+ ## 📋 Table of Contents
4
4
 
5
- IOC (Inversion of Control) is a design pattern that hands over the creation of objects and management of their dependencies to a container, rather than creating objects directly in the code.
5
+ - [Core Philosophy](#-core-philosophy) - UI separation, logic independence
6
+ - [What is IOC](#-what-is-ioc) - Inversion of Control
7
+ - [Why Need IOC](#-why-need-ioc) - Core problems it solves
8
+ - [Two Key Questions](#-two-key-questions) - Why need interfaces? Why separate even simple components?
9
+ - [Implementation in the Project](#-implementation-in-the-project) - Bootstrap integration
10
+ - [How to Use](#-how-to-use) - Practical guide
11
+ - [Testing](#-testing) - Independent testing and combination testing
12
+ - [Best Practices](#-best-practices) - 8 core practices
13
+ - [FAQ](#-faq) - Common questions
6
14
 
7
- **In simple terms**: Like a factory producing products, you don't need to know how products are manufactured; you just tell the factory what product you need, and the factory will provide it.
15
+ ---
8
16
 
9
- ## IOC Implementation in Project
17
+ ## 🎯 Core Philosophy
10
18
 
11
- ### Core Technology Stack
19
+ > **🚨 Important Principle: UI is UI, logic is logic, they must be separated!**
12
20
 
13
- - **InversifyJS**: As the IOC container implementation (you can also manually implement your own container)
14
- - **TypeScript**: Provides type safety
15
- - **Decorator Pattern**: Uses `@injectable()` and `@inject()` decorators
21
+ > **⭐ Core Advantage: UI and logic can be tested independently, and also in combination!**
16
22
 
17
- ### Core File Structure
23
+ ### Core Concept
18
24
 
19
25
  ```
20
- config/
21
- ├── IOCIdentifier.ts # IOC identifier definitions
22
- src/
23
- ├── core/
24
- ├── IOC.ts # IOC main entry
25
- ├── registers/ # Registers directory
26
- ├── RegisterGlobals.ts # Global service registration
27
- ├── RegisterCommon.ts # Common service registration
28
- └── RegisterControllers.ts # Controller registration
29
- └── globals.ts # Global instances
30
- ├── base/
31
- └── cases/
32
- └── InversifyContainer.ts # Inversify container implementation
26
+ ┌─────────────────────────────────────────┐
27
+ │ Traditional Approach: UI and Logic Mixed│
28
+ │ │
29
+ │ Component │
30
+ ├── UI rendering │
31
+ ├── Business logic │
32
+ ├── API calls │
33
+ ├── State management │
34
+ └── Data processing │
35
+
36
+ │ ❌ Problems: │
37
+ - Hard to test (need to render) │
38
+ - Logic can't be reused │
39
+ │ - Unclear responsibilities │
40
+ └─────────────────────────────────────────┘
41
+
42
+ ┌─────────────────────────────────────────┐
43
+ │ IOC Approach: UI and Logic Completely │
44
+ │ Separated │
45
+ │ │
46
+ │ Component (UI Layer) │
47
+ │ └── Only responsible for rendering │
48
+ │ ↓ Get through IOC │
49
+ │ Service (Logic Layer) │
50
+ │ ├── Business logic │
51
+ │ ├── API calls │
52
+ │ ├── State management │
53
+ │ └── Data processing │
54
+ │ │
55
+ │ ✅ Advantages: │
56
+ │ - UI and logic can be tested separately │
57
+ │ - Logic can be reused │
58
+ │ - Clear responsibilities │
59
+ └─────────────────────────────────────────┘
33
60
  ```
34
61
 
35
- ## Basic Concepts
62
+ ---
36
63
 
37
- ### 1. IOC Identifiers
64
+ ## 🔄 What is IOC
38
65
 
39
- ```tsx
40
- // config/IOCIdentifier.ts
41
- export const IOCIdentifier = Object.freeze({
42
- JSON: 'JSON',
43
- LocalStorage: 'LocalStorage',
44
- Logger: 'Logger',
45
- AppConfig: 'AppConfig'
46
- // ... more identifiers
47
- });
48
- ```
66
+ IOC (Inversion of Control) = **Don't create objects yourself, let the container create and manage them**
49
67
 
50
- ### 2. IOC Identifier Mapping
68
+ ### Traditional Approach vs IOC
51
69
 
52
- ```tsx
53
- // core/IOC.ts
54
- export interface IOCIdentifierMap {
55
- [IOCIdentifier.JSON]: import('@qlover/fe-corekit').JSONSerializer;
56
- [IOCIdentifier.LocalStorage]: import('@qlover/fe-corekit').ObjectStorage<
57
- string,
58
- string
59
- >;
60
- [IOCIdentifier.Logger]: import('@qlover/logger').LoggerInterface;
61
- [IOCIdentifier.AppConfig]: import('@qlover/corekit-bridge').EnvConfigInterface;
62
- // ... more mappings
70
+ ```typescript
71
+ // ❌ Traditional approach: Create dependencies yourself (tight coupling)
72
+ class UserComponent {
73
+ private userService = new UserService(); // Create yourself
74
+ private storage = new LocalStorage(); // Create yourself
75
+ private api = new UserApi(); // Create yourself
76
+
77
+ async loadUser() {
78
+ return await this.userService.getUser();
79
+ }
63
80
  }
81
+
82
+ // Problems:
83
+ // 1. UserComponent depends on concrete implementations
84
+ // 2. Cannot replace UserService implementation
85
+ // 3. Cannot mock UserService in tests
86
+ // 4. Need to manually create UserService dependencies
87
+
88
+
89
+ // ✅ IOC approach: Container injects dependencies (loose coupling)
90
+ function UserComponent() {
91
+ // Get service from IOC container
92
+ const userService = useIOC('UserServiceInterface'); // Container provides
93
+
94
+ async function loadUser() {
95
+ return await userService.getUser();
96
+ }
97
+
98
+ // UI only responsible for rendering
99
+ return <div>...</div>;
100
+ }
101
+
102
+ // Advantages:
103
+ // 1. UserComponent depends on interface, not implementation
104
+ // 2. Can easily replace UserService implementation
105
+ // 3. Can mock UserService in tests
106
+ // 4. UserService dependencies managed by container
64
107
  ```
65
108
 
66
- ### 3. IOC Function
109
+ ### Analogy
67
110
 
68
- ```tsx
69
- // core/IOC.ts
70
- export const IOC = createIOCFunction<IOCIdentifierMap>(
71
- new InversifyContainer()
72
- );
73
111
  ```
112
+ Traditional Approach = Cook at home
113
+ - Need to buy groceries (create dependencies)
114
+ - Need to cook (manage lifecycle)
115
+ - Need to wash dishes (clean up resources)
116
+
117
+ IOC Approach = Go to restaurant
118
+ - Order food (tell container what you need)
119
+ - Wait for food (container provides service)
120
+ - Don't need to worry about kitchen (dependency management handled by container)
121
+ ```
122
+
123
+ ---
74
124
 
75
- ## Usage
125
+ ## 🤔 Why Need IOC
76
126
 
77
- ### 1. Getting Service Instances
127
+ ### Core Problem: UI and Logic Mixed Together
78
128
 
79
- ```tsx
80
- // Using class name
81
- const userService = IOC(UserService);
129
+ #### ❌ Problem Example: No UI Separation
82
130
 
83
- // Using string identifier
84
- const logger = IOC('Logger');
85
- // Or
86
- const logger = IOC(IOCIdentifier.Logger);
131
+ ```typescript
132
+ // Traditional component: UI and logic mixed
133
+ function UserProfile() {
134
+ const [user, setUser] = useState(null);
135
+ const [loading, setLoading] = useState(false);
136
+ const [error, setError] = useState(null);
137
+
138
+ // 😰 Business logic mixed in component
139
+ useEffect(() => {
140
+ setLoading(true);
141
+
142
+ // 😰 API call in component
143
+ fetch('/api/user')
144
+ .then(res => res.json())
145
+ .then(data => {
146
+ // 😰 Data processing in component
147
+ const processedData = {
148
+ ...data,
149
+ fullName: `${data.firstName} ${data.lastName}`
150
+ };
151
+ setUser(processedData);
152
+ })
153
+ .catch(err => setError(err))
154
+ .finally(() => setLoading(false));
155
+ }, []);
87
156
 
88
- // Using AppConfig
89
- const appConfig = IOC(IOCIdentifier.AppConfig);
157
+ // 😰 More business logic
158
+ const handleLogout = () => {
159
+ localStorage.removeItem('token');
160
+ localStorage.removeItem('user');
161
+ window.location.href = '/login';
162
+ };
163
+
164
+ // UI rendering
165
+ if (loading) return <div>Loading...</div>;
166
+ if (error) return <div>Error: {error.message}</div>;
167
+
168
+ return (
169
+ <div>
170
+ <h1>{user?.fullName}</h1>
171
+ <button onClick={handleLogout}>Logout</button>
172
+ </div>
173
+ );
174
+ }
175
+
176
+ // 😰😰😰 Problem Summary:
177
+ // 1. UI and logic mixed, hard to maintain
178
+ // 2. Logic can't be reused (what if another component needs user info?)
179
+ // 3. Hard to test (need to render component to test business logic)
180
+ // 4. Unclear responsibilities (component does too much)
181
+ // 5. Can't test logic separately (must test through UI)
90
182
  ```
91
183
 
92
- ### 2. Using Dependency Injection in Services
184
+ #### Solution: IOC + UI Separation
93
185
 
94
- ```tsx
95
- import { inject, injectable } from 'inversify';
96
- import { UserApi } from '@/base/apis/userApi/UserApi';
97
- import { RouteService } from './RouteService';
98
- import { IOCIdentifier } from '@config/IOCIdentifier';
186
+ ```typescript
187
+ // Step 1: Define interface (Port)
188
+ export interface UserServiceInterface {
189
+ getUser(): Promise<UserInfo>;
190
+ logout(): Promise<void>;
191
+ isAuthenticated(): boolean;
192
+ }
99
193
 
194
+ // ✅ Step 2: Implement service (Logic layer)
100
195
  @injectable()
101
- export class UserService extends UserAuthService<UserInfo> {
196
+ export class UserService implements UserServiceInterface {
102
197
  constructor(
103
- @inject(RouteService) protected routerService: RouteService,
104
- @inject(UserApi) userApi: UserAuthApiInterface<UserInfo>,
105
- @inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
106
- @inject(IOCIdentifier.LocalStorageEncrypt)
107
- storage: SyncStorageInterface<string, string>
108
- ) {
109
- super(userApi, {
110
- userStorage: {
111
- key: appConfig.userInfoStorageKey,
112
- storage: storage
113
- },
114
- credentialStorage: {
115
- key: appConfig.userTokenStorageKey,
116
- storage: storage
117
- }
118
- });
198
+ @inject(UserApi) private api: UserApi,
199
+ @inject(IOCIdentifier.AppConfig) private config: AppConfig,
200
+ @inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage,
201
+ @inject(IOCIdentifier.RouteServiceInterface) private router: RouteService
202
+ ) {}
203
+
204
+ // Pure logic: Get user info
205
+ async getUser(): Promise<UserInfo> {
206
+ const data = await this.api.getUserInfo();
207
+
208
+ // Data processing
209
+ return {
210
+ ...data,
211
+ fullName: `${data.firstName} ${data.lastName}`
212
+ };
213
+ }
214
+
215
+ // Pure logic: Logout
216
+ async logout(): Promise<void> {
217
+ this.storage.removeItem(this.config.userTokenStorageKey);
218
+ this.storage.removeItem(this.config.userInfoStorageKey);
219
+ await this.router.push('/login');
220
+ }
221
+
222
+ isAuthenticated(): boolean {
223
+ return !!this.storage.getItem(this.config.userTokenStorageKey);
119
224
  }
120
225
  }
226
+
227
+ // ✅ Step 3: UI component (UI layer)
228
+ function UserProfile() {
229
+ // Get service from IOC container
230
+ const userService = useIOC('UserServiceInterface');
231
+ const [user, setUser] = useState(null);
232
+ const [loading, setLoading] = useState(false);
233
+
234
+ useEffect(() => {
235
+ setLoading(true);
236
+ // ✅ UI only calls service, contains no business logic
237
+ userService.getUser()
238
+ .then(setUser)
239
+ .finally(() => setLoading(false));
240
+ }, []);
241
+
242
+ // ✅ UI only responsible for rendering and event binding
243
+ if (loading) return <div>Loading...</div>;
244
+
245
+ return (
246
+ <div>
247
+ <h1>{user?.fullName}</h1>
248
+ <button onClick={() => userService.logout()}>Logout</button>
249
+ </div>
250
+ );
251
+ }
252
+
253
+ // ✅✅✅ Advantages Summary:
254
+ // 1. UI and logic completely separated, clear responsibilities
255
+ // 2. Logic can be reused (other components can use UserService)
256
+ // 3. Easy to test (can test UserService independently, no need to render UI)
257
+ // 4. Easy to maintain (changing logic doesn't affect UI, changing UI doesn't affect logic)
258
+ // 5. Can test logic separately (no dependency on UI)
121
259
  ```
122
260
 
123
- ### 3. Using in Bootstrap
261
+ ### Comparison Summary
262
+
263
+ | Feature | No UI Separation | IOC + UI Separation |
264
+ | ------------------------------- | ---------------------------------- | --------------------------------------------- |
265
+ | **Clarity of Responsibilities** | ❌ UI and logic mixed | ✅ UI only renders, logic independent |
266
+ | **Testability** | ❌ Must render component to test | ✅ Logic can be tested independently |
267
+ | **Reusability** | ❌ Logic can't be reused | ✅ Logic can be reused in multiple components |
268
+ | **Maintainability** | ❌ Changing logic affects UI | ✅ UI and logic modified independently |
269
+ | **Test Speed** | ❌ Slow (need to render UI) | ✅ Fast (pure logic tests) |
270
+ | **Test Complexity** | ❌ High (need to mock many things) | ✅ Low (only need to mock interfaces) |
271
+
272
+ ---
273
+
274
+ ## ❓ Two Key Questions
275
+
276
+ ### Question 1: Why does an implementation class also need an interface?
277
+
278
+ Many developers ask: "If `UserService` only has one implementation class, why define a `UserServiceInterface` interface?"
279
+
280
+ #### Answer: For testability and flexibility
281
+
282
+ ```typescript
283
+ // ❌ No interface: Hard to test
284
+ class UserComponent {
285
+ constructor(
286
+ @inject(UserService) private userService: UserService // Depend on concrete implementation
287
+ ) {}
288
+ }
289
+
290
+ // In tests:
291
+ describe('UserComponent', () => {
292
+ it('should load user', () => {
293
+ // ❌ Problem: Cannot mock UserService
294
+ // UserService has many dependencies (API, Storage, Router, etc.)
295
+ // Need to create all these dependencies to create UserService
296
+
297
+ const userApi = new UserApi(); // Need to create
298
+ const storage = new Storage(); // Need to create
299
+ const router = new Router(); // Need to create
300
+ const config = new AppConfig(); // Need to create
301
+
302
+ const userService = new UserService(userApi, config, storage, router);
303
+ const component = new UserComponent(userService);
304
+
305
+ // 😰 Too complex!
306
+ });
307
+ });
308
+
309
+ // ✅ With interface: Easy to test
310
+ class UserComponent {
311
+ constructor(
312
+ @inject('UserServiceInterface') // Depend on interface
313
+ private userService: UserServiceInterface
314
+ ) {}
315
+ }
316
+
317
+ // In tests:
318
+ describe('UserComponent', () => {
319
+ it('should load user', () => {
320
+ // ✅ Only need to mock interface
321
+ const mockUserService: UserServiceInterface = {
322
+ getUser: jest.fn().mockResolvedValue({ name: 'John' }),
323
+ logout: jest.fn(),
324
+ isAuthenticated: jest.fn().mockReturnValue(true)
325
+ };
326
+
327
+ const component = new UserComponent(mockUserService);
124
328
 
125
- ```tsx
126
- // Register services in bootstrapper
127
- bootstrap.use([
128
- IOC(UserService), // User service
129
- IOC(I18nService), // Internationalization service
130
- new UserApiBootstarp() // API configuration
131
- ]);
329
+ // ✅ Simple and clear!
330
+ });
331
+ });
132
332
  ```
133
333
 
134
- ## Service Registration
334
+ **Key Advantages:**
335
+
336
+ 1. **Simple testing** - Only need to mock interface methods, no need to create real dependencies
337
+ 2. **Isolation** - When testing UserComponent, don't need to care about UserService implementation details
338
+ 3. **Flexibility** - Can easily replace implementation in future (like adding MockUserService, CacheUserService, etc.)
339
+ 4. **Decoupling** - Component only depends on interface, not concrete implementation
340
+
341
+ **Even with only one implementation class, interface is necessary because:**
342
+
343
+ - ✅ Need to mock in tests
344
+ - ✅ May have new implementations in future
345
+ - ✅ Component shouldn't depend on concrete implementation
346
+ - ✅ Interface is contract, implementation is detail
135
347
 
136
- ### 1. Global Service Registration
348
+ ### Question 2: Why does a simple UI component also need UI separation?
137
349
 
138
- ```tsx
139
- // core/registers/RegisterGlobals.ts
140
- export const RegisterGlobals: IOCRegister = {
141
- register(container, _, options): void {
142
- // Register application configuration
143
- container.bind(IOCIdentifier.AppConfig, options!.appConfig);
350
+ Many developers ask: "My component is simple, just displays a username, why separate?"
144
351
 
145
- // Register logging service
146
- container.bind(Logger, logger);
147
- container.bind(IOCIdentifier.Logger, logger);
352
+ #### Answer: For testability and future extensibility
148
353
 
149
- // Register storage services
150
- container.bind(IOCIdentifier.LocalStorage, localStorage);
151
- container.bind(IOCIdentifier.LocalStorageEncrypt, localStorageEncrypt);
152
- container.bind(IOCIdentifier.CookieStorage, cookieStorage);
354
+ ```typescript
355
+ // ❌ Simple component, not separated
356
+ function UserName() {
357
+ const [name, setName] = useState('');
358
+
359
+ useEffect(() => {
360
+ // 😰 Even if simple, logic is mixed in UI
361
+ fetch('/api/user')
362
+ .then(res => res.json())
363
+ .then(data => setName(data.name));
364
+ }, []);
365
+
366
+ return <span>{name}</span>;
367
+ }
368
+
369
+ // Problems:
370
+ // 1. Cannot test logic (must render component)
371
+ // 2. What if logic becomes complex? (add cache, error handling, etc.)
372
+ // 3. What if other components need username? (copy-paste?)
373
+
374
+
375
+ // ✅ Simple component, but separated
376
+ // 1. Service (Logic layer)
377
+ @injectable()
378
+ export class UserService implements UserServiceInterface {
379
+ constructor(@inject(UserApi) private api: UserApi) {}
380
+
381
+ async getUserName(): Promise<string> {
382
+ const user = await this.api.getUserInfo();
383
+ return user.name;
153
384
  }
154
- };
385
+ }
386
+
387
+ // 2. UI component (UI layer)
388
+ function UserName() {
389
+ const userService = useIOC('UserServiceInterface');
390
+ const [name, setName] = useState('');
391
+
392
+ useEffect(() => {
393
+ userService.getUserName().then(setName);
394
+ }, []);
395
+
396
+ return <span>{name}</span>;
397
+ }
398
+
399
+ // Advantages:
400
+ // 1. ✅ Can test getUserName logic independently
401
+ // 2. ✅ When logic becomes complex in future, only need to modify UserService
402
+ // 3. ✅ Other components can reuse UserService
403
+ // 4. ✅ UI component stays simple, only responsible for rendering
155
404
  ```
156
405
 
157
- ### 2. Common Service Registration
406
+ **Key Scenario: Logic Gradually Becomes Complex**
158
407
 
159
- ```tsx
160
- // core/registers/RegisterCommon.ts
161
- export const RegisterCommon: IOCRegister = {
162
- register(container, _, options): void {
163
- const AppConfig = container.get(IOCIdentifier.AppConfig);
408
+ ```typescript
409
+ // ❌ Not separated: Component becomes bloated when logic gets complex
410
+ function UserName() {
411
+ const [name, setName] = useState('');
412
+ const [loading, setLoading] = useState(false);
413
+ const [error, setError] = useState(null);
164
414
 
165
- // Register API-related services
166
- const feApiToken = new TokenStorage(AppConfig.userTokenStorageKey, {
167
- storage: container.get(IOCIdentifier.LocalStorageEncrypt)
168
- });
415
+ useEffect(() => {
416
+ setLoading(true);
169
417
 
170
- container.bind(IOCIdentifier.FeApiToken, feApiToken);
171
- container.bind(IOCIdentifier.FeApiCommonPlugin, feApiRequestCommonPlugin);
418
+ // 😰 Add cache
419
+ const cached = localStorage.getItem('userName');
420
+ if (cached) {
421
+ setName(cached);
422
+ setLoading(false);
423
+ return;
424
+ }
172
425
 
173
- // Register theme service
174
- container.bind(
175
- ThemeService,
176
- new ThemeService({
177
- ...themeConfig,
178
- storage: localStorage
426
+ // 😰 Add error handling
427
+ fetch('/api/user')
428
+ .then(res => {
429
+ if (!res.ok) throw new Error('Failed');
430
+ return res.json();
179
431
  })
180
- );
181
-
182
- // Register router service
183
- container.bind(
184
- RouteService,
185
- new RouteService({
186
- routes: baseRoutes,
187
- logger
432
+ .then(data => {
433
+ setName(data.name);
434
+ localStorage.setItem('userName', data.name);
188
435
  })
189
- );
436
+ .catch(err => setError(err))
437
+ .finally(() => setLoading(false));
438
+ }, []);
439
+
440
+ // 😰 Component becomes complex
441
+ if (loading) return <span>Loading...</span>;
442
+ if (error) return <span>Error</span>;
443
+ return <span>{name}</span>;
444
+ }
190
445
 
191
- // Register i18n service
192
- container.bind(I18nService, new I18nService(options!.pathname));
446
+
447
+ // ✅ With separation: When logic becomes complex, only modify service
448
+ @injectable()
449
+ export class UserService implements UserServiceInterface {
450
+ constructor(
451
+ @inject(UserApi) private api: UserApi,
452
+ @inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage
453
+ ) {}
454
+
455
+ // ✅ Logic in service, clear and straightforward
456
+ async getUserName(): Promise<string> {
457
+ // Cache logic
458
+ const cached = this.storage.getItem('userName');
459
+ if (cached) return cached;
460
+
461
+ // API call
462
+ const user = await this.api.getUserInfo();
463
+
464
+ // Cache
465
+ this.storage.setItem('userName', user.name);
466
+
467
+ return user.name;
193
468
  }
194
- };
469
+ }
470
+
471
+ // ✅ UI component stays simple
472
+ function UserName() {
473
+ const userService = useIOC('UserServiceInterface');
474
+ const [name, setName] = useState('');
475
+ const [loading, setLoading] = useState(false);
476
+
477
+ useEffect(() => {
478
+ setLoading(true);
479
+ userService.getUserName()
480
+ .then(setName)
481
+ .finally(() => setLoading(false));
482
+ }, []);
483
+
484
+ if (loading) return <span>Loading...</span>;
485
+ return <span>{name}</span>;
486
+ }
487
+ ```
488
+
489
+ **Summary: Even if component is simple, still separate because:**
490
+
491
+ - ✅ **Simple now doesn't mean simple later** - Requirements change
492
+ - ✅ **Logic can be reused** - Other components may need it
493
+ - ✅ **Easy to test** - Logic can be tested independently
494
+ - ✅ **Clear responsibilities** - UI only renders, logic independent
495
+ - ✅ **Easy to maintain** - Changing logic doesn't affect UI
496
+
497
+ ---
498
+
499
+ ## 🛠️ Implementation in the Project
500
+
501
+ ### 1. File Structure
502
+
503
+ ```
504
+ src/
505
+ ├── base/
506
+ │ ├── port/ # Interface definition layer
507
+ │ │ ├── UserServiceInterface.ts
508
+ │ │ ├── I18nServiceInterface.ts
509
+ │ │ └── RouteServiceInterface.ts
510
+ │ └── services/ # Service implementation layer
511
+ │ ├── UserService.ts
512
+ │ ├── I18nService.ts
513
+ │ └── RouteService.ts
514
+ ├── core/
515
+ │ ├── clientIoc/
516
+ │ │ ├── ClientIOC.ts # IOC container
517
+ │ │ └── ClientIOCRegister.ts # Registrar
518
+ │ └── globals.ts # Global instances
519
+ ├── uikit/
520
+ │ ├── hooks/
521
+ │ │ └── useIOC.ts # React Hook
522
+ │ └── contexts/
523
+ │ └── IOCContext.tsx # React Context
524
+ └── config/
525
+ └── IOCIdentifier.ts # Identifier definitions
526
+
195
527
  ```
196
528
 
197
- ### 3. Controller Registration
198
-
199
- ```tsx
200
- // core/registers/RegisterControllers.ts
201
- export class RegisterControllers implements IOCRegister {
202
- register(
203
- container: IOCContainer,
204
- _: IOCManagerInterface<IOCContainer>
205
- ): void {
206
- // Register controllers
207
- const jsonStorageController = new JSONStorageController(localStorage);
208
- container.bind(JSONStorageController, jsonStorageController);
209
-
210
- // Configure processor
211
- container
212
- .get(ProcesserExecutor)
213
- .use(container.get(I18nKeyErrorPlugin))
214
- .use(container.get(UserService));
529
+ ### 2. IOC Identifier Definition
530
+
531
+ ```typescript
532
+ // config/IOCIdentifier.ts
533
+ export interface IOCIdentifierMap {
534
+ AppConfig: AppConfig;
535
+ Logger: LoggerInterface;
536
+ LocalStorageEncrypt: SyncStorageInterface<string, string>;
537
+ UserServiceInterface: UserServiceInterface;
538
+ I18nServiceInterface: I18nServiceInterface;
539
+ RouteServiceInterface: RouteServiceInterface;
540
+ }
541
+
542
+ export const IOCIdentifier = {
543
+ AppConfig: 'AppConfig',
544
+ Logger: 'Logger',
545
+ LocalStorageEncrypt: 'LocalStorageEncrypt',
546
+ UserServiceInterface: 'UserServiceInterface',
547
+ I18nServiceInterface: 'I18nServiceInterface',
548
+ RouteServiceInterface: 'RouteServiceInterface'
549
+ } as const;
550
+ ```
551
+
552
+ ### 3. Service Registration
553
+
554
+ ```typescript
555
+ // src/core/clientIoc/ClientIOCRegister.ts
556
+ export class ClientIOCRegister implements IOCRegisterInterface {
557
+ constructor(protected options: IocRegisterOptions) {}
558
+
559
+ /**
560
+ * Register global services
561
+ */
562
+ protected registerGlobals(ioc: IOCContainerInterface): void {
563
+ const { appConfig } = this.options;
564
+ const { dialogHandler, localStorageEncrypt, JSON, logger } = globals;
565
+
566
+ // ✅ Register global instances
567
+ ioc.bind(IOCIdentifier.JSONSerializer, JSON);
568
+ ioc.bind(IOCIdentifier.Logger, logger);
569
+ ioc.bind(IOCIdentifier.AppConfig, appConfig);
570
+ ioc.bind(IOCIdentifier.LocalStorageEncrypt, localStorageEncrypt);
571
+ }
572
+
573
+ /**
574
+ * Register business services
575
+ */
576
+ protected registerImplement(ioc: IOCContainerInterface): void {
577
+ // ✅ Register service implementations
578
+ ioc.bind(
579
+ IOCIdentifier.I18nServiceInterface,
580
+ new I18nService(this.options.pathname)
581
+ );
582
+
583
+ ioc.bind(IOCIdentifier.RouteServiceInterface, new RouteService(/* ... */));
584
+
585
+ // ✅ Service can depend on other services
586
+ ioc.bind(IOCIdentifier.UserServiceInterface, ioc.get(UserService));
587
+ }
588
+
589
+ /**
590
+ * Registration entry point
591
+ */
592
+ register(ioc: IOCContainerInterface): void {
593
+ this.registerGlobals(ioc);
594
+ this.registerImplement(ioc);
215
595
  }
216
596
  }
217
597
  ```
218
598
 
219
- ## Practical Application Scenarios
599
+ ### 4. Create IOC Container
220
600
 
221
- ### 1. User Authentication Service
601
+ ```typescript
602
+ // src/core/clientIoc/ClientIOC.ts
603
+ import { createIOCFunction } from '@qlover/corekit-bridge';
604
+ import { InversifyContainer } from '@/base/cases/InversifyContainer';
605
+ import { ClientIOCRegister } from './ClientIOCRegister';
222
606
 
223
- ```tsx
224
- @injectable()
225
- export class UserService extends UserAuthService<UserInfo> {
226
- constructor(
227
- @inject(RouteService) protected routerService: RouteService,
228
- @inject(UserApi) userApi: UserAuthApiInterface<UserInfo>,
229
- @inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
230
- @inject(IOCIdentifier.LocalStorageEncrypt)
231
- storage: SyncStorageInterface<string, string>
232
- ) {
233
- super(userApi, {
234
- userStorage: {
235
- key: appConfig.userInfoStorageKey,
236
- storage: storage
237
- },
238
- credentialStorage: {
239
- key: appConfig.userTokenStorageKey,
240
- storage: storage
607
+ export const clientIOC = {
608
+ create(options: IocRegisterOptions) {
609
+ // Create container
610
+ const container = new InversifyContainer();
611
+
612
+ // Create IOC function
613
+ const IOC = createIOCFunction(container);
614
+
615
+ // Register services
616
+ const register = new ClientIOCRegister(options);
617
+ register.register(container, IOC);
618
+
619
+ return IOC;
620
+ }
621
+ };
622
+ ```
623
+
624
+ ### 5. Initialize in Bootstrap
625
+
626
+ ```typescript
627
+ // src/core/bootstraps/BootstrapClient.ts
628
+ export class BootstrapClient {
629
+ static async main(args: BootstrapClientArgs) {
630
+ const { root, bootHref, ioc } = args;
631
+
632
+ // ✅ Create IOC container
633
+ const IOC = ioc.create({
634
+ pathname: bootHref,
635
+ appConfig: appConfig
636
+ });
637
+
638
+ // Use IOC in Bootstrap
639
+ const bootstrap = new Bootstrap({
640
+ root,
641
+ logger,
642
+ ioc: {
643
+ manager: IOC,
644
+ register: iocRegister
241
645
  }
242
646
  });
647
+
648
+ await bootstrap.initialize();
649
+ await bootstrap.start();
243
650
  }
651
+ }
652
+ ```
244
653
 
245
- async onBefore(): Promise<void> {
246
- if (this.isAuthenticated()) {
247
- return;
248
- }
654
+ ---
249
655
 
250
- const userToken = this.getToken();
251
- if (!userToken) {
252
- throw new AppError('NO_USER_TOKEN');
253
- }
656
+ ## 📝 How to Use
254
657
 
255
- await this.userInfo();
256
- this.store.authSuccess();
257
- }
658
+ ### 1. Define Interface (Port)
659
+
660
+ ```typescript
661
+ // src/base/port/UserServiceInterface.ts
662
+ export interface UserServiceInterface {
663
+ getUser(): Promise<UserInfo>;
664
+ login(username: string, password: string): Promise<void>;
665
+ logout(): Promise<void>;
666
+ isAuthenticated(): boolean;
258
667
  }
259
668
  ```
260
669
 
261
- ### 2. API Configuration Service
670
+ ### 2. Implement Service
671
+
672
+ ```typescript
673
+ // src/base/services/UserService.ts
674
+ import { injectable, inject } from 'inversify';
675
+
676
+ @injectable()
677
+ export class UserService implements UserServiceInterface {
678
+ constructor(
679
+ @inject(UserApi) private api: UserApi,
680
+ @inject(IOCIdentifier.AppConfig) private config: AppConfig,
681
+ @inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage,
682
+ @inject(IOCIdentifier.RouteServiceInterface) private router: RouteService
683
+ ) {}
684
+
685
+ async getUser(): Promise<UserInfo> {
686
+ const token = this.storage.getItem(this.config.userTokenStorageKey);
687
+ if (!token) throw new Error('No token');
262
688
 
263
- ```tsx
264
- export class UserApiBootstarp implements BootstrapExecutorPlugin {
265
- readonly pluginName = 'UserApiBootstarp';
689
+ return await this.api.getUserInfo(token);
690
+ }
691
+
692
+ async login(username: string, password: string): Promise<void> {
693
+ const response = await this.api.login({ username, password });
694
+ this.storage.setItem(this.config.userTokenStorageKey, response.token);
695
+ }
266
696
 
267
- onBefore({ parameters: { ioc } }: BootstrapContext): void {
268
- // Get UserApi instance through IOC and configure plugins
269
- ioc
270
- .get<UserApi>(UserApi)
271
- .usePlugin(new FetchURLPlugin())
272
- .usePlugin(IOC.get(IOCIdentifier.ApiMockPlugin))
273
- .usePlugin(IOC.get(RequestLogger));
697
+ async logout(): Promise<void> {
698
+ this.storage.removeItem(this.config.userTokenStorageKey);
699
+ await this.router.push('/login');
700
+ }
701
+
702
+ isAuthenticated(): boolean {
703
+ return !!this.storage.getItem(this.config.userTokenStorageKey);
274
704
  }
275
705
  }
276
706
  ```
277
707
 
278
- ### 3. Using in Components
708
+ ### 3. Use in UI Components
709
+
710
+ ```typescript
711
+ // src/pages/UserProfile.tsx
712
+ import { useIOC } from '@/uikit/hooks/useIOC';
279
713
 
280
- ```tsx
281
- // Using IOC services in React components
282
714
  function UserProfile() {
283
- const userService = IOC(UserService);
284
- const { user } = useStore(userService.store);
715
+ // Get service from IOC container
716
+ const userService = useIOC('UserServiceInterface');
717
+ const [user, setUser] = useState<UserInfo | null>(null);
285
718
 
719
+ useEffect(() => {
720
+ userService.getUser().then(setUser);
721
+ }, []);
722
+
723
+ const handleLogout = () => {
724
+ userService.logout();
725
+ };
726
+
727
+ // ✅ UI only responsible for rendering
286
728
  return (
287
729
  <div>
288
- <h1>Welcome, {user?.name}</h1>
289
- <button onClick={() => userService.logout()}>Logout</button>
730
+ <h1>{user?.name}</h1>
731
+ <button onClick={handleLogout}>Logout</button>
290
732
  </div>
291
733
  );
292
734
  }
293
735
  ```
294
736
 
295
- ## Best Practices
737
+ ### 4. Use Other Services in Services
296
738
 
297
- ### 1. Service Design Principles
298
-
299
- ```tsx
300
- // ✅ Good design: Single responsibility
739
+ ```typescript
740
+ // src/base/services/ProfileService.ts
301
741
  @injectable()
302
- export class UserService {
742
+ export class ProfileService {
303
743
  constructor(
304
- @inject(UserApi) private userApi: UserApi,
305
- @inject(IOCIdentifier.AppConfig) private appConfig: AppConfig
744
+ // Service can depend on other services
745
+ @inject(IOCIdentifier.UserServiceInterface)
746
+ private userService: UserServiceInterface,
747
+ @inject(IOCIdentifier.I18nServiceInterface)
748
+ private i18n: I18nServiceInterface
306
749
  ) {}
307
750
 
308
- async getUserInfo(): Promise<UserInfo> {
309
- return this.userApi.getUserInfo();
751
+ async getUserProfile(): Promise<string> {
752
+ const user = await this.userService.getUser();
753
+ return this.i18n.t('profile.welcome', { name: user.name });
310
754
  }
311
755
  }
756
+ ```
757
+
758
+ ---
759
+
760
+ ## 🧪 Testing
761
+
762
+ ### Core Advantage: UI and logic can be tested independently, and also in combination
763
+
764
+ #### 1. Test Logic Independently (No UI needed)
765
+
766
+ ```typescript
767
+ // __tests__/src/base/services/UserService.test.ts
768
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
769
+ import { UserService } from '@/base/services/UserService';
770
+
771
+ describe('UserService (Logic Test)', () => {
772
+ let userService: UserService;
773
+ let mockApi: any;
774
+ let mockStorage: any;
775
+ let mockRouter: any;
776
+ let mockConfig: any;
777
+
778
+ beforeEach(() => {
779
+ // ✅ Only need to mock interfaces
780
+ mockApi = {
781
+ getUserInfo: vi.fn(),
782
+ login: vi.fn()
783
+ };
784
+
785
+ mockStorage = {
786
+ getItem: vi.fn(),
787
+ setItem: vi.fn(),
788
+ removeItem: vi.fn()
789
+ };
790
+
791
+ mockRouter = {
792
+ push: vi.fn()
793
+ };
794
+
795
+ mockConfig = {
796
+ userTokenStorageKey: '__test_token__'
797
+ };
798
+
799
+ // ✅ Create service
800
+ userService = new UserService(mockApi, mockConfig, mockStorage, mockRouter);
801
+ });
802
+
803
+ it('should get user when token exists', async () => {
804
+ // ✅ Set mock return value
805
+ mockStorage.getItem.mockReturnValue('test-token');
806
+ mockApi.getUserInfo.mockResolvedValue({ name: 'John' });
807
+
808
+ // ✅ Test logic
809
+ const user = await userService.getUser();
810
+
811
+ // ✅ Verify result
812
+ expect(user.name).toBe('John');
813
+ expect(mockStorage.getItem).toHaveBeenCalledWith('__test_token__');
814
+ expect(mockApi.getUserInfo).toHaveBeenCalledWith('test-token');
815
+ });
816
+
817
+ it('should throw error when no token', async () => {
818
+ // ✅ Test error scenario
819
+ mockStorage.getItem.mockReturnValue(null);
820
+
821
+ await expect(userService.getUser()).rejects.toThrow('No token');
822
+ });
823
+
824
+ it('should login and save token', async () => {
825
+ // ✅ Test login logic
826
+ mockApi.login.mockResolvedValue({ token: 'new-token' });
827
+
828
+ await userService.login('user', 'pass');
829
+
830
+ expect(mockApi.login).toHaveBeenCalledWith({
831
+ username: 'user',
832
+ password: 'pass'
833
+ });
834
+ expect(mockStorage.setItem).toHaveBeenCalledWith(
835
+ '__test_token__',
836
+ 'new-token'
837
+ );
838
+ });
839
+
840
+ it('should logout and clear token', async () => {
841
+ // ✅ Test logout logic
842
+ await userService.logout();
843
+
844
+ expect(mockStorage.removeItem).toHaveBeenCalledWith('__test_token__');
845
+ expect(mockRouter.push).toHaveBeenCalledWith('/login');
846
+ });
847
+ });
848
+
849
+ // ✅✅✅ Advantages:
850
+ // 1. Don't need to render UI
851
+ // 2. Tests run fast (pure logic)
852
+ // 3. Easy to mock (only need to mock interfaces)
853
+ // 4. Can test all edge cases
854
+ ```
855
+
856
+ #### 2. Test UI Independently (No real logic needed)
857
+
858
+ ```typescript
859
+ // __tests__/src/pages/UserProfile.test.tsx
860
+ import { describe, it, expect, vi } from 'vitest';
861
+ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
862
+ import { UserProfile } from '@/pages/UserProfile';
863
+ import { IOCProvider } from '@/uikit/contexts/IOCContext';
864
+
865
+ describe('UserProfile (UI Test)', () => {
866
+ it('should display user name', async () => {
867
+ // ✅ Mock service
868
+ const mockUserService = {
869
+ getUser: vi.fn().mockResolvedValue({ name: 'John Doe' }),
870
+ logout: vi.fn(),
871
+ isAuthenticated: vi.fn().mockReturnValue(true)
872
+ };
873
+
874
+ const mockIOC = (identifier: string) => {
875
+ if (identifier === 'UserServiceInterface') return mockUserService;
876
+ };
877
+
878
+ // ✅ Render component
879
+ render(
880
+ <IOCProvider value={mockIOC}>
881
+ <UserProfile />
882
+ </IOCProvider>
883
+ );
884
+
885
+ // ✅ Verify UI
886
+ await waitFor(() => {
887
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
888
+ });
889
+ });
890
+
891
+ it('should call logout when button clicked', async () => {
892
+ const mockUserService = {
893
+ getUser: vi.fn().mockResolvedValue({ name: 'John' }),
894
+ logout: vi.fn(),
895
+ isAuthenticated: vi.fn().mockReturnValue(true)
896
+ };
897
+
898
+ const mockIOC = (identifier: string) => {
899
+ if (identifier === 'UserServiceInterface') return mockUserService;
900
+ };
901
+
902
+ render(
903
+ <IOCProvider value={mockIOC}>
904
+ <UserProfile />
905
+ </IOCProvider>
906
+ );
907
+
908
+ // ✅ Simulate user action
909
+ const logoutButton = screen.getByText('Logout');
910
+ fireEvent.click(logoutButton);
911
+
912
+ // ✅ Verify service call
913
+ expect(mockUserService.logout).toHaveBeenCalled();
914
+ });
915
+ });
916
+
917
+ // ✅✅✅ Advantages:
918
+ // 1. Don't need real service implementation
919
+ // 2. Can easily simulate various scenarios
920
+ // 3. UI tests focus on UI logic
921
+ ```
922
+
923
+ #### 3. Combination Testing (UI + Logic)
924
+
925
+ ```typescript
926
+ // __tests__/src/integration/UserFlow.test.tsx
927
+ import { describe, it, expect } from 'vitest';
928
+ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
929
+ import { UserProfile } from '@/pages/UserProfile';
930
+ import { UserService } from '@/base/services/UserService';
931
+ import { IOCProvider } from '@/uikit/contexts/IOCContext';
932
+
933
+ describe('User Flow (Integration Test)', () => {
934
+ it('should complete user login flow', async () => {
935
+ // ✅ Use real service implementation
936
+ const mockApi = {
937
+ getUserInfo: vi.fn().mockResolvedValue({ name: 'John' }),
938
+ login: vi.fn().mockResolvedValue({ token: 'test-token' })
939
+ };
940
+
941
+ const mockStorage = {
942
+ getItem: vi.fn(),
943
+ setItem: vi.fn(),
944
+ removeItem: vi.fn()
945
+ };
946
+
947
+ const mockRouter = { push: vi.fn() };
948
+ const mockConfig = { userTokenStorageKey: '__token__' };
949
+
950
+ // ✅ Create real service
951
+ const userService = new UserService(
952
+ mockApi,
953
+ mockConfig,
954
+ mockStorage,
955
+ mockRouter
956
+ );
312
957
 
313
- // Bad design: Too many responsibilities
958
+ const mockIOC = (identifier: string) => {
959
+ if (identifier === 'UserServiceInterface') return userService;
960
+ };
961
+
962
+ // ✅ Render real UI
963
+ render(
964
+ <IOCProvider value={mockIOC}>
965
+ <UserProfile />
966
+ </IOCProvider>
967
+ );
968
+
969
+ // ✅ Test complete flow
970
+ await waitFor(() => {
971
+ expect(screen.getByText('John')).toBeInTheDocument();
972
+ });
973
+
974
+ // ✅ Click logout
975
+ fireEvent.click(screen.getByText('Logout'));
976
+
977
+ // ✅ Verify entire flow
978
+ expect(mockStorage.removeItem).toHaveBeenCalledWith('__token__');
979
+ expect(mockRouter.push).toHaveBeenCalledWith('/login');
980
+ });
981
+ });
982
+
983
+ // ✅✅✅ Advantages:
984
+ // 1. Test real user flow
985
+ // 2. Can discover UI and logic integration issues
986
+ // 3. Closer to real usage scenarios
987
+ ```
988
+
989
+ ### Testing Strategy Summary
990
+
991
+ ```
992
+ ┌─────────────────────────────────────────┐
993
+ │ Testing Pyramid │
994
+ │ │
995
+ │ △ UI Tests (Few) │
996
+ │ ╱ ╲ │
997
+ │ ╱ ╲ │
998
+ │ ╱ ╲ │
999
+ │ ╱───────╲ Integration Tests (Some) │
1000
+ │ ╱ ╲ │
1001
+ │╱═══════════╲ Logic Tests (Many) │
1002
+ │ │
1003
+ │ Logic Tests: Fast, stable, comprehensive│
1004
+ │ Integration Tests: Verify integration │
1005
+ │ UI Tests: Verify user interactions │
1006
+ └─────────────────────────────────────────┘
1007
+ ```
1008
+
1009
+ **Recommended Test Ratio:**
1010
+
1011
+ - 70% Logic tests (UserService.test.ts)
1012
+ - 20% Integration tests (UserFlow.test.tsx)
1013
+ - 10% UI tests (UserProfile.test.tsx)
1014
+
1015
+ ---
1016
+
1017
+ ## 💎 Best Practices
1018
+
1019
+ ### 1. ✅ Always Define Interface
1020
+
1021
+ ```typescript
1022
+ // ✅ Good practice: Define interface first
1023
+ export interface UserServiceInterface {
1024
+ getUser(): Promise<UserInfo>;
1025
+ logout(): Promise<void>;
1026
+ }
1027
+
1028
+ // Then implement
314
1029
  @injectable()
315
- export class BadService {
1030
+ export class UserService implements UserServiceInterface {
1031
+ // ...
1032
+ }
1033
+
1034
+ // ❌ Bad practice: Write implementation directly
1035
+ @injectable()
1036
+ export class UserService {
1037
+ // No interface, hard to test
1038
+ }
1039
+ ```
1040
+
1041
+ ### 2. ✅ Complete UI and Logic Separation
1042
+
1043
+ ```typescript
1044
+ // ✅ Good practice: UI only responsible for rendering
1045
+ function UserProfile() {
1046
+ const userService = useIOC('UserServiceInterface');
1047
+ const [user, setUser] = useState(null);
1048
+
1049
+ useEffect(() => {
1050
+ userService.getUser().then(setUser);
1051
+ }, []);
1052
+
1053
+ return <div>{user?.name}</div>;
1054
+ }
1055
+
1056
+ // ❌ Bad practice: Logic mixed in UI
1057
+ function UserProfile() {
1058
+ const [user, setUser] = useState(null);
1059
+
1060
+ useEffect(() => {
1061
+ fetch('/api/user')
1062
+ .then(res => res.json())
1063
+ .then(setUser);
1064
+ }, []);
1065
+
1066
+ return <div>{user?.name}</div>;
1067
+ }
1068
+ ```
1069
+
1070
+ ### 3. ✅ Use Dependency Injection
1071
+
1072
+ ```typescript
1073
+ // ✅ Good practice: Inject through constructor
1074
+ @injectable()
1075
+ export class UserService {
316
1076
  constructor(
317
- @inject(UserApi) private userApi: UserApi,
318
- @inject(RouteService) private routeService: RouteService,
319
- @inject(ThemeService) private themeService: ThemeService,
320
- @inject(I18nService) private i18nService: I18nService
1077
+ @inject(UserApi) private api: UserApi,
1078
+ @inject(IOCIdentifier.AppConfig) private config: AppConfig
321
1079
  ) {}
1080
+ }
1081
+
1082
+ // ❌ Bad practice: Create dependencies directly
1083
+ export class UserService {
1084
+ private api = new UserApi();
1085
+ private config = new AppConfig();
1086
+ }
1087
+ ```
1088
+
1089
+ ### 4. ✅ Single Responsibility for Services
1090
+
1091
+ ```typescript
1092
+ // ✅ Good practice: Each service responsible for one thing
1093
+ @injectable()
1094
+ export class UserService {
1095
+ // Only responsible for user-related logic
1096
+ async getUser() {
1097
+ /* ... */
1098
+ }
1099
+ async logout() {
1100
+ /* ... */
1101
+ }
1102
+ }
322
1103
 
323
- // One service doing too many things
324
- async handleUserAction(): Promise<void> {
325
- // Handle user logic
326
- // Handle routing logic
327
- // Handle theme logic
328
- // Handle i18n logic
1104
+ @injectable()
1105
+ export class ThemeService {
1106
+ // Only responsible for theme-related logic
1107
+ setTheme() {
1108
+ /* ... */
1109
+ }
1110
+ getTheme() {
1111
+ /* ... */
329
1112
  }
330
1113
  }
1114
+
1115
+ // ❌ Bad practice: One service does multiple things
1116
+ @injectable()
1117
+ export class ApplicationService {
1118
+ async getUser() {
1119
+ /* ... */
1120
+ }
1121
+ setTheme() {
1122
+ /* ... */
1123
+ }
1124
+ changeLanguage() {
1125
+ /* ... */
1126
+ }
1127
+ // Too many responsibilities!
1128
+ }
331
1129
  ```
332
1130
 
333
- ### 2. Dependency Injection Best Practices
1131
+ ### 5. Depend on Interfaces, Not Implementations
334
1132
 
335
- ```tsx
336
- // ✅ Use interfaces instead of concrete implementations
1133
+ ```typescript
1134
+ // ✅ Good practice
337
1135
  @injectable()
338
1136
  export class UserService {
339
1137
  constructor(
340
- @inject('UserApiInterface') private userApi: UserAuthApiInterface<UserInfo>
1138
+ @inject('UserApiInterface') private api: UserApiInterface // Interface
341
1139
  ) {}
342
1140
  }
343
1141
 
344
- // Use identifiers instead of class names
1142
+ // Bad practice
345
1143
  @injectable()
346
- export class SomeService {
1144
+ export class UserService {
347
1145
  constructor(
348
- @inject(IOCIdentifier.Logger) private logger: LoggerInterface,
349
- @inject(IOCIdentifier.AppConfig) private appConfig: AppConfig
1146
+ @inject(UserApi) private api: UserApi // Concrete implementation
350
1147
  ) {}
351
1148
  }
352
1149
  ```
353
1150
 
354
- ### 3. Error Handling
1151
+ ### 6. Separate Even If Simple
355
1152
 
356
- ```tsx
1153
+ ```typescript
1154
+ // ✅ Good practice: Separate even if simple
357
1155
  @injectable()
358
- export class SafeService {
359
- constructor(@inject(IOCIdentifier.Logger) private logger: LoggerInterface) {}
360
-
361
- async doSomething(): Promise<void> {
362
- try {
363
- // Business logic
364
- } catch (error) {
365
- this.logger.error('Operation failed:', error);
366
- throw error;
367
- }
1156
+ export class CounterService {
1157
+ private count = 0;
1158
+
1159
+ increment() {
1160
+ this.count++;
1161
+ return this.count;
368
1162
  }
369
1163
  }
370
- ```
371
-
372
- ## Debugging and Testing
373
1164
 
374
- ### 1. Debugging IOC Container
1165
+ function Counter() {
1166
+ const counterService = useIOC('CounterService');
1167
+ const [count, setCount] = useState(0);
375
1168
 
376
- ```tsx
377
- // Check if service is registered
378
- const container = IOC.implemention;
379
- const isRegistered = container.isBound(UserService);
1169
+ const handleClick = () => {
1170
+ setCount(counterService.increment());
1171
+ };
380
1172
 
381
- // Get all registered services
382
- const bindings = container.getAll(UserService);
383
- ```
1173
+ return <button onClick={handleClick}>{count}</button>;
1174
+ }
384
1175
 
385
- ### 2. Unit Testing
1176
+ // Bad practice: Simple logic also mixed in UI
1177
+ function Counter() {
1178
+ const [count, setCount] = useState(0);
386
1179
 
387
- ```tsx
388
- import { Container } from 'inversify';
1180
+ return (
1181
+ <button onClick={() => setCount(count + 1)}>
1182
+ {count}
1183
+ </button>
1184
+ );
1185
+ }
1186
+ ```
389
1187
 
390
- describe('UserService', () => {
391
- let container: Container;
392
- let userService: UserService;
1188
+ ### 7. Write Comprehensive Tests
393
1189
 
394
- beforeEach(() => {
395
- container = new Container();
1190
+ ```typescript
1191
+ // Good practice: Logic tests + UI tests + Integration tests
1192
+ describe('UserService (Logic)', () => {
1193
+ it('should get user', async () => {
1194
+ /* ... */
1195
+ });
1196
+ it('should handle error', async () => {
1197
+ /* ... */
1198
+ });
1199
+ });
396
1200
 
397
- // Register test dependencies
398
- container.bind('UserApiInterface').toConstantValue(mockUserApi);
399
- container.bind(IOCIdentifier.AppConfig).toConstantValue(mockAppConfig);
400
- container
401
- .bind(IOCIdentifier.LocalStorageEncrypt)
402
- .toConstantValue(mockStorage);
1201
+ describe('UserProfile (UI)', () => {
1202
+ it('should display user', async () => {
1203
+ /* ... */
1204
+ });
1205
+ });
403
1206
 
404
- userService = container.get(UserService);
1207
+ describe('User Flow (Integration)', () => {
1208
+ it('should complete flow', async () => {
1209
+ /* ... */
405
1210
  });
1211
+ });
406
1212
 
407
- it('should authenticate user successfully', async () => {
408
- const result = await userService.onBefore();
409
- expect(result).toBeDefined();
1213
+ // Bad practice: Only UI tests
1214
+ describe('UserProfile', () => {
1215
+ it('should work', async () => {
1216
+ // Only test UI, logic not tested
410
1217
  });
411
1218
  });
412
1219
  ```
413
1220
 
414
- ## Summary
1221
+ ### 8. ✅ Use Type-safe Identifiers
1222
+
1223
+ ```typescript
1224
+ // ✅ Good practice: Type-safe identifiers
1225
+ const userService = useIOC('UserServiceInterface');
1226
+ // TypeScript knows userService type
1227
+
1228
+ // ❌ Bad practice: String literals
1229
+ const userService = useIOC('UserService');
1230
+ // Easy to misspell, no type checking
1231
+ ```
1232
+
1233
+ ---
1234
+
1235
+ ## ❓ FAQ
1236
+
1237
+ ### Q1: Does IOC increase complexity?
1238
+
1239
+ **A:** Short-term maybe, but long-term it greatly reduces complexity:
1240
+
1241
+ **Short-term (small projects):**
1242
+
1243
+ - Need to define interfaces
1244
+ - Need to register services
1245
+ - Need to learn IOC concepts
1246
+
1247
+ **Long-term (project grows):**
1248
+
1249
+ - ✅ Easy to test (save lots of testing time)
1250
+ - ✅ Easy to maintain (clear dependency relationships)
1251
+ - ✅ Easy to extend (adding new features is simple)
1252
+ - ✅ Team collaboration (clear responsibilities)
1253
+
1254
+ ### Q2: Do all components need IOC?
1255
+
1256
+ **A:** Not necessarily, but recommended:
1257
+
1258
+ **Scenarios that need IOC:**
1259
+
1260
+ - ✅ Components with business logic
1261
+ - ✅ Components that call APIs
1262
+ - ✅ Components that access Storage
1263
+ - ✅ Components that need testing
1264
+
1265
+ **Scenarios that can skip IOC:**
1266
+
1267
+ - Pure presentational components (only receive props)
1268
+ - Very simple UI components (like Button, Icon)
1269
+
1270
+ ### Q3: Why not directly import service?
1271
+
1272
+ **A:**
1273
+
1274
+ ```typescript
1275
+ // ❌ Direct import
1276
+ import { userService } from '@/services/UserService';
1277
+
1278
+ function UserProfile() {
1279
+ // Problems:
1280
+ // 1. userService is singleton, can't replace in tests
1281
+ // 2. userService dependencies created at module load time
1282
+ // 3. Hard to mock
1283
+ }
1284
+
1285
+ // ✅ Use IOC
1286
+ function UserProfile() {
1287
+ const userService = useIOC('UserServiceInterface');
1288
+
1289
+ // Advantages:
1290
+ // 1. Can provide mock implementation in tests
1291
+ // 2. Dependencies managed by container, created on demand
1292
+ // 3. Easy to mock
1293
+ }
1294
+ ```
1295
+
1296
+ ### Q4: How to test components using IOC?
1297
+
1298
+ **A:** Provide mock IOC:
1299
+
1300
+ ```typescript
1301
+ const mockIOC = (identifier: string) => {
1302
+ if (identifier === 'UserServiceInterface') {
1303
+ return mockUserService;
1304
+ }
1305
+ // ... other services
1306
+ };
1307
+
1308
+ render(
1309
+ <IOCProvider value={mockIOC}>
1310
+ <UserProfile />
1311
+ </IOCProvider>
1312
+ );
1313
+ ```
1314
+
1315
+ ### Q5: What's the difference between IOC and Context?
1316
+
1317
+ **A:**
1318
+
1319
+ | Feature | React Context | IOC Container |
1320
+ | ------------------------- | ------------------------- | --------------------- |
1321
+ | **Scope** | React component tree | Global |
1322
+ | **Dependency Management** | ❌ None | ✅ Yes |
1323
+ | **Lifecycle** | Component lifecycle | Application lifecycle |
1324
+ | **Testing** | ⚠️ Need Provider | ✅ Easy to mock |
1325
+ | **Type Safety** | ⚠️ Need manual definition | ✅ Auto-inference |
1326
+
1327
+ **Recommendation:**
1328
+
1329
+ - Use IOC to manage services (logic)
1330
+ - Use Context to manage UI state
1331
+
1332
+ ---
1333
+
1334
+ ## 📚 Related Documentation
1335
+
1336
+ - [Project Architecture Design](./index.md) - Understand overall architecture
1337
+ - [Bootstrap Initializer](./bootstrap.md) - IOC in Bootstrap application
1338
+ - [Environment Variable Management](./env.md) - AppConfig injection
1339
+ - [Store State Management](./store.md) - How application layer notifies UI layer (IOC + Store)
1340
+ - [Testing Guide](./test-guide.md) - Detailed testing strategies
1341
+
1342
+ ---
1343
+
1344
+ ## 🎉 Summary
1345
+
1346
+ Core value of IOC container:
1347
+
1348
+ 1. **UI Separation** 🎨 - UI is UI, logic is logic
1349
+ 2. **Testability** 🧪 - Logic can be tested independently, UI can be tested independently, and also in combination
1350
+ 3. **Interfaces Required** 🔌 - Even with only one implementation, still need interface (for testing)
1351
+ 4. **Complete Separation** 🏗️ - Even simple components, still separate (for future)
1352
+ 5. **Dependency Management** 📦 - Container uniformly manages all dependencies
1353
+ 6. **Decoupling** 🔗 - Components don't depend on concrete implementations
1354
+ 7. **Easy to Maintain** 🛠️ - Clear dependency relationships
1355
+ 8. **Easy to Extend** 🚀 - Easy to add new features
1356
+
1357
+ **Remember two core principles:**
415
1358
 
416
- The role of IOC container in the project:
1359
+ 1. **UI is UI, logic is logic, they must be separated!**
1360
+ 2. **Even with only one implementation, still need interface; even if component is simple, still separate!**
417
1361
 
418
- 1. **Dependency Management**: Unified management of all service dependencies
419
- 2. **Type Safety**: Compile-time type checking through TypeScript
420
- 3. **Testability**: Easy to unit test and mock
421
- 4. **Maintainability**: Clear dependency relationships, easy to understand and modify
422
- 5. **Extensibility**: Easy to add new services and dependencies
1362
+ ---
423
1363
 
424
- Through proper use of the IOC container, code becomes more modular, testable, and maintainable.
1364
+ **Feedback:**
1365
+ If you have any questions or suggestions about the IOC container, please discuss in the team channel or submit an Issue.