@nutin/cli 1.3.1 → 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 (287) hide show
  1. package/CHANGELOG.md +89 -9
  2. package/README.md +20 -35
  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 +76 -25
  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/{prompts.mjs → create-app/new-app-prompt.mjs} +6 -6
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +10 -8
  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 -19
  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 +68 -35
  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 +9 -8
  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 +7 -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 +2 -2
  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 +24 -33
  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/data-binding.helper.test.js.hbs +75 -0
  70. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  71. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  72. package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
  73. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  74. package/templates/base/src/core/config.ts.hbs +7 -0
  75. package/templates/base/src/core/index.ts.hbs +0 -3
  76. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  77. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  78. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  79. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  80. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  81. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  83. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  85. package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
  86. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
  87. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  89. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
  90. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  91. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
  92. package/templates/base/src/core/services/index.ts.hbs +5 -6
  93. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  95. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  96. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  97. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +40 -29
  99. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  100. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
  102. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  103. package/templates/base/src/core/services/router/router.ts.hbs +40 -8
  104. package/templates/base/src/index.html.hbs +2 -6
  105. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  106. package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
  107. package/templates/base/src/styles/main.scss.hbs +1 -18
  108. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  109. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  110. package/templates/base/tools/builder/builder.js.hbs +24 -26
  111. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  112. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  114. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  115. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  116. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  117. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  118. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  119. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  120. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  121. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  122. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  123. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  124. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  125. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  126. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  127. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  129. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  130. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  131. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  132. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  133. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  136. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  137. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  138. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  139. package/templates/base/tools/dev/serve.js.hbs +11 -1
  140. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  141. package/templates/base/tools/generator/generator.js.hbs +64 -47
  142. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  143. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  144. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  145. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  146. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  147. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  148. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  149. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  150. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  151. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  153. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  154. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  155. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  156. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  157. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  158. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  159. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  164. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  165. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  166. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  168. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  169. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  170. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  171. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  172. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  173. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  175. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  176. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  177. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  178. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  179. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  180. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  181. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  182. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  185. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  186. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  187. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  188. package/templates/base/tools/utils/format.js.hbs +10 -10
  189. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  190. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  191. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  192. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  193. package/templates/base/tools/utils/index.js.hbs +1 -3
  194. package/templates/base/tools/utils/languages.js.hbs +15 -0
  195. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  196. package/templates/base/tools/utils/print.js.hbs +19 -13
  197. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  198. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  199. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  200. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  201. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  202. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  203. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  204. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  205. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  206. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  207. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  208. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  211. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  213. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  214. package/lib/src/context-builder.mjs +0 -40
  215. package/lib/src/file-generator.mjs +0 -109
  216. package/lib/src/json-generator.mjs +0 -31
  217. package/lib/src/package-manager.mjs +0 -139
  218. package/lib/src/print.mjs +0 -32
  219. package/lib/src/utils.mjs +0 -18
  220. package/lib/src/version.mjs +0 -1
  221. package/templates/base/builder.config.js.hbs +0 -36
  222. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  223. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  224. package/templates/base/src/app/dom.test.js.hbs +0 -13
  225. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  226. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  227. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  228. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  229. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  230. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -113
  231. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  232. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -131
  233. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  234. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  235. package/templates/base/src/libs/index.ts.hbs +0 -4
  236. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +0 -197
  237. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
  238. package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
  242. package/templates/base/src/styles/core/_index.scss.hbs +0 -6
  243. package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
  244. package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
  245. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  246. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  247. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  248. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  249. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  250. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  251. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  252. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  253. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  254. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  255. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  256. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  257. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  258. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  259. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  260. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  261. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  262. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  263. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
  264. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  265. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  266. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  267. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  268. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  269. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  270. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  271. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  272. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  273. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  274. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  275. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  276. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  277. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  278. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  279. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  280. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  281. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  282. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  283. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  284. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  285. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  286. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  287. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -1,8 +1,7 @@
1
- export * from './event-bus/event-bus.js';
1
+ export * from './event-bus/index.js';
2
+ export * from './globals/globals.js';
2
3
  export * from './http-client/http-client.js';
3
- export * from './router/router.js';
4
- export * from './store/store.js';
5
- export * from './pipe-registry/pipe-registry.js';
6
- {{#if i18n}}
7
4
  export * from './i18n/i18n.js';
8
- {{/if}}
5
+ export * from './pipe-registry/pipe-registry.js';
6
+ export * from './pipe-registry/pipes.js';
7
+ export * from './router/router.js';
@@ -1,7 +1,66 @@
1
1
  import { AppPipeRegistry } from '#root/dist/src/core/services/index.js';
2
+ import { registerPipes } from '#root/dist/src/core/services/pipe-registry/pipes.js';
2
3
 
3
4
  describe('PipeRegistry', () => {
4
5
  it('should be defined', () => {
5
6
  expect(AppPipeRegistry).toBeDefined();
6
- })
7
+ });
8
+
9
+ it('register() makes a pipe available via apply()', () => {
10
+ AppPipeRegistry.register('reverse-test', (value) => String(value).split('').reverse().join(''));
11
+ expect(AppPipeRegistry.apply('reverse-test', 'abc')).toBe('cba');
12
+ });
13
+
14
+ it('apply() passes extra args through to the pipe function', () => {
15
+ AppPipeRegistry.register('pad-test', (value, char) => `${char}${value}${char}`);
16
+ expect(AppPipeRegistry.apply('pad-test', 'x', ['*'])).toBe('*x*');
17
+ });
18
+
19
+ it('register() keeps the original implementation when the name is already registered', () => {
20
+ const warnSpy = spyOn(console, 'warn');
21
+ warnSpy.andCallFake(() => {});
22
+
23
+ AppPipeRegistry.register('dup-test', () => 'first');
24
+
25
+ AppPipeRegistry.register('dup-test', () => 'second');
26
+
27
+ expect(AppPipeRegistry.apply('dup-test', 'ignored')).toBe('first');
28
+
29
+ expect(warnSpy.callCount).toBe(1);
30
+ warnSpy.restore();
31
+ });
32
+
33
+ it('apply() warns and returns the raw value when the pipe is not registered', () => {
34
+ const warnSpy = spyOn(console, 'warn');
35
+ warnSpy.andCallFake(() => {});
36
+
37
+ const result = AppPipeRegistry.apply('does-not-exist', 'raw-value');
38
+
39
+ expect(result).toBe('raw-value');
40
+ expect(warnSpy.callCount).toBe(1);
41
+
42
+ warnSpy.restore();
43
+ });
44
+
45
+ it('onDestroy() clears all registered pipes', () => {
46
+ AppPipeRegistry.register('clear-test', (v) => v);
47
+ expect(AppPipeRegistry.apply('clear-test', 'x')).toBe('x');
48
+
49
+ AppPipeRegistry.onDestroy();
50
+
51
+ const warnSpy = spyOn(console, 'warn');
52
+ warnSpy.andCallFake(() => {});
53
+
54
+ expect(AppPipeRegistry.apply('clear-test', 'x')).toBe('x');
55
+ expect(warnSpy.callCount).toBe(1);
56
+
57
+ warnSpy.restore();
58
+
59
+ // AppPipeRegistry is a process-wide singleton shared by every test suite,
60
+ // so destroying it here would otherwise leak into every suite that runs
61
+ // afterward and relies on the app's pipes already being registered. Silenced
62
+ // since this can legitimately re-hit an "already exists" warning per pipe if
63
+ // some other suite re-registers them again later in the run.
64
+ silenceConsole('warn', () => registerPipes());
65
+ });
7
66
  });
@@ -9,7 +9,7 @@ class PipeRegistry extends Service<PipeRegistry> {
9
9
 
10
10
  public register(name: string, fn: PipeFunction): void {
11
11
  if(this._pipes[name]) {
12
- console.warn(`Skipping pipe "${name}": A pipe with this name already exists.`);
12
+ console.warn(`Pipe "${name}" already exists - skipping.`);
13
13
  return;
14
14
  }
15
15
  this._pipes[name] = fn;
@@ -0,0 +1,128 @@
1
+ import { registerPipes } from '#root/dist/src/core/services/pipe-registry/pipes.js';
2
+ import { AppPipeRegistry } from '#root/dist/src/core/index.js';
3
+
4
+ describe('pipes (registerPipes)', () => {
5
+ beforeAll(() => {
6
+ // AppPipeRegistry is a process-wide singleton shared by every suite in the
7
+ // run - if an earlier suite (e.g. pipe-registry.test.js) already re-registered
8
+ // the app's pipes, this call is a legitimate no-op that logs a "already exists"
9
+ // warning per pipe. Silence that expected noise rather than letting it leak.
10
+ silenceConsole('warn', () => registerPipes());
11
+ });
12
+
13
+ it('currency formats a number as USD/en-US by default', () => {
14
+ const expected = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(1234.5);
15
+ expect(AppPipeRegistry.apply('currency', 1234.5)).toBe(expected);
16
+ });
17
+
18
+ it('currency accepts a custom currency and locale via args', () => {
19
+ const expected = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(1000);
20
+ expect(AppPipeRegistry.apply('currency', 1000, ['EUR', 'de-DE'])).toBe(expected);
21
+ });
22
+
23
+ it('date formats using the "long" format by default', () => {
24
+ const value = '2024-03-15T00:00:00Z';
25
+ const expected = new Date(value).toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
26
+ expect(AppPipeRegistry.apply('date', value, ['en-US'])).toBe(expected);
27
+ });
28
+
29
+ it('date formats using the "short" format when requested', () => {
30
+ const value = '2024-03-15T00:00:00Z';
31
+ const expected = new Date(value).toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
32
+ expect(AppPipeRegistry.apply('date', value, ['en-US', 'short'])).toBe(expected);
33
+ });
34
+
35
+ it('date formats using the "time" format when requested', () => {
36
+ const value = '2024-03-15T14:30:00Z';
37
+ const expected = new Date(value).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit' });
38
+ expect(AppPipeRegistry.apply('date', value, ['en-US', 'time'])).toBe(expected);
39
+ });
40
+
41
+ it('date falls back to the "long" format for an unrecognized format name', () => {
42
+ const value = '2024-03-15T00:00:00Z';
43
+ const expected = new Date(value).toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
44
+ expect(AppPipeRegistry.apply('date', value, ['en-US', 'nonsense'])).toBe(expected);
45
+ });
46
+
47
+ it('date includes hour/minute in short/long formats when the time flag is set', () => {
48
+ const value = '2024-03-15T14:30:00Z';
49
+ const expected = new Date(value).toLocaleString('en-US', {
50
+ year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit',
51
+ });
52
+ expect(AppPipeRegistry.apply('date', value, ['en-US', 'long', true])).toBe(expected);
53
+ });
54
+
55
+ it('date warns and returns the raw stringified value for an invalid date', () => {
56
+ const warnSpy = spyOn(console, 'warn');
57
+ warnSpy.andCallFake(() => {});
58
+
59
+ const result = AppPipeRegistry.apply('date', 'not-a-date', ['en-US']);
60
+
61
+ expect(result).toBe('not-a-date');
62
+ expect(warnSpy.callCount).toBe(1);
63
+ warnSpy.restore();
64
+ });
65
+
66
+ it('number defaults to 0 decimal places', () => {
67
+ expect(AppPipeRegistry.apply('number', 3.14159)).toBe('3');
68
+ });
69
+
70
+ it('number respects an explicit decimals arg', () => {
71
+ expect(AppPipeRegistry.apply('number', 3.14159, ['2'])).toBe('3.14');
72
+ });
73
+
74
+ it('uppercase converts to upper case', () => {
75
+ expect(AppPipeRegistry.apply('uppercase', 'hello')).toBe('HELLO');
76
+ });
77
+
78
+ it('lowercase converts to lower case', () => {
79
+ expect(AppPipeRegistry.apply('lowercase', 'HELLO')).toBe('hello');
80
+ });
81
+
82
+ it('capitalize uppercases only the first letter and lowercases the rest', () => {
83
+ expect(AppPipeRegistry.apply('capitalize', 'HELLO world')).toBe('Hello world');
84
+ });
85
+
86
+ it('capitalizeAll capitalizes the first letter of every word', () => {
87
+ expect(AppPipeRegistry.apply('capitalizeAll', 'hello world')).toBe('Hello World');
88
+ });
89
+
90
+ it('capitalizeAll capitalizes after separators like comma and period', () => {
91
+ expect(AppPipeRegistry.apply('capitalizeAll', 'hello, world. goodbye')).toBe('Hello, World. Goodbye');
92
+ });
93
+
94
+ it('capitalizeAll does not capitalize a letter immediately following an apostrophe', () => {
95
+ expect(AppPipeRegistry.apply('capitalizeAll', "o'brien's house")).toBe("O'brien's House");
96
+ });
97
+
98
+ it('truncate leaves a string shorter than the given length unchanged', () => {
99
+ expect(AppPipeRegistry.apply('truncate', 'Hi', ['5'])).toBe('Hi');
100
+ });
101
+
102
+ it('truncate cuts a longer string and appends the default suffix', () => {
103
+ expect(AppPipeRegistry.apply('truncate', 'Hello World', ['5'])).toBe('Hello...');
104
+ });
105
+
106
+ it('truncate accepts a custom suffix', () => {
107
+ expect(AppPipeRegistry.apply('truncate', 'Hello World', ['5', '---'])).toBe('Hello---');
108
+ });
109
+
110
+ it('default returns the value unchanged when it is truthy', () => {
111
+ expect(AppPipeRegistry.apply('default', 'value', ['N/A'])).toBe('value');
112
+ });
113
+
114
+ it('default falls back to the given default when the value is falsy', () => {
115
+ expect(AppPipeRegistry.apply('default', '', ['N/A'])).toBe('N/A');
116
+ expect(AppPipeRegistry.apply('default', 0, ['N/A'])).toBe('N/A');
117
+ });
118
+
119
+ it('json pretty-prints a plain value', () => {
120
+ expect(AppPipeRegistry.apply('json', { a: 1 })).toBe(JSON.stringify({ a: 1 }, null, 2));
121
+ });
122
+
123
+ it('json falls back to String(value) when JSON.stringify throws (e.g. a circular reference)', () => {
124
+ const circular = {};
125
+ circular.self = circular;
126
+ expect(AppPipeRegistry.apply('json', circular)).toBe(String(circular));
127
+ });
128
+ });
@@ -1,4 +1,4 @@
1
- import { AppPipeRegistry } from "../../core/index.js";
1
+ import { AppPipeRegistry } from "./pipe-registry.js";
2
2
 
3
3
  export const registerPipes = (): void => {
4
4
  // currency
@@ -21,9 +21,12 @@ export const registerPipes = (): void => {
21
21
  time = false,
22
22
  ) => {
23
23
  const date = new Date(value);
24
+ // '' is not a valid Intl.DateTimeFormatOptions value for hour/minute, so
25
+ // omit those keys entirely rather than setting them when time is false.
26
+ const timeOptions: Intl.DateTimeFormatOptions = time ? { hour: '2-digit', minute: '2-digit' } : {};
24
27
  const formats = {
25
- short: { year: 'numeric', month: 'short', day: 'numeric', hour: time ? '2-digit' : '', minute: time ? '2-digit' : '' } as Intl.DateTimeFormatOptions,
26
- long: { year: 'numeric', month: 'long', day: 'numeric', hour: time ? '2-digit' : '', minute: time ? '2-digit' : '' } as Intl.DateTimeFormatOptions,
28
+ short: { year: 'numeric', month: 'short', day: 'numeric', ...timeOptions } as Intl.DateTimeFormatOptions,
29
+ long: { year: 'numeric', month: 'long', day: 'numeric', ...timeOptions } as Intl.DateTimeFormatOptions,
27
30
  time: { hour: '2-digit', minute: '2-digit' } as Intl.DateTimeFormatOptions
28
31
  };
29
32
 
@@ -51,10 +54,10 @@ export const registerPipes = (): void => {
51
54
  });
52
55
  AppPipeRegistry.register('capitalizeAll', (value) => {
53
56
  const str = String(value);
54
- return str.replace(/(^|[ .,"'])\s*([a-z])/g, (match, separator, char, offset) => {
57
+ return str.replace(/(^|[ .,"'])(\s*)([a-z])/g, (match, separator, whitespace, char, offset) => {
55
58
  // Don't capitalize if preceded by a single quote (apostrophe)
56
59
  if (separator === "'" && offset > 0) return match;
57
- return separator + char.toUpperCase();
60
+ return separator + whitespace + char.toUpperCase();
58
61
  });
59
62
  });
60
63
  AppPipeRegistry.register('truncate', (value, length = 50, suffix = '...') => {
@@ -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,13 +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
42
  const collapsed = path.replace(/\/\/+/g, '/');
9
43
  const url = new URL(collapsed, window.location.origin);
10
- return (url.pathname || '/').replace(/\/+$/, '') || '/';
44
+ const pathname = (url.pathname || '/').replace(/\/+$/, '') || '/';
45
+ return this.extractLocale(pathname).strippedPath;
11
46
  }
12
47
 
13
48
  public static updateHistory(
@@ -15,28 +50,12 @@ export class NavigationManager {
15
50
  currentPath: string,
16
51
  pushState: boolean
17
52
  ): void {
18
- if (this.shouldPushState(pushState, currentPath, normalizedPath)) {
19
- window.history.pushState({}, '', normalizedPath);
53
+ const localizedPath = this.addLocalePrefix(normalizedPath);
54
+ if (pushState && window.location.pathname !== localizedPath) {
55
+ window.history.pushState({}, '', localizedPath);
20
56
  }
21
57
  }
22
58
 
23
- public static updateMetaContent(currentView: View): void {
24
- if (!currentView.shouldUpdateMetaContent()) return;
25
-
26
- const strippedName = currentView.viewName.replace('-view', '');
27
- {{#if i18n}}
28
- const title = `${strippedName}.meta.title`;
29
- const description = `${strippedName}.meta.description`;
30
-
31
- document.title = I18nService.translate(title);
32
- document.querySelector('meta[name="description"]')
33
- ?.setAttribute('content', I18nService.translate(description));
34
- {{else}}
35
-
36
- document.title = strippedName.charAt(0).toUpperCase() + strippedName.slice(1).toLowerCase();
37
- {{/if}}
38
- }
39
-
40
59
  public static getCurrentPath(): string {
41
60
  return this.normalizePath(window.location.pathname);
42
61
  }
@@ -68,12 +87,4 @@ export class NavigationManager {
68
87
  });
69
88
  return params;
70
89
  }
71
-
72
- private static shouldPushState(
73
- pushState: boolean,
74
- currentPath: string,
75
- normalizedPath: string
76
- ): boolean {
77
- return pushState && currentPath !== normalizedPath;
78
- }
79
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
  });