@nutin/cli 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/CHANGELOG.md +122 -8
  2. package/README.md +21 -37
  3. package/bin/add-feature.mjs +9 -0
  4. package/bin/cli.mjs +8 -0
  5. package/bin/create-app.mjs +5 -4
  6. package/bin/update-app.mjs +9 -0
  7. package/lib/index.mjs +81 -30
  8. package/lib/src/add-feature/feature-adder.mjs +83 -0
  9. package/lib/src/add-feature/feature-context-builder.mjs +25 -0
  10. package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
  11. package/lib/src/add-feature/package-json-updater.mjs +49 -0
  12. package/lib/src/common/feature-registry.mjs +11 -0
  13. package/lib/src/common/file-generator.mjs +159 -0
  14. package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
  15. package/lib/src/common/package-data.mjs +10 -0
  16. package/lib/src/common/package-json-helper.mjs +103 -0
  17. package/lib/src/common/project-meta.mjs +54 -0
  18. package/lib/src/create-app/context-builder.mjs +21 -0
  19. package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
  20. package/lib/src/create-app/json-manager.mjs +73 -0
  21. package/lib/src/create-app/new-app-prompt.mjs +51 -0
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +15 -13
  23. package/lib/src/create-app/success-message.mjs +14 -0
  24. package/lib/src/update-app/conflict-report-writer.mjs +31 -0
  25. package/lib/src/update-app/fetch-old-templates.mjs +61 -0
  26. package/lib/src/update-app/project-updater.mjs +136 -0
  27. package/lib/src/update-app/template-differ.mjs +124 -0
  28. package/lib/src/update-app/update-context-builder.mjs +25 -0
  29. package/lib/src/update-app/update-summary-printer.mjs +22 -0
  30. package/lib/src/update-app/version-compare.mjs +12 -0
  31. package/lib/src/utils/print.mjs +36 -0
  32. package/lib/src/utils/promise-exec-alias.mjs +4 -0
  33. package/package.json +17 -12
  34. package/templates/base/.gitignore.hbs +6 -1
  35. package/templates/base/AGENTS.md.hbs +169 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +227 -0
  37. package/templates/base/README.md.hbs +8 -17
  38. package/templates/base/config/languages.json.hbs +6 -0
  39. package/templates/base/config/seo.json.hbs +12 -0
  40. package/templates/base/nutin.config.js.hbs +51 -0
  41. package/templates/base/src/app/components/index.ts.hbs +1 -2
  42. package/templates/base/src/app/globals.d.ts.hbs +3 -15
  43. package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
  44. package/templates/base/src/app/main.ts.hbs +7 -26
  45. package/templates/base/src/app/services/index.ts.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
  47. package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
  48. package/templates/base/src/app/views/index.ts.hbs +1 -0
  49. package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
  50. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
  51. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
  52. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +81 -31
  53. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
  54. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +38 -23
  55. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +41 -1
  59. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +13 -14
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  65. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  66. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  67. package/templates/base/src/core/base-classes/component/component.ts.hbs +27 -40
  68. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
  69. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -0
  70. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
  71. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  72. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  73. package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
  74. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  75. package/templates/base/src/core/config.ts.hbs +7 -0
  76. package/templates/base/src/core/index.ts.hbs +0 -3
  77. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  78. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  79. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  80. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  81. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  83. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  85. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  86. package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
  87. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  89. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  90. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
  91. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  92. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +60 -9
  93. package/templates/base/src/core/services/index.ts.hbs +5 -6
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  95. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  96. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  97. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +42 -30
  100. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  102. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +13 -70
  103. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  104. package/templates/base/src/core/services/router/router.ts.hbs +40 -9
  105. package/templates/base/src/index.html.hbs +2 -6
  106. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  107. package/templates/base/src/styles/base/_variables.scss.hbs +1 -85
  108. package/templates/base/src/styles/main.scss.hbs +1 -18
  109. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  110. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  111. package/templates/base/tools/builder/builder.js.hbs +24 -26
  112. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  114. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  115. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  116. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  117. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  118. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  119. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  120. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  121. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  122. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  123. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  124. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  125. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  126. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  127. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  129. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  130. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  131. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  132. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  133. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  136. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  137. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  138. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  139. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  140. package/templates/base/tools/dev/serve.js.hbs +11 -1
  141. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  142. package/templates/base/tools/generator/generator.js.hbs +64 -47
  143. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  144. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  145. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  146. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  147. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  148. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  149. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  150. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  151. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  153. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  154. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  155. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  156. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  157. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  158. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  159. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  164. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  165. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  166. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  168. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  169. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  170. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  171. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  172. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  173. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  175. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  176. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  177. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  178. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  179. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  180. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  181. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  182. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  185. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  186. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  187. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  188. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  189. package/templates/base/tools/utils/format.js.hbs +10 -10
  190. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  191. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  192. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  193. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  194. package/templates/base/tools/utils/index.js.hbs +1 -3
  195. package/templates/base/tools/utils/languages.js.hbs +15 -0
  196. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  197. package/templates/base/tools/utils/print.js.hbs +19 -13
  198. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  199. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  200. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  201. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  202. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  203. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  204. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  205. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  206. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  207. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  208. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  211. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  213. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  214. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  215. package/lib/src/context-builder.mjs +0 -35
  216. package/lib/src/file-generator.mjs +0 -109
  217. package/lib/src/json-generator.mjs +0 -31
  218. package/lib/src/package-manager.mjs +0 -139
  219. package/lib/src/print.mjs +0 -31
  220. package/lib/src/prompts.mjs +0 -123
  221. package/lib/src/utils.mjs +0 -18
  222. package/lib/src/version.mjs +0 -1
  223. package/templates/base/builder.config.js.hbs +0 -35
  224. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  225. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  226. package/templates/base/src/app/dom.test.js.hbs +0 -13
  227. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  228. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  229. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  230. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  231. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  232. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -85
  233. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  234. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -108
  235. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  236. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  237. package/templates/base/src/libs/index.ts.hbs +0 -4
  238. package/templates/base/src/libs/popover/popover.ts.hbs +0 -145
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -72
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  242. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  243. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  244. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  245. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  246. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  247. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  248. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  249. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  250. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  251. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  252. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  253. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  254. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  255. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  256. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  257. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  258. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  259. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  260. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -240
  261. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  262. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  263. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  264. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  265. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  266. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  267. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  268. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  269. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  270. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  271. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  272. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  273. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  274. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  275. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  276. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  277. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  278. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  279. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  280. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  281. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  282. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  283. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  284. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -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,24 +1,26 @@
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 { TrustLevel } from './helpers/security.helper.js';
10
- export { CatalogItemConfig } from './helpers/catalog.helper.js';
7
+ import { Component, AppEventBus } from '../../index.js';
8
+ import { SecurityHelper, TrustLevel } from './helpers/security.helper.js';
9
+
10
+ export { SecurityHelper, TrustLevel } from './helpers/security.helper.js';
11
+ export { CatalogItemConfig, CatalogConfig, CatalogItemPrimitive } from './helpers/catalog.helper.js';
12
+ export { TokenHelper } from './helpers/token.helper.js';
11
13
 
12
14
  /**```typescript
13
15
  * export interface ComponentConfig {
14
16
  * selector: string;
15
- * factory: (element: HTMLElement) => BaseComponent;
17
+ * factory: (element: HTMLElement) => Component;
16
18
  * }
17
19
  * ```
18
20
  */
19
21
  export interface ComponentConfig {
20
22
  selector: string;
21
- factory: (element: HTMLElement) => BaseComponent;
23
+ factory: (element: HTMLElement) => Component;
22
24
  }
23
25
 
24
26
  export interface BaseComponentOptions {
@@ -30,63 +32,111 @@ export interface BaseComponentOptions {
30
32
 
31
33
  export abstract class BaseComponent<T extends HTMLElement = HTMLElement> {
32
34
  private _children: BaseComponent[] = [];
35
+ private _isRendering = false;
33
36
  protected element: T;
34
- protected eventListeners: Array<[string, EventListener]> = [];
37
+ protected eventListeners: Array<[EventTarget, string, EventListener]> = [];
38
+ private _busSubscriptions: Array<[EventKey, (data: any) => void]> = [];
39
+ protected trustLevel: TrustLevel | undefined;
35
40
 
36
41
  constructor({
37
- template = '',
38
42
  mountTarget = '#app',
39
43
  tagName = 'div',
40
44
  trustLevel
41
45
  }: BaseComponentOptions) {
42
- this.element = DomHelper.createElement<T>(tagName, template, trustLevel);
43
- this.parseDataAttributes();
44
- this.autoBindEvents();
46
+ this.trustLevel = trustLevel;
47
+ this.element = DomHelper.createElement<T>(tagName, '', trustLevel);
45
48
  DomHelper.mountElement(this.element, mountTarget);
46
49
  }
47
50
 
48
- public childConfigs(): ComponentConfig[] {
51
+ public getElement(): T {
52
+ return this.element;
53
+ }
54
+
55
+ public registerChildren(): ComponentConfig[] {
49
56
  return [];
50
57
  }
51
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
+
52
79
  public destroy(): void {
53
- EventHelper.destroyEvents(this.element, this.eventListeners);
80
+ this.onBeforeDestroy();
81
+ EventHelper.destroyEvents(this.eventListeners);
82
+ this._busSubscriptions.forEach(([event, callback]) => {
83
+ AppEventBus.off(event, callback);
84
+ });
85
+ this._busSubscriptions = [];
54
86
  ChildrenHelper.destroyChildren(this._children);
55
87
  this.element.remove();
88
+ this.onAfterDestroy();
56
89
  }
57
90
 
58
- public render(): HTMLElement {
59
- this.addChildren();
60
- return this.element;
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 '';
61
99
  }
62
100
 
63
- /**
64
- * ```typescript
65
- interface CatalogConfig {
66
- array: CatalogItemConfig[];
67
- elementName: string;
68
- elementTag?: keyof HTMLElementTagNameMap;
69
- selector: string;
70
- component: new (el: HTMLElement, data: any) => BaseComponent;
71
- };
72
- ```
73
- */
74
- protected catalogConfig(config: CatalogConfig): ComponentConfig[] {
75
- return CatalogHelper.generateCatalog(config);
101
+ protected hydrate(): void {
102
+ this.parseDataAttributes();
103
+ DomHelper.cleanupOptionalContent();
104
+ }
105
+
106
+ protected compose(): void {
107
+ this.addChildren();
76
108
  }
77
109
 
78
110
  protected parseDataAttributes(): void {
79
- {{#if i18n}}
80
111
  I18nHelper.parseI18nAttributes(this.element);
81
- {{/if}}
82
112
  PipeHelper.parsePipeAttributes(this.element);
83
113
  }
84
114
 
85
115
  protected autoBindEvents(): void {
116
+ EventHelper.destroyEvents(this.eventListeners);
117
+ this.eventListeners.length = 0;
86
118
  EventHelper.bindEvents(this, this.element, this.eventListeners);
87
119
  }
88
120
 
89
121
  protected addChildren(): void {
90
122
  ChildrenHelper.addChildren(this, this.element, this._children);
91
123
  }
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]);
135
+ }
136
+
137
+ protected listenToRenderEvents(events: EventKey[]): void {
138
+ events.forEach((event: EventKey) => {
139
+ this.listen(event, () => this.render());
140
+ });
141
+ }
92
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,11 +1,11 @@
1
- import { BaseComponent, ComponentConfig } from "../base-component.js";
1
+ import { Component, ComponentConfig, ComponentOptions } from "../../index.js";
2
2
 
3
- export interface CatalogConfig {
4
- array: CatalogItemConfig[];
3
+ export interface CatalogConfig extends ComponentOptions {
4
+ items: CatalogItemConfig[];
5
5
  elementName: string;
6
6
  elementTag?: keyof HTMLElementTagNameMap;
7
7
  selector: string;
8
- component: new (el: HTMLElement, data: any) => BaseComponent;
8
+ component: new (el: HTMLElement, data: any, props?: any) => Component;
9
9
  };
10
10
 
11
11
  export interface CatalogItemBase {
@@ -23,12 +23,12 @@ export type CatalogItemConfig<T = any> =
23
23
 
24
24
  /**
25
25
  * ```typescript
26
- interface CatalogConfig {
27
- array: CatalogItemConfig[];
26
+ interface CatalogConfig extends ComponentOptions {
27
+ items: CatalogItemConfig[];
28
28
  elementName: string;
29
29
  elementTag?: keyof HTMLElementTagNameMap;
30
30
  selector: string;
31
- component: new (el: HTMLElement, data: any) => BaseComponent;
31
+ component: new (el: HTMLElement, data: any) => Component;
32
32
  };
33
33
 
34
34
  type CatalogItemConfig<T = any> =
@@ -36,42 +36,57 @@ type CatalogItemConfig<T = any> =
36
36
  ```
37
37
  */
38
38
  export class CatalogHelper {
39
- public static generateCatalog(config: CatalogConfig): ComponentConfig[] {
40
- if (!config.array || config.array.length < 1) return [];
39
+ public static generateCatalog(config: CatalogConfig, scope: Element = document.documentElement): ComponentConfig[] {
40
+ if (!config.items || config.items.length < 1) return [];
41
41
 
42
- const container = document.querySelector(`[data-catalog="${config.selector}"]`);
42
+ const componentConfigs: ComponentConfig[] = [];
43
+ const containers = scope.querySelectorAll(`[data-catalog="${config.selector}"]`);
43
44
 
44
- if (!container || !(container instanceof HTMLElement)) return [];
45
+ containers.forEach((container) => {
46
+ if (!container || !(container instanceof HTMLElement)) return;
47
+ componentConfigs.push(...this.getComponentConfigItems(config, container))
48
+ })
45
49
 
46
- const componentConfigs: ComponentConfig[] = [];
50
+ return componentConfigs;
51
+ }
47
52
 
48
- for (let i = 0; i < config.array.length; i++) {
53
+ private static getComponentConfigItems(config: CatalogConfig, container: HTMLElement): ComponentConfig[] {
54
+ container.innerHTML = '';
55
+ const componentConfigs: ComponentConfig[] = [];
56
+ for (let i = 0; i < config.items.length; i++) {
49
57
  this.createElements(i, config, container);
50
58
  this.pushConfig(i, componentConfigs, config);
51
59
  }
52
-
53
60
  return componentConfigs;
54
61
  }
55
62
 
56
63
  private static createElements(index: number, config: CatalogConfig, container: HTMLElement): void {
57
- const el = document.createElement(config.elementTag || 'div');
64
+ const wrapper = document.createElement(config.elementTag || 'div');
65
+ const el = document.createElement('div');
66
+ wrapper.appendChild(el);
58
67
  el.setAttribute('data-component', `${config.elementName}-${index}`);
59
- el.dataset.index = String(index);
60
- container?.appendChild(el);
68
+ wrapper.dataset.index = String(index);
69
+ container?.appendChild(wrapper);
61
70
  }
62
71
 
63
72
  private static pushConfig(index: number, componentConfigs: ComponentConfig[], config: CatalogConfig): void {
64
- const item = config.array[index];
73
+ const configWithIndex = this.getConfigWithIndex(config, index)
74
+
75
+ const { props, defaults, normalizeKeys } = config;
76
+ const options = { ...props, ...defaults, ...normalizeKeys };
65
77
 
66
- const configWithIndex: CatalogItemConfig = (item && typeof item === 'object')
67
- ? { ...(item as object), index: index }
68
- : { value: item, index: index };
69
-
70
78
  componentConfigs.push(
71
79
  {
72
80
  selector: `${config.elementName}-${index}`,
73
- factory: (el) => new config.component(el, configWithIndex)
81
+ factory: (el) => new config.component(el, configWithIndex, options),
74
82
  },
75
83
  )
76
84
  }
85
+
86
+ private static getConfigWithIndex(config: CatalogConfig, index: number): CatalogItemConfig {
87
+ const item = config.items[index];
88
+ return (item && typeof item === 'object')
89
+ ? { ...(item as object), index: index }
90
+ : { value: item, index: index };
91
+ }
77
92
  }
@@ -0,0 +1,74 @@
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('destroyChildren calls destroy on every child', () => {
66
+ let destroyCount = 0;
67
+ const children = [
68
+ { destroy: () => destroyCount++ },
69
+ { destroy: () => destroyCount++ },
70
+ ];
71
+ ChildrenHelper.destroyChildren(children);
72
+ expect(destroyCount).toBe(2);
73
+ });
74
+ });
@@ -6,7 +6,7 @@ export class ChildrenHelper {
6
6
  element: HTMLElement,
7
7
  children: BaseComponent[]
8
8
  ): void {
9
- const configs: ComponentConfig[] = component.childConfigs();
9
+ const configs: ComponentConfig[] = component.registerChildren();
10
10
  configs.forEach(config => {
11
11
  element.querySelectorAll(`[data-component="${config.selector}"]`).forEach(el => {
12
12
  if (el instanceof HTMLElement) {