@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,158 @@
1
+ import { BaseComponent, AppEventBus } from '#root/dist/src/core/index.js';
2
+
3
+ class TestComponent extends BaseComponent {
4
+ constructor(options = {}, hooks = {}) {
5
+ super(options);
6
+ this.hooks = hooks;
7
+ this.calls = [];
8
+ }
9
+ generateTemplate() {
10
+ this.calls.push('generateTemplate');
11
+ return this.hooks.template ?? '';
12
+ }
13
+ onBeforeRender() {
14
+ this.calls.push('onBeforeRender');
15
+ this.hooks.onBeforeRender?.(this);
16
+ }
17
+ onAfterRender() {
18
+ this.calls.push('onAfterRender');
19
+ }
20
+ onBeforeDestroy() {
21
+ this.calls.push('onBeforeDestroy');
22
+ }
23
+ onAfterDestroy() {
24
+ this.calls.push('onAfterDestroy');
25
+ }
26
+ compose() {
27
+ this.calls.push('compose');
28
+ super.compose();
29
+ }
30
+ hydrate() {
31
+ this.calls.push('hydrate');
32
+ super.hydrate();
33
+ }
34
+ autoBindEvents() {
35
+ this.calls.push('autoBindEvents');
36
+ super.autoBindEvents();
37
+ }
38
+ }
39
+
40
+ describe('BaseComponent', () => {
41
+ let app;
42
+
43
+ beforeEach(() => {
44
+ // dist/src/index.html already ships a real <main id="app">, so reuse it
45
+ // instead of appending a second element with the same id.
46
+ app = document.getElementById('app');
47
+ });
48
+
49
+ afterEach(() => {
50
+ AppEventBus.cleanupEventListeners();
51
+ app.innerHTML = '';
52
+ app = null;
53
+ });
54
+
55
+ it('mounts its element into the default "#app" target on construction', () => {
56
+ const component = new TestComponent();
57
+ expect(app.contains(component.getElement())).toBe(true);
58
+ });
59
+
60
+ it('creates the element with the given tagName', () => {
61
+ const component = new TestComponent({ tagName: 'section' });
62
+ expect(component.getElement().tagName).toBe('SECTION');
63
+ });
64
+
65
+ it('defaults registerChildren() to an empty array', () => {
66
+ const component = new TestComponent();
67
+ expect(component.registerChildren()).toEqual([]);
68
+ });
69
+
70
+ it('render() runs the full pipeline in order', () => {
71
+ const component = new TestComponent({}, { template: '<span>hi</span>' });
72
+ component.render();
73
+ expect(component.calls).toEqual([
74
+ 'onBeforeRender',
75
+ 'generateTemplate',
76
+ 'compose',
77
+ 'hydrate',
78
+ 'autoBindEvents',
79
+ 'onAfterRender',
80
+ ]);
81
+ expect(component.getElement().innerHTML).toBe('<span>hi</span>');
82
+ });
83
+
84
+ it('render() sanitizes the generated template before inserting it', () => {
85
+ const component = new TestComponent({}, { template: '<p>hi</p><script>bad()</script>' });
86
+ component.render();
87
+ expect(component.getElement().innerHTML).toBe('<p>hi</p>');
88
+ });
89
+
90
+ it('render() guards against re-entrant rendering and returns the existing element', () => {
91
+ let innerResult;
92
+ const component = new TestComponent({}, {
93
+ onBeforeRender: (self) => {
94
+ innerResult = self.render();
95
+ },
96
+ });
97
+
98
+ const outerResult = component.render();
99
+
100
+ expect(innerResult).toBe(outerResult);
101
+ expect(component.calls.filter(c => c === 'compose').length).toBe(1);
102
+ expect(component.calls.filter(c => c === 'onBeforeRender').length).toBe(1);
103
+ });
104
+
105
+ it('destroy() runs onBeforeDestroy then onAfterDestroy and removes the element from the DOM', () => {
106
+ const component = new TestComponent();
107
+ component.render();
108
+ const el = component.getElement();
109
+ const callsBeforeDestroy = component.calls.length;
110
+
111
+ component.destroy();
112
+
113
+ const destroyCalls = component.calls.slice(callsBeforeDestroy);
114
+ expect(destroyCalls).toEqual(['onBeforeDestroy', 'onAfterDestroy']);
115
+ expect(app.contains(el)).toBe(false);
116
+ });
117
+
118
+ it('listen() subscribes via AppEventBus and stops receiving events after destroy()', () => {
119
+ const component = new TestComponent();
120
+ let received = null;
121
+ component.listen('user-login', (data) => { received = data; });
122
+
123
+ AppEventBus.emit('user-login', { id: 1 });
124
+ expect(received).toEqual({ id: 1 });
125
+
126
+ component.destroy();
127
+ received = null;
128
+ AppEventBus.emit('user-login', { id: 2 });
129
+ expect(received).toBe(null);
130
+ });
131
+
132
+ it('listenToRenderEvents() triggers render() whenever one of the given events fires', () => {
133
+ const component = new TestComponent();
134
+ component.listenToRenderEvents(['user-login']);
135
+
136
+ const before = component.calls.filter(c => c === 'onBeforeRender').length;
137
+ AppEventBus.emit('user-login', {});
138
+ const after = component.calls.filter(c => c === 'onBeforeRender').length;
139
+
140
+ expect(after).toBe(before + 1);
141
+ component.destroy();
142
+ });
143
+
144
+ it('createCatalogComponents() delegates to CatalogHelper.generateCatalog scoped to the component element', () => {
145
+ const component = new TestComponent();
146
+ component.getElement().innerHTML = '<div data-catalog="list"></div>';
147
+
148
+ const configs = component.createCatalogComponents({
149
+ items: [{ id: 1 }],
150
+ selector: 'list',
151
+ elementName: 'item',
152
+ component: class {},
153
+ });
154
+
155
+ expect(configs.length).toBe(1);
156
+ component.destroy();
157
+ });
158
+ });
@@ -1,14 +1,15 @@
1
1
  import { DomHelper } from './helpers/dom.helper.js';
2
2
  import { EventHelper } from './helpers/event.helper.js';
3
- {{#if i18n}}
4
3
  import { I18nHelper } from './helpers/i18n.helper.js';
5
- {{/if}}
6
4
  import { PipeHelper } from './helpers/pipe.helper.js';
7
5
  import { ChildrenHelper } from './helpers/children.helper.js';
8
6
  import { CatalogHelper, CatalogConfig } from './helpers/catalog.helper.js';
9
- import { Component, ComponentOptions, AppEventBus } from '../../index.js';
7
+ import { Component, AppEventBus } from '../../index.js';
10
8
  import { SecurityHelper, TrustLevel } from './helpers/security.helper.js';
9
+
10
+ export { SecurityHelper, TrustLevel } from './helpers/security.helper.js';
11
11
  export { CatalogItemConfig, CatalogConfig, CatalogItemPrimitive } from './helpers/catalog.helper.js';
12
+ export { TokenHelper } from './helpers/token.helper.js';
12
13
 
13
14
  /**```typescript
14
15
  * export interface ComponentConfig {
@@ -31,58 +32,83 @@ export interface BaseComponentOptions {
31
32
 
32
33
  export abstract class BaseComponent<T extends HTMLElement = HTMLElement> {
33
34
  private _children: BaseComponent[] = [];
35
+ private _isRendering = false;
34
36
  protected element: T;
35
37
  protected eventListeners: Array<[EventTarget, string, EventListener]> = [];
36
- private _options: ComponentOptions;
38
+ private _busSubscriptions: Array<[EventKey, (data: any) => void]> = [];
39
+ protected trustLevel: TrustLevel | undefined;
37
40
 
38
41
  constructor({
39
- template = '',
40
42
  mountTarget = '#app',
41
43
  tagName = 'div',
42
44
  trustLevel
43
45
  }: BaseComponentOptions) {
44
- this.element = DomHelper.createElement<T>(tagName, template, trustLevel);
45
- this._options = { template, mountTarget, tagName, trustLevel };
46
- this.parseDataAttributes();
47
- this.autoBindEvents();
46
+ this.trustLevel = trustLevel;
47
+ this.element = DomHelper.createElement<T>(tagName, '', trustLevel);
48
48
  DomHelper.mountElement(this.element, mountTarget);
49
49
  }
50
50
 
51
- public childConfigs(): ComponentConfig[] {
51
+ public getElement(): T {
52
+ return this.element;
53
+ }
54
+
55
+ public registerChildren(): ComponentConfig[] {
52
56
  return [];
53
57
  }
54
58
 
59
+ public render(): HTMLElement {
60
+ if (this._isRendering) return this.element;
61
+ this._isRendering = true;
62
+
63
+ try {
64
+ this.onBeforeRender();
65
+ this.element.innerHTML = SecurityHelper.sanitizeTemplate(
66
+ this.generateTemplate(),
67
+ this.trustLevel
68
+ );
69
+ this.compose();
70
+ this.hydrate();
71
+ this.autoBindEvents()
72
+ this.onAfterRender();
73
+ return this.element;
74
+ } finally {
75
+ this._isRendering = false;
76
+ }
77
+ }
78
+
55
79
  public destroy(): void {
80
+ this.onBeforeDestroy();
56
81
  EventHelper.destroyEvents(this.eventListeners);
82
+ this._busSubscriptions.forEach(([event, callback]) => {
83
+ AppEventBus.off(event, callback);
84
+ });
85
+ this._busSubscriptions = [];
57
86
  ChildrenHelper.destroyChildren(this._children);
58
87
  this.element.remove();
88
+ this.onAfterDestroy();
59
89
  }
60
90
 
61
- public render(): HTMLElement {
62
- this.addChildren();
91
+ // Lifecycle hooks — override in subclasses as needed
92
+ protected onBeforeRender(): void {}
93
+ protected onAfterRender(): void {}
94
+ protected onBeforeDestroy(): void {}
95
+ protected onAfterDestroy(): void {}
96
+
97
+ protected generateTemplate(): string {
98
+ return '';
99
+ }
100
+
101
+ protected hydrate(): void {
102
+ this.parseDataAttributes();
63
103
  DomHelper.cleanupOptionalContent();
64
- return this.element;
65
104
  }
66
105
 
67
- /**
68
- * ```typescript
69
- interface CatalogConfig extends ComponentOptions {
70
- array: CatalogItemConfig[];
71
- elementName: string;
72
- elementTag?: keyof HTMLElementTagNameMap;
73
- selector: string;
74
- component: new (el: HTMLElement, data: any) => BaseComponent;
75
- };
76
- ```
77
- */
78
- protected catalogConfig(config: CatalogConfig): ComponentConfig[] {
79
- return CatalogHelper.generateCatalog(config, this.element);
106
+ protected compose(): void {
107
+ this.addChildren();
80
108
  }
81
109
 
82
110
  protected parseDataAttributes(): void {
83
- {{#if i18n}}
84
111
  I18nHelper.parseI18nAttributes(this.element);
85
- {{/if}}
86
112
  PipeHelper.parsePipeAttributes(this.element);
87
113
  }
88
114
 
@@ -95,15 +121,22 @@ interface CatalogConfig extends ComponentOptions {
95
121
  protected addChildren(): void {
96
122
  ChildrenHelper.addChildren(this, this.element, this._children);
97
123
  }
98
-
99
- protected forceRender(): void {
100
- this.element.innerHTML = SecurityHelper.sanitizeTemplate(this._options.template, this._options.trustLevel);
101
- this.render();
124
+
125
+ protected createCatalogComponents(config: CatalogConfig): ComponentConfig[] {
126
+ return CatalogHelper.generateCatalog(config, this.element);
127
+ }
128
+
129
+ protected listen<K extends EventKey>(
130
+ event: K,
131
+ callback: (data: EventMap[K]) => void
132
+ ): void {
133
+ AppEventBus.subscribe(event, callback);
134
+ this._busSubscriptions.push([event, callback]);
102
135
  }
103
136
 
104
- protected listenToRenderEvents(events: EventKey[], force: boolean = false): void {
137
+ protected listenToRenderEvents(events: EventKey[]): void {
105
138
  events.forEach((event: EventKey) => {
106
- AppEventBus.subscribe(event, () => force ? this.forceRender() : this.render());
107
- })
139
+ this.listen(event, () => this.render());
140
+ });
108
141
  }
109
142
  }
@@ -0,0 +1,124 @@
1
+ import { CatalogHelper } from '#root/dist/src/core/base-classes/base-component/helpers/catalog.helper.js';
2
+
3
+ describe('CatalogHelper', () => {
4
+ let scope;
5
+
6
+ beforeEach(() => {
7
+ scope = document.createElement('div');
8
+ document.body.appendChild(scope);
9
+ });
10
+
11
+ afterEach(() => {
12
+ scope.remove();
13
+ scope = null;
14
+ });
15
+
16
+ it('returns an empty array when config.items is missing or empty', () => {
17
+ expect(CatalogHelper.generateCatalog({ items: [], selector: 'x', elementName: 'item' }, scope)).toEqual([]);
18
+ expect(CatalogHelper.generateCatalog({ selector: 'x', elementName: 'item' }, scope)).toEqual([]);
19
+ });
20
+
21
+ it('returns an empty array when no matching [data-catalog] container exists', () => {
22
+ const configs = CatalogHelper.generateCatalog(
23
+ { items: [{ id: 1 }], selector: 'missing', elementName: 'item' },
24
+ scope
25
+ );
26
+ expect(configs).toEqual([]);
27
+ });
28
+
29
+ it('builds a wrapper + inner [data-component] element per array item inside each matching container', () => {
30
+ scope.innerHTML = '<div data-catalog="list"></div>';
31
+ CatalogHelper.generateCatalog(
32
+ { items: [{ id: 1 }, { id: 2 }], selector: 'list', elementName: 'item', component: class {} },
33
+ scope
34
+ );
35
+
36
+ const container = scope.querySelector('[data-catalog="list"]');
37
+ expect(container.children.length).toBe(2);
38
+ expect(container.children[0].dataset.index).toBe('0');
39
+ expect(container.children[0].querySelector('[data-component="item-0"]')).toBeDefined();
40
+ expect(container.children[1].dataset.index).toBe('1');
41
+ expect(container.children[1].querySelector('[data-component="item-1"]')).toBeDefined();
42
+ });
43
+
44
+ it('clears any pre-existing content in the container before building', () => {
45
+ scope.innerHTML = '<div data-catalog="list"><span class="stale"></span></div>';
46
+ CatalogHelper.generateCatalog(
47
+ { items: [{ id: 1 }], selector: 'list', elementName: 'item', component: class {} },
48
+ scope
49
+ );
50
+ expect(scope.querySelector('.stale')).toBe(null);
51
+ });
52
+
53
+ it('uses the configured elementTag for the wrapper element', () => {
54
+ scope.innerHTML = '<div data-catalog="list"></div>';
55
+ CatalogHelper.generateCatalog(
56
+ { items: [{ id: 1 }], selector: 'list', elementName: 'item', elementTag: 'li', component: class {} },
57
+ scope
58
+ );
59
+ const wrapper = scope.querySelector('[data-catalog="list"]').firstElementChild;
60
+ expect(wrapper.tagName).toBe('LI');
61
+ });
62
+
63
+ it('returns component configs whose factory constructs the configured component with merged options and an indexed item', () => {
64
+ scope.innerHTML = '<div data-catalog="list"></div>';
65
+ const calls = [];
66
+ class FakeComponent {
67
+ constructor(el, data, props) { calls.push({ el, data, props }); }
68
+ }
69
+
70
+ const configs = CatalogHelper.generateCatalog(
71
+ {
72
+ items: [{ id: 1 }],
73
+ selector: 'list',
74
+ elementName: 'item',
75
+ component: FakeComponent,
76
+ props: { a: 1 },
77
+ defaults: { b: 2 },
78
+ },
79
+ scope
80
+ );
81
+
82
+ expect(configs.length).toBe(1);
83
+ expect(configs[0].selector).toBe('item-0');
84
+
85
+ const el = scope.querySelector('[data-component="item-0"]');
86
+ configs[0].factory(el);
87
+
88
+ expect(calls[0].el).toBe(el);
89
+ expect(calls[0].data).toEqual({ id: 1, index: 0 });
90
+ expect(calls[0].props).toEqual({ a: 1, b: 2 });
91
+ });
92
+
93
+ it('skips falsy or non-HTMLElement entries returned by querySelectorAll', () => {
94
+ scope.innerHTML = '<div data-catalog="list"></div>';
95
+ const realContainer = scope.querySelector('[data-catalog="list"]');
96
+ const fakeScope = { querySelectorAll: () => [null, realContainer] };
97
+
98
+ const configs = CatalogHelper.generateCatalog(
99
+ { items: [{ id: 1 }], selector: 'list', elementName: 'item', component: class {} },
100
+ fakeScope
101
+ );
102
+
103
+ expect(configs.length).toBe(1);
104
+ });
105
+
106
+ it('wraps primitive array items as { value, index }', () => {
107
+ scope.innerHTML = '<div data-catalog="list"></div>';
108
+ const calls = [];
109
+ class FakeComponent {
110
+ constructor(_el, data) { calls.push(data); }
111
+ }
112
+
113
+ const configs = CatalogHelper.generateCatalog(
114
+ { items: ['red', 'blue'], selector: 'list', elementName: 'item', component: FakeComponent },
115
+ scope
116
+ );
117
+
118
+ configs[0].factory(scope.querySelector('[data-component="item-0"]'));
119
+ configs[1].factory(scope.querySelector('[data-component="item-1"]'));
120
+
121
+ expect(calls[0]).toEqual({ value: 'red', index: 0 });
122
+ expect(calls[1]).toEqual({ value: 'blue', index: 1 });
123
+ });
124
+ });
@@ -1,7 +1,7 @@
1
1
  import { Component, ComponentConfig, ComponentOptions } from "../../index.js";
2
2
 
3
3
  export interface CatalogConfig extends ComponentOptions {
4
- array: CatalogItemConfig[];
4
+ items: CatalogItemConfig[];
5
5
  elementName: string;
6
6
  elementTag?: keyof HTMLElementTagNameMap;
7
7
  selector: string;
@@ -24,7 +24,7 @@ export type CatalogItemConfig<T = any> =
24
24
  /**
25
25
  * ```typescript
26
26
  interface CatalogConfig extends ComponentOptions {
27
- array: CatalogItemConfig[];
27
+ items: CatalogItemConfig[];
28
28
  elementName: string;
29
29
  elementTag?: keyof HTMLElementTagNameMap;
30
30
  selector: string;
@@ -37,22 +37,23 @@ type CatalogItemConfig<T = any> =
37
37
  */
38
38
  export class CatalogHelper {
39
39
  public static generateCatalog(config: CatalogConfig, scope: Element = document.documentElement): ComponentConfig[] {
40
- if (!config.array || config.array.length < 1) return [];
40
+ if (!config.items || config.items.length < 1) return [];
41
41
 
42
42
  const componentConfigs: ComponentConfig[] = [];
43
43
  const containers = scope.querySelectorAll(`[data-catalog="${config.selector}"]`);
44
44
 
45
45
  containers.forEach((container) => {
46
- if (!container || !(container instanceof HTMLElement) || (container.firstElementChild)) return;
47
- componentConfigs.push(...this.getComponentConfigArray(config, container))
46
+ if (!container || !(container instanceof HTMLElement)) return;
47
+ componentConfigs.push(...this.getComponentConfigItems(config, container))
48
48
  })
49
49
 
50
50
  return componentConfigs;
51
51
  }
52
52
 
53
- private static getComponentConfigArray(config: CatalogConfig, container: HTMLElement): ComponentConfig[] {
53
+ private static getComponentConfigItems(config: CatalogConfig, container: HTMLElement): ComponentConfig[] {
54
+ container.innerHTML = '';
54
55
  const componentConfigs: ComponentConfig[] = [];
55
- for (let i = 0; i < config.array.length; i++) {
56
+ for (let i = 0; i < config.items.length; i++) {
56
57
  this.createElements(i, config, container);
57
58
  this.pushConfig(i, componentConfigs, config);
58
59
  }
@@ -83,7 +84,7 @@ export class CatalogHelper {
83
84
  }
84
85
 
85
86
  private static getConfigWithIndex(config: CatalogConfig, index: number): CatalogItemConfig {
86
- const item = config.array[index];
87
+ const item = config.items[index];
87
88
  return (item && typeof item === 'object')
88
89
  ? { ...(item as object), index: index }
89
90
  : { value: item, index: index };
@@ -0,0 +1,96 @@
1
+ import { ChildrenHelper } from '#root/dist/src/core/base-classes/base-component/helpers/children.helper.js';
2
+
3
+ describe('ChildrenHelper', () => {
4
+ let element;
5
+
6
+ beforeEach(() => {
7
+ element = document.createElement('div');
8
+ document.body.appendChild(element);
9
+ });
10
+
11
+ afterEach(() => {
12
+ element.remove();
13
+ element = null;
14
+ });
15
+
16
+ it('instantiates, renders and registers a child component for each matching [data-component] element', () => {
17
+ element.innerHTML = '<div data-component="widget"></div>';
18
+ const rendered = [];
19
+ const factoryCalls = [];
20
+
21
+ const component = {
22
+ registerChildren: () => [{
23
+ selector: 'widget',
24
+ factory: (el) => {
25
+ factoryCalls.push(el);
26
+ return {
27
+ render: () => rendered.push('rendered'),
28
+ destroy: () => {},
29
+ };
30
+ },
31
+ }],
32
+ };
33
+
34
+ const children = [];
35
+ ChildrenHelper.addChildren(component, element, children);
36
+
37
+ expect(factoryCalls.length).toBe(1);
38
+ expect(factoryCalls[0]).toBe(element.querySelector('[data-component="widget"]'));
39
+ expect(rendered.length).toBe(1);
40
+ expect(children.length).toBe(1);
41
+ });
42
+
43
+ it('instantiates one child per matched element for a single registerChildren entry', () => {
44
+ element.innerHTML = '<div data-component="widget"></div><div data-component="widget"></div>';
45
+ const component = {
46
+ registerChildren: () => [{
47
+ selector: 'widget',
48
+ factory: () => ({ render: () => {}, destroy: () => {} }),
49
+ }],
50
+ };
51
+
52
+ const children = [];
53
+ ChildrenHelper.addChildren(component, element, children);
54
+
55
+ expect(children.length).toBe(2);
56
+ });
57
+
58
+ it('does nothing when there are no registerChildren', () => {
59
+ const component = { registerChildren: () => [] };
60
+ const children = [];
61
+ ChildrenHelper.addChildren(component, element, children);
62
+ expect(children.length).toBe(0);
63
+ });
64
+
65
+ it('destroys previously-tracked children and resets the array before mounting new ones on a second call', () => {
66
+ let destroyCount = 0;
67
+ element.innerHTML = '<div data-component="widget"></div>';
68
+ const component = {
69
+ registerChildren: () => [{
70
+ selector: 'widget',
71
+ factory: () => ({ render: () => {}, destroy: () => destroyCount++ }),
72
+ }],
73
+ };
74
+
75
+ const children = [];
76
+ ChildrenHelper.addChildren(component, element, children);
77
+ expect(children.length).toBe(1);
78
+ expect(destroyCount).toBe(0);
79
+
80
+ element.innerHTML = '<div data-component="widget"></div>';
81
+ ChildrenHelper.addChildren(component, element, children);
82
+
83
+ expect(destroyCount).toBe(1);
84
+ expect(children.length).toBe(1);
85
+ });
86
+
87
+ it('destroyChildren calls destroy on every child', () => {
88
+ let destroyCount = 0;
89
+ const children = [
90
+ { destroy: () => destroyCount++ },
91
+ { destroy: () => destroyCount++ },
92
+ ];
93
+ ChildrenHelper.destroyChildren(children);
94
+ expect(destroyCount).toBe(2);
95
+ });
96
+ });
@@ -2,11 +2,14 @@ import { BaseComponent, ComponentConfig } from '../base-component.js';
2
2
 
3
3
  export class ChildrenHelper {
4
4
  public static addChildren(
5
- component: BaseComponent,
6
- element: HTMLElement,
5
+ component: BaseComponent,
6
+ element: HTMLElement,
7
7
  children: BaseComponent[]
8
8
  ): void {
9
- const configs: ComponentConfig[] = component.childConfigs();
9
+ this.destroyChildren(children);
10
+ children.length = 0;
11
+
12
+ const configs: ComponentConfig[] = component.registerChildren();
10
13
  configs.forEach(config => {
11
14
  element.querySelectorAll(`[data-component="${config.selector}"]`).forEach(el => {
12
15
  if (el instanceof HTMLElement) {