@nutin/cli 1.3.1 → 2.1.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 (289) hide show
  1. package/CHANGELOG.md +106 -9
  2. package/README.md +17 -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 +13 -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 +142 -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 +168 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +199 -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 +44 -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 +6 -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 +2 -57
  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 +5 -38
  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 +96 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +6 -3
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +151 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +8 -2
  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 +89 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +4 -1
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  65. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  66. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  67. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  68. package/templates/base/src/core/base-classes/component/component.ts.hbs +24 -33
  69. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -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 +71 -0
  74. package/templates/base/src/core/base-classes/view/view.ts.hbs +24 -49
  75. package/templates/base/src/core/config.ts.hbs +10 -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 +187 -0
  87. package/templates/base/src/core/services/globals/globals.ts.hbs +104 -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 +73 -2
  91. package/templates/base/src/core/services/http-client/http-client.ts.hbs +4 -1
  92. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  93. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
  94. package/templates/base/src/core/services/index.ts.hbs +5 -6
  95. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  96. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  97. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  98. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +257 -6
  100. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +56 -29
  101. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  102. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  103. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
  104. package/templates/base/src/core/services/router/router.test.js.hbs +304 -2
  105. package/templates/base/src/core/services/router/router.ts.hbs +47 -12
  106. package/templates/base/src/index.html.hbs +2 -6
  107. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  108. package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
  109. package/templates/base/src/styles/main.scss.hbs +1 -18
  110. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  111. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  112. package/templates/base/tools/builder/builder.js.hbs +24 -26
  113. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  114. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  115. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  116. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  117. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  118. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  119. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  120. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  121. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  122. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  123. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  124. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  125. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  126. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  127. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  128. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  129. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  130. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  131. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  132. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  133. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  134. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  136. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  137. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  138. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  139. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  140. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  141. package/templates/base/tools/dev/serve.js.hbs +11 -1
  142. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  143. package/templates/base/tools/generator/generator.js.hbs +44 -47
  144. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  145. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  146. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  147. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  148. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  149. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  150. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  151. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +8 -0
  152. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +17 -0
  153. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  154. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  155. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  156. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  157. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  158. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  159. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +4 -3
  160. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  164. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  165. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  166. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  167. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  168. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  169. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  170. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  171. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  172. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  173. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  174. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  175. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  176. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  177. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  178. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  179. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  180. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  181. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  182. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  183. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  185. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  186. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  187. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  188. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  189. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  190. package/templates/base/tools/utils/format.js.hbs +10 -10
  191. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  192. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  193. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  194. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  195. package/templates/base/tools/utils/index.js.hbs +1 -3
  196. package/templates/base/tools/utils/languages.js.hbs +15 -0
  197. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  198. package/templates/base/tools/utils/print.js.hbs +19 -13
  199. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  200. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  201. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  202. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  203. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  204. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  205. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  206. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  207. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  208. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  209. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  211. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  212. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  213. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  214. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  215. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  216. package/lib/src/context-builder.mjs +0 -40
  217. package/lib/src/file-generator.mjs +0 -109
  218. package/lib/src/json-generator.mjs +0 -31
  219. package/lib/src/package-manager.mjs +0 -139
  220. package/lib/src/print.mjs +0 -32
  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 -36
  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 -113
  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 -131
  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/helpers/focus-trap.helper.ts.hbs +0 -197
  239. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
  240. package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
  241. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
  242. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  243. package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
  244. package/templates/base/src/styles/core/_index.scss.hbs +0 -6
  245. package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
  246. package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
  247. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  248. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  249. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  250. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  251. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  252. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  253. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  254. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  255. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  256. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  257. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  258. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  259. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  260. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  261. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  262. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  263. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  264. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  265. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
  266. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  267. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  268. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  269. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  270. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  271. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  272. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  273. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  274. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  275. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  276. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  277. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  278. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  279. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  280. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  281. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  282. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  283. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  284. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  285. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  286. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  287. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  288. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  289. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -0,0 +1,123 @@
1
+ import { Lifecycle, AppEventBus } from '#root/dist/src/core/index.js';
2
+
3
+ describe('LifecycleFacade (Lifecycle)', () => {
4
+ afterEach(() => {
5
+ AppEventBus.cleanupEventListeners();
6
+ });
7
+
8
+ it('beforeRender emits "before-render"', () => {
9
+ const emit = spyOn(AppEventBus, 'emit');
10
+ Lifecycle.beforeRender();
11
+ expect(emit).toHaveBeenCalledWith('before-render');
12
+ emit.restore();
13
+ });
14
+
15
+ it('onBeforeRender subscribes to "before-render", and the returned unsubscribe removes it', () => {
16
+ let called = 0;
17
+ const unsubscribe = Lifecycle.onBeforeRender(() => called++);
18
+
19
+ Lifecycle.beforeRender();
20
+ expect(called).toBe(1);
21
+
22
+ unsubscribe();
23
+ Lifecycle.beforeRender();
24
+ expect(called).toBe(1);
25
+ });
26
+
27
+ it('afterRender emits "after-render"', () => {
28
+ const emit = spyOn(AppEventBus, 'emit');
29
+ Lifecycle.afterRender();
30
+ expect(emit).toHaveBeenCalledWith('after-render');
31
+ emit.restore();
32
+ });
33
+
34
+ it('onAfterRender subscribes to "after-render", and the returned unsubscribe removes it', () => {
35
+ let called = 0;
36
+ const unsubscribe = Lifecycle.onAfterRender(() => called++);
37
+
38
+ Lifecycle.afterRender();
39
+ expect(called).toBe(1);
40
+
41
+ unsubscribe();
42
+ Lifecycle.afterRender();
43
+ expect(called).toBe(1);
44
+ });
45
+
46
+ it('beforeDestroy emits "before-destroy"', () => {
47
+ const emit = spyOn(AppEventBus, 'emit');
48
+ Lifecycle.beforeDestroy();
49
+ expect(emit).toHaveBeenCalledWith('before-destroy');
50
+ emit.restore();
51
+ });
52
+
53
+ it('onBeforeDestroy subscribes to "before-destroy", and the returned unsubscribe removes it', () => {
54
+ let called = 0;
55
+ const unsubscribe = Lifecycle.onBeforeDestroy(() => called++);
56
+
57
+ Lifecycle.beforeDestroy();
58
+ expect(called).toBe(1);
59
+
60
+ unsubscribe();
61
+ Lifecycle.beforeDestroy();
62
+ expect(called).toBe(1);
63
+ });
64
+
65
+ it('afterDestroy emits "after-destroy"', () => {
66
+ const emit = spyOn(AppEventBus, 'emit');
67
+ Lifecycle.afterDestroy();
68
+ expect(emit).toHaveBeenCalledWith('after-destroy');
69
+ emit.restore();
70
+ });
71
+
72
+ it('onAfterDestroy subscribes to "after-destroy", and the returned unsubscribe removes it', () => {
73
+ let called = 0;
74
+ const unsubscribe = Lifecycle.onAfterDestroy(() => called++);
75
+
76
+ Lifecycle.afterDestroy();
77
+ expect(called).toBe(1);
78
+
79
+ unsubscribe();
80
+ Lifecycle.afterDestroy();
81
+ expect(called).toBe(1);
82
+ });
83
+
84
+ it('viewMount emits "view-mount" with { viewName }', () => {
85
+ const emit = spyOn(AppEventBus, 'emit');
86
+ Lifecycle.viewMount('home');
87
+ expect(emit).toHaveBeenCalledWith('view-mount', { viewName: 'home' });
88
+ emit.restore();
89
+ });
90
+
91
+ it('onViewMount subscribes to "view-mount", and the returned unsubscribe removes it', () => {
92
+ let received = null;
93
+ const unsubscribe = Lifecycle.onViewMount((data) => { received = data; });
94
+
95
+ Lifecycle.viewMount('home');
96
+ expect(received).toEqual({ viewName: 'home' });
97
+
98
+ unsubscribe();
99
+ received = null;
100
+ Lifecycle.viewMount('other');
101
+ expect(received).toBe(null);
102
+ });
103
+
104
+ it('viewUnmount emits "view-unmount" with { viewName }', () => {
105
+ const emit = spyOn(AppEventBus, 'emit');
106
+ Lifecycle.viewUnmount('home');
107
+ expect(emit).toHaveBeenCalledWith('view-unmount', { viewName: 'home' });
108
+ emit.restore();
109
+ });
110
+
111
+ it('onViewUnmount subscribes to "view-unmount", and the returned unsubscribe removes it', () => {
112
+ let received = null;
113
+ const unsubscribe = Lifecycle.onViewUnmount((data) => { received = data; });
114
+
115
+ Lifecycle.viewUnmount('home');
116
+ expect(received).toEqual({ viewName: 'home' });
117
+
118
+ unsubscribe();
119
+ received = null;
120
+ Lifecycle.viewUnmount('other');
121
+ expect(received).toBe(null);
122
+ });
123
+ });
@@ -0,0 +1,61 @@
1
+ import { AppEventBus, IEventBus } from "./event-bus.js";
2
+
3
+ class LifecycleFacade {
4
+ constructor(private bus: IEventBus) {}
5
+
6
+ beforeRender(): void {
7
+ this.bus.emit('before-render');
8
+ }
9
+
10
+ onBeforeRender(callback: () => void): () => void {
11
+ this.bus.subscribe('before-render', callback);
12
+ return () => this.bus.off('before-render', callback);
13
+ }
14
+
15
+ afterRender(): void {
16
+ this.bus.emit('after-render');
17
+ }
18
+
19
+ onAfterRender(callback: () => void): () => void {
20
+ this.bus.subscribe('after-render', callback);
21
+ return () => this.bus.off('after-render', callback);
22
+ }
23
+
24
+ beforeDestroy(): void {
25
+ this.bus.emit('before-destroy');
26
+ }
27
+
28
+ onBeforeDestroy(callback: () => void): () => void {
29
+ this.bus.subscribe('before-destroy', callback);
30
+ return () => this.bus.off('before-destroy', callback);
31
+ }
32
+
33
+ afterDestroy(): void {
34
+ this.bus.emit('after-destroy');
35
+ }
36
+
37
+ onAfterDestroy(callback: () => void): () => void {
38
+ this.bus.subscribe('after-destroy', callback);
39
+ return () => this.bus.off('after-destroy', callback);
40
+ }
41
+
42
+ viewMount(viewName: string): void {
43
+ this.bus.emit('view-mount', {viewName});
44
+ }
45
+
46
+ onViewMount(callback: (data: { viewName: string }) => void): () => void {
47
+ this.bus.subscribe('view-mount', callback);
48
+ return () => this.bus.off('view-mount', callback);
49
+ }
50
+
51
+ viewUnmount(viewName: string): void {
52
+ this.bus.emit('view-unmount', { viewName });
53
+ }
54
+
55
+ onViewUnmount(callback: (data: { viewName: string }) => void): () => void {
56
+ this.bus.subscribe('view-unmount', callback);
57
+ return () => this.bus.off('view-unmount', callback);
58
+ }
59
+ }
60
+
61
+ export const Lifecycle = new LifecycleFacade(AppEventBus);
@@ -0,0 +1,46 @@
1
+ import { Navigation, AppEventBus } from '#root/dist/src/core/index.js';
2
+
3
+ describe('NavigationFacade (Navigation)', () => {
4
+ afterEach(() => {
5
+ AppEventBus.cleanupEventListeners();
6
+ });
7
+
8
+ it('navigateTo emits "navigate" with { path }', () => {
9
+ const emit = spyOn(AppEventBus, 'emit');
10
+ Navigation.navigateTo('/about');
11
+ expect(emit).toHaveBeenCalledWith('navigate', { path: '/about' });
12
+ emit.restore();
13
+ });
14
+
15
+ it('onNavigate subscribes to "navigate", and the returned unsubscribe removes it', () => {
16
+ let received = null;
17
+ const unsubscribe = Navigation.onNavigate((data) => { received = data; });
18
+
19
+ Navigation.navigateTo('/about');
20
+ expect(received).toEqual({ path: '/about' });
21
+
22
+ unsubscribe();
23
+ received = null;
24
+ Navigation.navigateTo('/contact');
25
+ expect(received).toBe(null);
26
+ });
27
+
28
+ it('reload emits "reload"', () => {
29
+ const emit = spyOn(AppEventBus, 'emit');
30
+ Navigation.reload();
31
+ expect(emit).toHaveBeenCalledWith('reload');
32
+ emit.restore();
33
+ });
34
+
35
+ it('onReload subscribes to "reload", and the returned unsubscribe removes it', () => {
36
+ let called = 0;
37
+ const unsubscribe = Navigation.onReload(() => called++);
38
+
39
+ Navigation.reload();
40
+ expect(called).toBe(1);
41
+
42
+ unsubscribe();
43
+ Navigation.reload();
44
+ expect(called).toBe(1);
45
+ });
46
+ });
@@ -0,0 +1,25 @@
1
+ import { AppEventBus, IEventBus } from "./event-bus.js";
2
+
3
+ class NavigationFacade {
4
+ constructor(private bus: IEventBus) {}
5
+
6
+ navigateTo(path: string): void {
7
+ this.bus.emit('navigate', { path });
8
+ }
9
+
10
+ onNavigate(callback: (data: { path: string }) => void): () => void {
11
+ this.bus.subscribe('navigate', callback);
12
+ return () => this.bus.off('navigate', callback);
13
+ }
14
+
15
+ reload(): void {
16
+ this.bus.emit('reload');
17
+ }
18
+
19
+ onReload(callback: () => void): () => void {
20
+ this.bus.subscribe('reload', callback);
21
+ return () => this.bus.off('reload', callback);
22
+ }
23
+ }
24
+
25
+ export const Navigation = new NavigationFacade(AppEventBus);
@@ -0,0 +1,187 @@
1
+ import { registerGlobals, hideGlobals, revealGlobals, Globals } from '#root/dist/src/core/services/globals/globals.js';
2
+
3
+ function makeComponent(tag = 'div') {
4
+ return class {
5
+ constructor(mountTarget) {
6
+ this.mountTarget = mountTarget;
7
+ }
8
+ render() {
9
+ return document.createElement(tag);
10
+ }
11
+ };
12
+ }
13
+
14
+ describe('globals (registerGlobals)', () => {
15
+ afterEach(() => {
16
+ // registerGlobals mounts directly onto document.body (a sibling of the
17
+ // pre-existing <main id="app">), so only strip what each test added.
18
+ [...document.body.children].forEach((el) => {
19
+ if (el.id !== 'app') el.remove();
20
+ });
21
+ // Globals is a process-wide singleton (like every other Service) - drop
22
+ // its registered-id state so a later test's ids can't collide with an
23
+ // earlier one's.
24
+ Globals.testingResetAll();
25
+ });
26
+
27
+ it('is defined', () => {
28
+ expect(registerGlobals).toBeDefined();
29
+ });
30
+
31
+ it('touches nothing when called with no options', () => {
32
+ registerGlobals({});
33
+ expect(document.body.children.length).toBe(1); // just #app
34
+ });
35
+
36
+ it('prepends a single "before" entry ahead of the existing #app, stamping its id', () => {
37
+ registerGlobals({ before: [{ component: makeComponent('header'), id: 'header' }] });
38
+ expect(document.body.firstElementChild.tagName).toBe('HEADER');
39
+ expect(document.body.firstElementChild.id).toBe('header');
40
+ expect(document.body.children[1].id).toBe('app');
41
+ });
42
+
43
+ it('appends a single "after" entry behind the existing #app, stamping its id', () => {
44
+ registerGlobals({ after: [{ component: makeComponent('footer'), id: 'footer' }] });
45
+ expect(document.body.lastElementChild.tagName).toBe('FOOTER');
46
+ expect(document.body.lastElementChild.id).toBe('footer');
47
+ });
48
+
49
+ it('mounts multiple "before" entries in the given order (not reversed)', () => {
50
+ registerGlobals({
51
+ before: [
52
+ { component: makeComponent('div'), id: 'first' },
53
+ { component: makeComponent('div'), id: 'second' },
54
+ ],
55
+ });
56
+ const ids = [...document.body.children].filter((el) => el.id !== 'app').map((el) => el.id);
57
+ expect(ids).toEqual(['first', 'second']);
58
+ });
59
+
60
+ it('mounts multiple "after" entries in the given order (not reversed)', () => {
61
+ registerGlobals({
62
+ after: [
63
+ { component: makeComponent('div'), id: 'first' },
64
+ { component: makeComponent('div'), id: 'second' },
65
+ ],
66
+ });
67
+ const ids = [...document.body.children].filter((el) => el.id !== 'app').map((el) => el.id);
68
+ expect(ids).toEqual(['first', 'second']);
69
+ });
70
+
71
+ it('passes the same mountTarget value through to every constructed component', () => {
72
+ const seen = [];
73
+ class TrackingComponent {
74
+ constructor(mountTarget) {
75
+ seen.push(mountTarget);
76
+ }
77
+ render() {
78
+ return document.createElement('div');
79
+ }
80
+ }
81
+ registerGlobals({
82
+ before: [{ component: TrackingComponent, id: 'a' }],
83
+ after: [{ component: TrackingComponent, id: 'b' }],
84
+ });
85
+ expect(seen).toEqual(['body', 'body']);
86
+ });
87
+
88
+ it('registering a duplicate id warns and skips the second mount', () => {
89
+ const warnSpy = spyOn(console, 'warn');
90
+ warnSpy.andCallFake(() => {});
91
+
92
+ registerGlobals({ before: [{ component: makeComponent('header'), id: 'dup' }] });
93
+ registerGlobals({ after: [{ component: makeComponent('footer'), id: 'dup' }] });
94
+
95
+ const matches = [...document.body.children].filter((el) => el.id === 'dup');
96
+ expect(matches.length).toBe(1);
97
+ expect(matches[0].tagName).toBe('HEADER');
98
+ expect(warnSpy.callCount).toBe(1);
99
+
100
+ warnSpy.restore();
101
+ });
102
+ });
103
+
104
+ describe('globals (hideGlobals/revealGlobals)', () => {
105
+ afterEach(() => {
106
+ [...document.body.children].forEach((el) => {
107
+ if (el.id !== 'app') el.remove();
108
+ });
109
+ // Globals is a process-wide singleton - drop its captured-display state so
110
+ // a later test's ids can't inherit an earlier test's original display value.
111
+ Globals.testingResetAll();
112
+ });
113
+
114
+ it('toggle display for every given id that is present', () => {
115
+ const header = document.createElement('div');
116
+ header.id = 'header';
117
+ document.body.appendChild(header);
118
+ const footer = document.createElement('div');
119
+ footer.id = 'footer';
120
+ document.body.appendChild(footer);
121
+
122
+ hideGlobals(['header', 'footer']);
123
+ expect(header.style.display).toBe('none');
124
+ expect(footer.style.display).toBe('none');
125
+
126
+ revealGlobals(['header', 'footer']);
127
+ expect(header.style.display).toBe('block');
128
+ expect(footer.style.display).toBe('block');
129
+ });
130
+
131
+ it('only touch the display property, leaving other inline styles intact', () => {
132
+ const header = document.createElement('div');
133
+ header.id = 'header';
134
+ header.style.cssText = 'background: coral; padding: 8px';
135
+ document.body.appendChild(header);
136
+
137
+ hideGlobals(['header']);
138
+ expect(header.style.display).toBe('none');
139
+ expect(header.style.background).toBe('coral');
140
+ expect(header.style.padding).toBe('8px');
141
+
142
+ revealGlobals(['header']);
143
+ expect(header.style.display).toBe('block');
144
+ expect(header.style.background).toBe('coral');
145
+ expect(header.style.padding).toBe('8px');
146
+ });
147
+
148
+ it('are no-ops for ids that are absent', () => {
149
+ expect(() => hideGlobals(['missing'])).not.toThrow();
150
+ expect(() => revealGlobals(['missing'])).not.toThrow();
151
+ });
152
+
153
+ it('revealGlobals restores the display value captured when hideGlobals was called', () => {
154
+ const header = document.createElement('div');
155
+ header.id = 'header';
156
+ header.style.display = 'flex';
157
+ document.body.appendChild(header);
158
+
159
+ hideGlobals(['header']);
160
+ expect(header.style.display).toBe('none');
161
+
162
+ revealGlobals(['header']);
163
+ expect(header.style.display).toBe('flex');
164
+ });
165
+
166
+ it('revealGlobals accepts an explicit display override that wins over the captured value', () => {
167
+ const header = document.createElement('div');
168
+ header.id = 'header';
169
+ header.style.display = 'flex';
170
+ document.body.appendChild(header);
171
+
172
+ hideGlobals(['header']);
173
+ revealGlobals(['header'], 'inline-block');
174
+
175
+ expect(header.style.display).toBe('inline-block');
176
+ });
177
+
178
+ it('revealGlobals falls back to "block" when there is no prior hideGlobals call for that id', () => {
179
+ const header = document.createElement('div');
180
+ header.id = 'header';
181
+ document.body.appendChild(header);
182
+
183
+ revealGlobals(['header']);
184
+
185
+ expect(header.style.display).toBe('block');
186
+ });
187
+ });
@@ -0,0 +1,104 @@
1
+ import { Service } from '../../base-classes/index.js';
2
+
3
+ export interface GlobalMountable {
4
+ render(): HTMLElement;
5
+ destroy?(): void;
6
+ }
7
+
8
+ export interface GlobalConfig<T extends GlobalMountable = GlobalMountable> {
9
+ /** The component's own class — constructed internally as `new component(mountTarget)`. */
10
+ component: new (mountTarget: HTMLElement) => T;
11
+ /** Stamped onto the mounted root element; pass this same id to `hideGlobals`/`revealGlobals`. */
12
+ id: string;
13
+ }
14
+
15
+ export interface RegisterGlobalsOptions {
16
+ /** Rendered and prepended to `<body>`, in the given order (e.g. a header). */
17
+ before?: GlobalConfig[];
18
+ /** Rendered and appended to `<body>`, in the given order (e.g. a footer). */
19
+ after?: GlobalConfig[];
20
+ }
21
+
22
+ export class Globals extends Service<Globals> {
23
+ private _mounted: Record<string, GlobalMountable> = {};
24
+ private _originalDisplay: Record<string, string> = {};
25
+
26
+ constructor() { super(); }
27
+
28
+ public register({ before = [], after = [] }: RegisterGlobalsOptions): void {
29
+ // Component subclasses generated via `nutin generate component` type their
30
+ // mountTarget param as HTMLElement, but DomHelper.mountElement only
31
+ // *appends* (rather than replacing) the target when mountTarget is a string
32
+ // selector. This cast satisfies the stricter constructor type while still
33
+ // passing the string 'body' through at runtime, so components append into
34
+ // <body> instead of replacing it.
35
+ const mountTarget = 'body' as unknown as HTMLElement;
36
+ const isElement = (el: HTMLElement | null): el is HTMLElement => el !== null;
37
+
38
+ const mount = ({ component: Component, id }: GlobalConfig): HTMLElement | null => {
39
+ if (this._mounted[id]) {
40
+ console.warn(`Global "${id}" is already registered - skipping.`);
41
+ return null;
42
+ }
43
+ const instance = new Component(mountTarget);
44
+ const element = instance.render();
45
+ element.id = id;
46
+ this._mounted[id] = instance;
47
+ return element;
48
+ };
49
+
50
+ document.body.prepend(...before.map(mount).filter(isElement));
51
+ document.body.append(...after.map(mount).filter(isElement));
52
+ }
53
+
54
+ public hide(globalIds: string[]): void {
55
+ globalIds.forEach((id) => {
56
+ const el = document.getElementById(id) as HTMLElement | null;
57
+ if (!el) return;
58
+ if (!(id in this._originalDisplay)) {
59
+ this._originalDisplay[id] = el.style.display || getComputedStyle(el).display;
60
+ }
61
+ el.style.display = 'none';
62
+ });
63
+ }
64
+
65
+ public reveal(globalIds: string[], display?: string): void {
66
+ globalIds.forEach((id) => {
67
+ const el = document.getElementById(id) as HTMLElement | null;
68
+ if (!el) return;
69
+ el.style.display = display ?? this._originalDisplay[id] ?? 'block';
70
+ });
71
+ }
72
+
73
+ protected onDestroy(): void {
74
+ Object.values(this._mounted).forEach((instance) => instance.destroy?.());
75
+ this._mounted = {};
76
+ this._originalDisplay = {};
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Mounts components outside the router's render target (`<main id="app">`),
82
+ * e.g. a header/footer that should persist across every route. Each entry's
83
+ * `component` is constructed internally — callers never see or handle
84
+ * `mountTarget` themselves — and its rendered root element is given the
85
+ * entry's `id`, so `hideGlobals(ids)`/`revealGlobals(ids)` can target it
86
+ * later by that same id. Registering an `id` that's already registered logs
87
+ * a `console.warn` and skips it.
88
+ */
89
+ export const registerGlobals = (options: RegisterGlobalsOptions): void => {
90
+ Globals.getInstance().register(options);
91
+ };
92
+
93
+ /** Hide the elements with the given ids (as registered via `registerGlobals`). */
94
+ export const hideGlobals = (globalIds: string[]): void => {
95
+ Globals.getInstance().hide(globalIds);
96
+ };
97
+
98
+ /**
99
+ * Reveal the elements with the given ids (as registered via `registerGlobals`), restoring
100
+ * each element's display value as captured by `hideGlobals` — pass `display` to override.
101
+ */
102
+ export const revealGlobals = (globalIds: string[], display?: string): void => {
103
+ Globals.getInstance().reveal(globalIds, display);
104
+ };
@@ -75,6 +75,11 @@ describe('HttpBuilder', () => {
75
75
  expect(url.search).toBe('?page=1&limit=10');
76
76
  });
77
77
 
78
+ it('buildRequestUrl leaves an existing query in the endpoint untouched when no new params are passed', () => {
79
+ const url = HttpBuilder.buildRequestUrl('https://api.example.com?page=1');
80
+ expect(url.search).toBe('?page=1');
81
+ });
82
+
78
83
  // Tests for buildRequestOptions
79
84
  it('buildRequestOptions sets GET without body', () => {
80
85
  const options = HttpBuilder.buildRequestOptions(
@@ -106,4 +111,24 @@ describe('HttpBuilder', () => {
106
111
  expect(options.body).toBeUndefined();
107
112
  expect(options.headers).toBeUndefined();
108
113
  });
114
+
115
+ it('buildRequestOptions sets PUT with body', () => {
116
+ const data = { id: 1, name: 'Updated' };
117
+ const options = HttpBuilder.buildRequestOptions('PUT', data, {}, mockAbortSignal);
118
+ expect(options.method).toBe('PUT');
119
+ expect(options.body).toBe(JSON.stringify(data));
120
+ });
121
+
122
+ it('buildRequestOptions sets PATCH with body', () => {
123
+ const data = { name: 'Patched' };
124
+ const options = HttpBuilder.buildRequestOptions('PATCH', data, {}, mockAbortSignal);
125
+ expect(options.method).toBe('PATCH');
126
+ expect(options.body).toBe(JSON.stringify(data));
127
+ });
128
+
129
+ it('buildRequestOptions sets DELETE without body', () => {
130
+ const options = HttpBuilder.buildRequestOptions('DELETE', undefined, {}, mockAbortSignal);
131
+ expect(options.method).toBe('DELETE');
132
+ expect(options.body).toBeUndefined();
133
+ });
109
134
  })
@@ -55,6 +55,26 @@ describe('HttpManager', () => {
55
55
  expect(thrown).toBe(true);
56
56
  });
57
57
 
58
+ it('should throw HttpError with a null response body when the error body is not valid JSON', async () => {
59
+ const mockResponse = new Response('plain text failure', {
60
+ status: 500,
61
+ statusText: 'Internal Server Error',
62
+ headers: { 'Content-Type': 'text/plain' }
63
+ });
64
+
65
+ let thrown = false;
66
+ try {
67
+ await HttpManager.validateResponse(mockResponse);
68
+ } catch (e) {
69
+ thrown = true;
70
+ expect(e).toBeInstanceOf(HttpError);
71
+ expect(e.status).toBe(500);
72
+ expect(e.statusText).toBe('Internal Server Error');
73
+ expect(e.response).toBe(null);
74
+ }
75
+ expect(thrown).toBe(true);
76
+ });
77
+
58
78
  it('should detect JSON content types', () => {
59
79
  expect(HttpManager.isJsonResponse('application/json')).toBe(true);
60
80
  expect(HttpManager.isJsonResponse('application/json; charset=utf-8')).toBe(true);