@nutin/cli 1.3.0 → 2.0.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 (284) hide show
  1. package/CHANGELOG.md +122 -8
  2. package/README.md +21 -37
  3. package/bin/add-feature.mjs +9 -0
  4. package/bin/cli.mjs +8 -0
  5. package/bin/create-app.mjs +5 -4
  6. package/bin/update-app.mjs +9 -0
  7. package/lib/index.mjs +81 -30
  8. package/lib/src/add-feature/feature-adder.mjs +83 -0
  9. package/lib/src/add-feature/feature-context-builder.mjs +25 -0
  10. package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
  11. package/lib/src/add-feature/package-json-updater.mjs +49 -0
  12. package/lib/src/common/feature-registry.mjs +11 -0
  13. package/lib/src/common/file-generator.mjs +159 -0
  14. package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
  15. package/lib/src/common/package-data.mjs +10 -0
  16. package/lib/src/common/package-json-helper.mjs +103 -0
  17. package/lib/src/common/project-meta.mjs +54 -0
  18. package/lib/src/create-app/context-builder.mjs +21 -0
  19. package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
  20. package/lib/src/create-app/json-manager.mjs +73 -0
  21. package/lib/src/create-app/new-app-prompt.mjs +51 -0
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +15 -13
  23. package/lib/src/create-app/success-message.mjs +14 -0
  24. package/lib/src/update-app/conflict-report-writer.mjs +31 -0
  25. package/lib/src/update-app/fetch-old-templates.mjs +61 -0
  26. package/lib/src/update-app/project-updater.mjs +136 -0
  27. package/lib/src/update-app/template-differ.mjs +124 -0
  28. package/lib/src/update-app/update-context-builder.mjs +25 -0
  29. package/lib/src/update-app/update-summary-printer.mjs +22 -0
  30. package/lib/src/update-app/version-compare.mjs +12 -0
  31. package/lib/src/utils/print.mjs +36 -0
  32. package/lib/src/utils/promise-exec-alias.mjs +4 -0
  33. package/package.json +17 -12
  34. package/templates/base/.gitignore.hbs +6 -1
  35. package/templates/base/AGENTS.md.hbs +169 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +227 -0
  37. package/templates/base/README.md.hbs +8 -17
  38. package/templates/base/config/languages.json.hbs +6 -0
  39. package/templates/base/config/seo.json.hbs +12 -0
  40. package/templates/base/nutin.config.js.hbs +51 -0
  41. package/templates/base/src/app/components/index.ts.hbs +1 -2
  42. package/templates/base/src/app/globals.d.ts.hbs +3 -15
  43. package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
  44. package/templates/base/src/app/main.ts.hbs +7 -26
  45. package/templates/base/src/app/services/index.ts.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
  47. package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
  48. package/templates/base/src/app/views/index.ts.hbs +1 -0
  49. package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
  50. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
  51. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
  52. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +81 -31
  53. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
  54. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +38 -23
  55. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +41 -1
  59. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +13 -14
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  65. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  66. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  67. package/templates/base/src/core/base-classes/component/component.ts.hbs +27 -40
  68. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
  69. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -0
  70. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
  71. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  72. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  73. package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
  74. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  75. package/templates/base/src/core/config.ts.hbs +7 -0
  76. package/templates/base/src/core/index.ts.hbs +0 -3
  77. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  78. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  79. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  80. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  81. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  83. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  85. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  86. package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
  87. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  89. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  90. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
  91. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  92. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +60 -9
  93. package/templates/base/src/core/services/index.ts.hbs +5 -6
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  95. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  96. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  97. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +42 -30
  100. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  102. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +13 -70
  103. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  104. package/templates/base/src/core/services/router/router.ts.hbs +40 -9
  105. package/templates/base/src/index.html.hbs +2 -6
  106. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  107. package/templates/base/src/styles/base/_variables.scss.hbs +1 -85
  108. package/templates/base/src/styles/main.scss.hbs +1 -18
  109. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  110. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  111. package/templates/base/tools/builder/builder.js.hbs +24 -26
  112. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  114. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  115. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  116. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  117. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  118. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  119. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  120. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  121. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  122. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  123. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  124. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  125. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  126. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  127. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  129. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  130. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  131. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  132. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  133. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  136. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  137. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  138. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  139. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  140. package/templates/base/tools/dev/serve.js.hbs +11 -1
  141. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  142. package/templates/base/tools/generator/generator.js.hbs +64 -47
  143. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  144. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  145. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  146. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  147. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  148. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  149. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  150. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  151. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  153. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  154. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  155. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  156. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  157. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  158. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  159. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  164. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  165. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  166. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  168. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  169. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  170. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  171. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  172. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  173. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  175. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  176. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  177. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  178. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  179. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  180. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  181. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  182. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  185. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  186. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  187. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  188. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  189. package/templates/base/tools/utils/format.js.hbs +10 -10
  190. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  191. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  192. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  193. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  194. package/templates/base/tools/utils/index.js.hbs +1 -3
  195. package/templates/base/tools/utils/languages.js.hbs +15 -0
  196. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  197. package/templates/base/tools/utils/print.js.hbs +19 -13
  198. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  199. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  200. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  201. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  202. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  203. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  204. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  205. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  206. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  207. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  208. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  211. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  213. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  214. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  215. package/lib/src/context-builder.mjs +0 -35
  216. package/lib/src/file-generator.mjs +0 -109
  217. package/lib/src/json-generator.mjs +0 -31
  218. package/lib/src/package-manager.mjs +0 -139
  219. package/lib/src/print.mjs +0 -31
  220. package/lib/src/prompts.mjs +0 -123
  221. package/lib/src/utils.mjs +0 -18
  222. package/lib/src/version.mjs +0 -1
  223. package/templates/base/builder.config.js.hbs +0 -35
  224. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  225. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  226. package/templates/base/src/app/dom.test.js.hbs +0 -13
  227. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  228. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  229. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  230. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  231. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  232. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -85
  233. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  234. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -108
  235. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  236. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  237. package/templates/base/src/libs/index.ts.hbs +0 -4
  238. package/templates/base/src/libs/popover/popover.ts.hbs +0 -145
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -72
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  242. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  243. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  244. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  245. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  246. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  247. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  248. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  249. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  250. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  251. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  252. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  253. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  254. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  255. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  256. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  257. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  258. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  259. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  260. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -240
  261. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  262. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  263. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  264. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  265. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  266. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  267. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  268. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  269. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  270. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  271. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  272. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  273. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  274. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  275. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  276. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  277. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  278. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  279. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  280. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  281. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  282. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  283. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  284. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -1,4 +1,6 @@
1
1
  import { NavigationManager } from '#root/dist/src/core/services/router/helpers/navigation-manager.helper.js';
2
+ import { I18nService } from '#root/dist/src/core/services/index.js';
3
+ import { CONFIG } from '#root/dist/src/core/config.js';
2
4
 
3
5
  describe('NavigationManager', () => {
4
6
  it('should normalize paths by removing trailing slashes', () => {
@@ -8,12 +10,6 @@ describe('NavigationManager', () => {
8
10
  expect(NavigationManager.normalizePath('')).toBe('/');
9
11
  });
10
12
 
11
- it('should determine if pushState should be used', () => {
12
- expect(NavigationManager.shouldPushState(true, '/home', '/about')).toBe(true);
13
- expect(NavigationManager.shouldPushState(false, '/home', '/about')).toBe(false);
14
- expect(NavigationManager.shouldPushState(true, '/about', '/about')).toBe(false);
15
- });
16
-
17
13
  it('should call pushState when conditions are met', () => {
18
14
  const originalPushState = window.history.pushState;
19
15
  let called = false;
@@ -48,4 +44,151 @@ describe('NavigationManager', () => {
48
44
  const currentPath = window.location.pathname;
49
45
  expect(NavigationManager.getCurrentPath()).toBe(currentPath);
50
46
  });
47
+
48
+ it('extractLocale returns the original path untouched when CONFIG.i18n is disabled', () => {
49
+ CONFIG.i18n = false;
50
+ expect(NavigationManager.extractLocale('/fr/about')).toEqual({ locale: null, strippedPath: '/fr/about' });
51
+ });
52
+
53
+ it('extractLocale strips a recognized locale prefix when CONFIG.i18n is enabled', () => {
54
+ CONFIG.i18n = true;
55
+ I18nService['_LANGUAGES'] = ['en', 'fr'];
56
+ try {
57
+ expect(NavigationManager.extractLocale('/fr/about')).toEqual({ locale: 'fr', strippedPath: '/about' });
58
+ expect(NavigationManager.extractLocale('/fr')).toEqual({ locale: 'fr', strippedPath: '/' });
59
+ } finally {
60
+ CONFIG.i18n = false;
61
+ }
62
+ });
63
+
64
+ it('extractLocale leaves the path untouched when the first segment is not a recognized locale', () => {
65
+ CONFIG.i18n = true;
66
+ I18nService['_LANGUAGES'] = ['en', 'fr'];
67
+ try {
68
+ expect(NavigationManager.extractLocale('/about')).toEqual({ locale: null, strippedPath: '/about' });
69
+ } finally {
70
+ CONFIG.i18n = false;
71
+ }
72
+ });
73
+
74
+ it('addLocalePrefix returns the path unchanged when CONFIG.i18n is disabled', () => {
75
+ CONFIG.i18n = false;
76
+ expect(NavigationManager.addLocalePrefix('/about')).toBe('/about');
77
+ });
78
+
79
+ it('addLocalePrefix prefixes the path with the current language when CONFIG.i18n is enabled', () => {
80
+ CONFIG.i18n = true;
81
+ I18nService['_LANGUAGES'] = ['en', 'fr'];
82
+ I18nService['_currentLanguage'] = 'fr';
83
+ try {
84
+ expect(NavigationManager.addLocalePrefix('/about')).toBe('/fr/about');
85
+ expect(NavigationManager.addLocalePrefix('/')).toBe('/fr');
86
+ } finally {
87
+ CONFIG.i18n = false;
88
+ I18nService['_currentLanguage'] = 'en';
89
+ }
90
+ });
91
+
92
+ it('getCurrentLocale reads the locale from the current URL path when CONFIG.i18n is enabled', () => {
93
+ CONFIG.i18n = true;
94
+ I18nService['_LANGUAGES'] = ['en', 'fr'];
95
+ window.history.pushState({}, '', '/fr/about');
96
+ try {
97
+ expect(NavigationManager.getCurrentLocale()).toBe('fr');
98
+ } finally {
99
+ CONFIG.i18n = false;
100
+ window.history.pushState({}, '', '/');
101
+ }
102
+ });
103
+
104
+ it('getCurrentLocale returns null when CONFIG.i18n is disabled', () => {
105
+ window.history.pushState({}, '', '/fr/about');
106
+ try {
107
+ expect(NavigationManager.getCurrentLocale()).toBe(null);
108
+ } finally {
109
+ window.history.pushState({}, '', '/');
110
+ }
111
+ });
112
+
113
+ it('updateLocaleInUrl pushes a new URL when the locale-prefixed path differs from the current one', () => {
114
+ CONFIG.i18n = true;
115
+ I18nService['_LANGUAGES'] = ['en', 'fr'];
116
+ I18nService['_currentLanguage'] = 'fr';
117
+ window.history.pushState({}, '', '/about');
118
+
119
+ const originalPushState = window.history.pushState;
120
+ const calls = [];
121
+ window.history.pushState = (...args) => {
122
+ calls.push(args);
123
+ originalPushState.apply(window.history, args);
124
+ };
125
+
126
+ try {
127
+ NavigationManager.updateLocaleInUrl();
128
+ expect(calls.length).toBe(1);
129
+ expect(calls[0][2]).toBe('/fr/about');
130
+ } finally {
131
+ window.history.pushState = originalPushState;
132
+ CONFIG.i18n = false;
133
+ I18nService['_currentLanguage'] = 'en';
134
+ window.history.pushState({}, '', '/');
135
+ }
136
+ });
137
+
138
+ it('updateLocaleInUrl handles a root pathname that collapses to an empty string when stripped', () => {
139
+ CONFIG.i18n = false;
140
+ window.history.pushState({}, '', '/');
141
+
142
+ const originalPushState = window.history.pushState;
143
+ window.history.pushState = () => {};
144
+
145
+ try {
146
+ expect(() => NavigationManager.updateLocaleInUrl()).not.toThrow();
147
+ } finally {
148
+ window.history.pushState = originalPushState;
149
+ }
150
+ });
151
+
152
+ it('updateLocaleInUrl does nothing when the URL already has the correct locale prefix', () => {
153
+ CONFIG.i18n = true;
154
+ I18nService['_LANGUAGES'] = ['en', 'fr'];
155
+ I18nService['_currentLanguage'] = 'fr';
156
+ window.history.pushState({}, '', '/fr/about');
157
+
158
+ const originalPushState = window.history.pushState;
159
+ let called = false;
160
+ window.history.pushState = () => { called = true; };
161
+
162
+ try {
163
+ NavigationManager.updateLocaleInUrl();
164
+ expect(called).toBe(false);
165
+ } finally {
166
+ window.history.pushState = originalPushState;
167
+ CONFIG.i18n = false;
168
+ I18nService['_currentLanguage'] = 'en';
169
+ window.history.pushState({}, '', '/');
170
+ }
171
+ });
172
+
173
+ it('matchPattern matches a required param', () => {
174
+ expect(NavigationManager.matchPattern('/posts/:id', '/posts/123')).toEqual({ id: '123' });
175
+ });
176
+
177
+ it('matchPattern does not match when a required param segment is missing', () => {
178
+ expect(NavigationManager.matchPattern('/posts/:id', '/posts')).toBe(null);
179
+ });
180
+
181
+ it('matchPattern matches an optional param when present, and omits it when absent', () => {
182
+ expect(NavigationManager.matchPattern('/users/:id?', '/users/123')).toEqual({ id: '123' });
183
+ expect(NavigationManager.matchPattern('/users/:id?', '/users')).toEqual({});
184
+ });
185
+
186
+ it('matchPattern matches multiple params in the same pattern', () => {
187
+ expect(NavigationManager.matchPattern('/posts/:postId/comments/:commentId', '/posts/1/comments/2'))
188
+ .toEqual({ postId: '1', commentId: '2' });
189
+ });
190
+
191
+ it('matchPattern returns null when the path does not match the pattern at all', () => {
192
+ expect(NavigationManager.matchPattern('/posts/:id', '/other/123')).toBe(null);
193
+ });
51
194
  });
@@ -1,12 +1,48 @@
1
- import { {{#if i18n}}I18nService, {{/if}}View } from "../../../index.js";
1
+ import { I18nService } from "../../../index.js";
2
+ import { Language } from "../../i18n/languages.js";
3
+ import { CONFIG } from "../../../config.js";
2
4
 
3
5
  /**
4
6
  * Navigation - handles path normalization and history management
5
7
  */
6
8
  export class NavigationManager {
9
+ public static extractLocale(path: string): { locale: Language | null; strippedPath: string } {
10
+ if (!CONFIG.i18n) return { locale: null, strippedPath: path };
11
+
12
+ const segments = path.split('/').filter(Boolean);
13
+ const first = segments[0] as Language;
14
+ if (first && I18nService.languages.includes(first)) {
15
+ const rest = segments.slice(1).join('/');
16
+ return { locale: first, strippedPath: rest ? `/${rest}` : '/' };
17
+ }
18
+ return { locale: null, strippedPath: path };
19
+ }
20
+
21
+ public static addLocalePrefix(strippedPath: string): string {
22
+ if (!CONFIG.i18n) return strippedPath;
23
+
24
+ const lang = I18nService.currentLanguage;
25
+ return strippedPath === '/' ? `/${lang}` : `/${lang}${strippedPath}`;
26
+ }
27
+
28
+ public static getCurrentLocale(): Language | null {
29
+ return this.extractLocale(window.location.pathname).locale;
30
+ }
31
+
32
+ public static updateLocaleInUrl(): void {
33
+ const rawPathname = (new URL(window.location.pathname, window.location.origin).pathname || '/').replace(/\/+$/, '') || '/';
34
+ const { strippedPath } = this.extractLocale(rawPathname);
35
+ const newUrl = this.addLocalePrefix(strippedPath);
36
+ if (newUrl !== window.location.pathname) {
37
+ window.history.pushState({}, '', newUrl);
38
+ }
39
+ }
40
+
7
41
  public static normalizePath(path: string): string {
8
- const url = new URL(path, window.location.origin);
9
- return (url.pathname || '/').replace(/\/+$/, '') || '/';
42
+ const collapsed = path.replace(/\/\/+/g, '/');
43
+ const url = new URL(collapsed, window.location.origin);
44
+ const pathname = (url.pathname || '/').replace(/\/+$/, '') || '/';
45
+ return this.extractLocale(pathname).strippedPath;
10
46
  }
11
47
 
12
48
  public static updateHistory(
@@ -14,28 +50,12 @@ export class NavigationManager {
14
50
  currentPath: string,
15
51
  pushState: boolean
16
52
  ): void {
17
- if (this.shouldPushState(pushState, currentPath, normalizedPath)) {
18
- window.history.pushState({}, '', normalizedPath);
53
+ const localizedPath = this.addLocalePrefix(normalizedPath);
54
+ if (pushState && window.location.pathname !== localizedPath) {
55
+ window.history.pushState({}, '', localizedPath);
19
56
  }
20
57
  }
21
58
 
22
- public static updateMetaContent(currentView: View): void {
23
- if (!currentView.shouldUpdateMetaContent()) return;
24
-
25
- const strippedName = currentView.viewName.replace('-view', '');
26
- {{#if i18n}}
27
- const title = `${strippedName}.meta.title`;
28
- const description = `${strippedName}.meta.description`;
29
-
30
- document.title = I18nService.translate(title);
31
- document.querySelector('meta[name="description"]')
32
- ?.setAttribute('content', I18nService.translate(description));
33
- {{else}}
34
-
35
- document.title = strippedName.charAt(0).toUpperCase() + strippedName.slice(1).toLowerCase();
36
- {{/if}}
37
- }
38
-
39
59
  public static getCurrentPath(): string {
40
60
  return this.normalizePath(window.location.pathname);
41
61
  }
@@ -67,12 +87,4 @@ export class NavigationManager {
67
87
  });
68
88
  return params;
69
89
  }
70
-
71
- private static shouldPushState(
72
- pushState: boolean,
73
- currentPath: string,
74
- normalizedPath: string
75
- ): boolean {
76
- return pushState && currentPath !== normalizedPath;
77
- }
78
90
  }
@@ -29,6 +29,12 @@ describe('RouteGuardsManager', () => {
29
29
  expect(JSON.stringify(result)).toBe(JSON.stringify([guard]));
30
30
  });
31
31
 
32
+ it('should default to an empty guards array when an object route config has no guards', () => {
33
+ const config = { view: mockViewConstructor };
34
+ const result = RouteGuardsManager.getRouteGuards(config);
35
+ expect(result).toEqual([]);
36
+ });
37
+
32
38
  it('should pass all guards and return true from runGuards', async () => {
33
39
  const guards = [() => true, () => Promise.resolve(true)];
34
40
  const result = await RouteGuardsManager.runGuards(guards);
@@ -70,4 +76,25 @@ describe('RouteGuardsManager', () => {
70
76
  expect(typeof result.viewConstructor).toBe('function');
71
77
  expect(result.redirectTo).toBeUndefined();
72
78
  });
79
+
80
+ it('passes the actual route params through processRouteGuards to each guard', async () => {
81
+ const receivedParams = [];
82
+ const guard = (params) => { receivedParams.push(params); return true; };
83
+ const config = { view: mockViewConstructor, guards: [guard] };
84
+
85
+ await RouteGuardsManager.processRouteGuards(config, '/users/42', { id: '42' });
86
+
87
+ expect(receivedParams.length).toBe(1);
88
+ expect(receivedParams[0]).toEqual({ id: '42' });
89
+ });
90
+
91
+ it('defaults params to an empty object when none are passed to processRouteGuards', async () => {
92
+ const receivedParams = [];
93
+ const guard = (params) => { receivedParams.push(params); return true; };
94
+ const config = { view: mockViewConstructor, guards: [guard] };
95
+
96
+ await RouteGuardsManager.processRouteGuards(config, '/dashboard');
97
+
98
+ expect(receivedParams[0]).toEqual({});
99
+ });
73
100
  });
@@ -26,24 +26,11 @@ describe('ViewRenderManager', () => {
26
26
  return el;
27
27
  },
28
28
  destroy: () => {},
29
+ onEnter: () => {},
30
+ onExit: () => {},
29
31
  };
30
32
  };
31
33
 
32
- {{#if animation}}
33
- it('should add exit class and destroy current view on transition out', async () => {
34
- const view = mockView();
35
- const el = view.render();
36
- container.appendChild(el);
37
-
38
- // Spy on destroy
39
- const spy = spyOn(view, 'destroy');
40
-
41
- await ViewRenderManager.transitionOutCurrentView(view);
42
-
43
- expect(spy).toHaveBeenCalled();
44
- });
45
-
46
- {{/if}}
47
34
  it('should do nothing if no current view is passed to transitionOutCurrentView', async () => {
48
35
  const result = await ViewRenderManager.transitionOutCurrentView(null);
49
36
  expect(result).toBe(null);
@@ -51,7 +38,8 @@ describe('ViewRenderManager', () => {
51
38
 
52
39
  it('should emit view-unmount event on transition out', async () => {
53
40
  const emitted = [];
54
- AppEventBus.emit = (event, data) => emitted.push({ event, data });
41
+ const emitSpy = spyOn(AppEventBus, 'emit');
42
+ emitSpy.andCallFake((event, data) => emitted.push({ event, data }));
55
43
 
56
44
  const view = mockView('SettingsView');
57
45
  const el = view.render();
@@ -59,6 +47,84 @@ describe('ViewRenderManager', () => {
59
47
 
60
48
  await ViewRenderManager.transitionOutCurrentView(view);
61
49
 
62
- expect(JSON.stringify(emitted)).toContain(JSON.stringify({ event: 'view-unmount', data: 'SettingsView' }));
50
+ expect(JSON.stringify(emitted)).toContain(JSON.stringify({ event: 'view-unmount', data: { viewName: 'SettingsView' } }));
51
+ emitSpy.restore();
52
+ });
53
+
54
+ it('should call destroy() and onExit() on the current view when transitioning out', async () => {
55
+ let destroyed = false;
56
+ let exited = false;
57
+ const view = mockView('SettingsView');
58
+ view.destroy = () => { destroyed = true; };
59
+ view.onExit = () => { exited = true; };
60
+
61
+ await ViewRenderManager.transitionOutCurrentView(view);
62
+
63
+ expect(destroyed).toBe(true);
64
+ expect(exited).toBe(true);
65
+ });
66
+
67
+ it('should set route params, render, call onEnter and emit view-mount when rendering a new view', () => {
68
+ const emitted = [];
69
+ const emitSpy = spyOn(AppEventBus, 'emit');
70
+ emitSpy.andCallFake((event, data) => emitted.push({ event, data }));
71
+
72
+ const calls = [];
73
+ const viewElement = document.createElement('div');
74
+ const view = {
75
+ viewName: 'HomeView',
76
+ getElement: () => viewElement,
77
+ setRouteParams: (params) => calls.push(['setRouteParams', params]),
78
+ render: () => { calls.push(['render']); return viewElement; },
79
+ onEnter: () => calls.push(['onEnter']),
80
+ };
81
+
82
+ ViewRenderManager.renderNewView(() => view, { id: '42' });
83
+
84
+ expect(calls).toEqual([
85
+ ['setRouteParams', { id: '42' }],
86
+ ['render'],
87
+ ['onEnter'],
88
+ ]);
89
+ expect(JSON.stringify(emitted)).toContain(JSON.stringify({ event: 'view-mount', data: { viewName: 'HomeView' } }));
90
+ emitSpy.restore();
91
+ });
92
+
93
+ it('should not attempt to clear stale content when the new view has no parent element yet', () => {
94
+ const viewElement = document.createElement('div');
95
+ // Note: viewElement is intentionally never attached to the container.
96
+ const view = {
97
+ viewName: 'HomeView',
98
+ getElement: () => viewElement,
99
+ setRouteParams: () => {},
100
+ render: () => viewElement,
101
+ onEnter: () => {},
102
+ };
103
+
104
+ expect(() => ViewRenderManager.renderNewView(() => view)).not.toThrow();
105
+ });
106
+
107
+ it('should clear stale mount content before rendering the new view', () => {
108
+ const stale = document.createElement('div');
109
+ stale.className = 'stale-seo-content';
110
+ container.appendChild(stale);
111
+
112
+ const viewElement = document.createElement('div');
113
+ viewElement.className = 'mock-view';
114
+ container.appendChild(viewElement);
115
+
116
+ const view = {
117
+ viewName: 'HomeView',
118
+ getElement: () => viewElement,
119
+ setRouteParams: () => {},
120
+ render: () => viewElement,
121
+ onEnter: () => {},
122
+ };
123
+
124
+ ViewRenderManager.renderNewView(() => view);
125
+
126
+ expect(container.children.length).toBe(1);
127
+ expect(container.contains(stale)).toBe(false);
128
+ expect(container.contains(viewElement)).toBe(true);
63
129
  });
64
130
  });
@@ -1,35 +1,14 @@
1
- import { View, AppEventBus } from '../../../index.js';
1
+ import { View, Lifecycle } from '../../../index.js';
2
2
 
3
3
  /**
4
- * handles all view rendering{{#if transition}} and transition animation logic{{/if}}.
4
+ * handles all view rendering.
5
5
  */
6
6
  export class ViewRenderManager {
7
- {{#if transition}}
8
- public static renderAnimatedView(view: View): void {
9
- const container = document.getElementById('app');
10
- const el = view.render();
11
- el.classList.add('app-view-transition');
12
- container?.appendChild(el);
13
-
14
- requestAnimationFrame(() => {
15
- el.classList.add('enter-active');
16
- });
17
- }
18
-
19
- {{/if}}
20
7
  public static async transitionOutCurrentView(currentView: View | null): Promise<null> {
21
8
  if (!currentView) return null;
22
- {{#if transition}}
23
- const container = document.getElementById('app');
24
- const el = container?.firstElementChild as HTMLElement;
25
-
26
- if (el) {
27
- el.classList.add('exit-active');
28
- await this.delay(300); // match transition duration in _libs.scss
29
- }
30
- {{/if}}
31
9
  currentView.destroy();
32
- this.emitEvent('view-unmount', currentView.viewName);
10
+ currentView.onExit();
11
+ Lifecycle.viewUnmount(currentView.viewName);
33
12
  return null;
34
13
  }
35
14
 
@@ -38,61 +17,25 @@ export class ViewRenderManager {
38
17
  params: Record<string, string> = {}
39
18
  ): View {
40
19
  const view = viewConstructor();
20
+ ViewRenderManager.clearStaleMountContent(view);
41
21
 
42
22
  // Set route parameters before rendering
43
23
  view.setRouteParams(params);
44
24
 
45
- {{#if transition}}
46
- this.renderAnimatedView(view);
47
-
48
- {{else}}
49
25
  view.render();
26
+ view.onEnter();
50
27
 
51
- {{/if}}
52
-
53
- this.cleanupOptionalContent();
54
-
55
- this.emitEvent('view-mount', view.viewName);
28
+ Lifecycle.viewMount(view.viewName);
56
29
  return view;
57
30
  }
58
31
 
59
- public static cleanupOptionalContent(): void {
60
- const isEmpty = (el: HTMLElement): boolean => {
61
- const attrName = el.dataset.optional?.trim();
62
-
63
- if (attrName && attrName !== "") {
64
- const attrValue = el.getAttribute(attrName);
65
- return !attrValue || attrValue.trim() === "" || attrValue === "undefined";
66
- }
32
+ private static clearStaleMountContent(view: View): void {
33
+ const viewElement = view.getElement();
34
+ const container = viewElement.parentElement;
35
+ if (!container) return;
67
36
 
68
- if (el instanceof HTMLImageElement) {
69
- return !el.src || el.src.trim() === "";
70
- }
71
-
72
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
73
- return !el.value?.trim();
74
- }
75
-
76
- if (el instanceof HTMLMediaElement || el instanceof HTMLSourceElement) {
77
- return !el.getAttribute("src");
78
- }
79
-
80
- const content = el.textContent?.trim();
81
- return !content || content === "undefined";
82
- };
83
-
84
- document.querySelectorAll<HTMLElement>("[data-optional]").forEach(el => {
85
- if (isEmpty(el)) el.remove();
37
+ Array.from(container.childNodes).forEach((node) => {
38
+ if (node !== viewElement) node.remove();
86
39
  });
87
40
  }
88
-
89
- private static emitEvent(event: EventKey, viewName: string): void {
90
- AppEventBus.emit(event, viewName);
91
- }
92
- {{#if transition}}
93
-
94
- private static delay(ms: number): Promise<void> {
95
- return new Promise(res => setTimeout(res, ms));
96
- }
97
- {{/if}}
98
41
  }