@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
@@ -1,6 +1,17 @@
1
1
  export type TrustLevel = 'strict' | 'normal' | 'trusted';
2
2
 
3
3
  export class SecurityHelper {
4
+ private static readonly STRICT_STRIPPED_TAGS = new Set(['IFRAME', 'OBJECT', 'EMBED']);
5
+ private static readonly URL_ATTRS = new Set([
6
+ 'href',
7
+ 'src',
8
+ 'action',
9
+ 'formaction',
10
+ 'poster',
11
+ 'background',
12
+ ]);
13
+ private static readonly DANGEROUS_URL_SCHEMES = /^(javascript|data):/i;
14
+
4
15
  public static sanitizeTemplate(value: unknown, trustLevel: TrustLevel = 'normal'): string {
5
16
  if (value === null || value === undefined) return '';
6
17
  let template = String(value);
@@ -22,35 +33,69 @@ export class SecurityHelper {
22
33
 
23
34
  public static escapeHtml(value: unknown): string {
24
35
  if (value === null || value === undefined) return '';
25
- const str = String(value);
26
- return str
27
- .replace(/&/g, "&")
28
- .replace(/</g, "&lt;")
29
- .replace(/>/g, "&gt;")
30
- .replace(/"/g, "&quot;")
31
- .replace(/'/g, "&#039;");
36
+
37
+ return String(value)
38
+ .replace(/&/g, '&amp;')
39
+ .replace(/</g, '&lt;')
40
+ .replace(/>/g, '&gt;')
41
+ .replace(/"/g, '&quot;')
42
+ .replace(/'/g, '&#039;');
32
43
  }
33
44
 
34
45
  private static sanitizeInputTemplate(template: string, trustLevel: TrustLevel): string {
35
- // Trusted: no tamplate sanitization
46
+ // Trusted: no template sanitization
36
47
  if (trustLevel === 'trusted') {
37
48
  return template;
38
49
  }
39
50
 
40
- // Always remove scripts and inline event handlers
41
- template = template
42
- .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
43
- .replace(/\son\w+\s*=\s*["'][^"']*["']/gi, '');
44
-
45
- // Strict: remove iframe, object, embed, href, data: protocol
46
- if (trustLevel === 'strict') {
47
- template = template
48
- .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
49
- .replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
50
- .replace(/<embed\b[^>]*>/gi, '')
51
- .replace(/href\s*=\s*["']javascript:[^"']*["']/gi, '')
52
- .replace(/src\s*=\s*["']data:[^"']*["']/gi, '');
51
+ const holder = document.createElement('template');
52
+ holder.innerHTML = template;
53
+ this.sanitizeNode(holder.content, trustLevel);
54
+
55
+ return holder.innerHTML;
56
+ }
57
+
58
+ private static sanitizeNode(root: ParentNode, trustLevel: TrustLevel): void {
59
+ // Snapshot children before mutating — removing an element from a live
60
+ // collection while iterating it would skip its next sibling.
61
+ const children = Array.from(root.children);
62
+
63
+ for (const el of children) {
64
+ const tag = el.tagName;
65
+
66
+ if (tag === 'SCRIPT') {
67
+ el.remove();
68
+ continue;
69
+ }
70
+ if (trustLevel === 'strict' && this.STRICT_STRIPPED_TAGS.has(tag)) {
71
+ el.remove();
72
+ continue;
73
+ }
74
+
75
+ for (const attr of Array.from(el.attributes)) {
76
+ const name = attr.name.toLowerCase();
77
+ if (name.startsWith('on')) {
78
+ el.removeAttribute(attr.name);
79
+ continue;
80
+ }
81
+ if (trustLevel === 'strict' && this.URL_ATTRS.has(name) && this.isDangerousUrl(attr.value)) {
82
+ el.removeAttribute(attr.name);
83
+ }
84
+ }
85
+
86
+ if (tag === 'TEMPLATE') {
87
+ this.sanitizeNode((el as HTMLTemplateElement).content, trustLevel);
88
+ } else {
89
+ this.sanitizeNode(el, trustLevel);
90
+ }
53
91
  }
54
- return template;
92
+ }
93
+
94
+ private static isDangerousUrl(value: string): boolean {
95
+ // Browsers strip ASCII tab/newline/CR from a URL (and trim leading C0
96
+ // control/space) before scheme-sniffing, so "java\tscript:" still runs
97
+ // as javascript: — normalize the same way before checking the scheme.
98
+ const normalized = value.replace(/[\t\n\r]/g, '').replace(/^[\x00-\x20]+/, '');
99
+ return this.DANGEROUS_URL_SCHEMES.test(normalized);
55
100
  }
56
101
  }
@@ -0,0 +1,183 @@
1
+ import { TokenHelper } from '#root/dist/src/core/base-classes/base-component/helpers/token.helper.js';
2
+
3
+ describe('TokenHelper', () => {
4
+ const makeEvent = (overrides = {}) => ({
5
+ preventDefault: () => {},
6
+ target: null,
7
+ clientX: 10,
8
+ clientY: 20,
9
+ key: 'Enter',
10
+ code: 'Enter',
11
+ ...overrides,
12
+ });
13
+
14
+ it('resolve always calls event.preventDefault()', () => {
15
+ let called = false;
16
+ const ev = makeEvent({ preventDefault: () => { called = true; } });
17
+ TokenHelper.resolve('@id', document.createElement('div'), ev);
18
+ expect(called).toBe(true);
19
+ });
20
+
21
+ it('@id resolves and escapes the element id', () => {
22
+ const el = document.createElement('div');
23
+ el.id = '<x>';
24
+ expect(TokenHelper.resolve('@id', el, makeEvent())).toBe('&lt;x&gt;');
25
+ });
26
+
27
+ it('@class resolves the escaped className', () => {
28
+ const el = document.createElement('div');
29
+ el.className = 'a b';
30
+ expect(TokenHelper.resolve('@class', el, makeEvent())).toBe('a b');
31
+ });
32
+
33
+ it('@name resolves the escaped name attribute', () => {
34
+ const el = document.createElement('input');
35
+ el.name = 'field';
36
+ expect(TokenHelper.resolve('@name', el, makeEvent())).toBe('field');
37
+ });
38
+
39
+ it('@name falls back to an empty string when el has no name property', () => {
40
+ const el = document.createElement('div');
41
+ expect(TokenHelper.resolve('@name', el, makeEvent())).toBe('');
42
+ });
43
+
44
+ it('@tag resolves the escaped tag name', () => {
45
+ const el = document.createElement('div');
46
+ expect(TokenHelper.resolve('@tag', el, makeEvent())).toBe('DIV');
47
+ });
48
+
49
+ it('@value resolves the sanitized input value', () => {
50
+ const el = document.createElement('input');
51
+ el.value = '<b>';
52
+ expect(TokenHelper.resolve('@value', el, makeEvent())).toBe('&lt;b&gt;');
53
+ });
54
+
55
+ it('@checked resolves the checked state', () => {
56
+ const el = document.createElement('input');
57
+ el.type = 'checkbox';
58
+ el.checked = true;
59
+ expect(TokenHelper.resolve('@checked', el, makeEvent())).toBe(true);
60
+ });
61
+
62
+ it('@selected resolves the selected state', () => {
63
+ const select = document.createElement('select');
64
+ const option = document.createElement('option');
65
+ option.selected = true;
66
+ select.appendChild(option);
67
+ expect(TokenHelper.resolve('@selected', option, makeEvent())).toBe(true);
68
+ });
69
+
70
+ it('@textContent resolves escaped textContent', () => {
71
+ const el = document.createElement('div');
72
+ el.textContent = '<i>';
73
+ expect(TokenHelper.resolve('@textContent', el, makeEvent())).toBe('&lt;i&gt;');
74
+ });
75
+
76
+ it('@textContent falls back to an empty string when el has no textContent', () => {
77
+ expect(TokenHelper.resolve('@textContent', {}, makeEvent())).toBe('');
78
+ });
79
+
80
+ it('@innerText resolves escaped innerText', () => {
81
+ const el = document.createElement('div');
82
+ el.innerText = '<i>';
83
+ expect(TokenHelper.resolve('@innerText', el, makeEvent())).toBe('&lt;i&gt;');
84
+ });
85
+
86
+ it('@innerText falls back to an empty string when el has no innerText', () => {
87
+ expect(TokenHelper.resolve('@innerText', {}, makeEvent())).toBe('');
88
+ });
89
+
90
+ it('@html resolves escaped innerHTML', () => {
91
+ const el = document.createElement('div');
92
+ el.innerHTML = '<i>x</i>';
93
+ expect(TokenHelper.resolve('@html', el, makeEvent())).toBe('&lt;i&gt;x&lt;/i&gt;');
94
+ });
95
+
96
+ it('@html falls back to an empty string when el has no innerHTML', () => {
97
+ expect(TokenHelper.resolve('@html', {}, makeEvent())).toBe('');
98
+ });
99
+
100
+ it('@event resolves to the raw event object', () => {
101
+ const ev = makeEvent();
102
+ expect(TokenHelper.resolve('@event', document.createElement('div'), ev)).toBe(ev);
103
+ });
104
+
105
+ it('@target resolves to event.target', () => {
106
+ const target = document.createElement('span');
107
+ const ev = makeEvent({ target });
108
+ expect(TokenHelper.resolve('@target', document.createElement('div'), ev)).toBe(target);
109
+ });
110
+
111
+ it('@x and @y resolve to clientX/clientY', () => {
112
+ const ev = makeEvent({ clientX: 5, clientY: 6 });
113
+ expect(TokenHelper.resolve('@x', document.createElement('div'), ev)).toBe(5);
114
+ expect(TokenHelper.resolve('@y', document.createElement('div'), ev)).toBe(6);
115
+ });
116
+
117
+ it('@x and @y fall back to 0 when clientX/clientY are missing', () => {
118
+ const ev = makeEvent({ clientX: undefined, clientY: undefined });
119
+ expect(TokenHelper.resolve('@x', document.createElement('div'), ev)).toBe(0);
120
+ expect(TokenHelper.resolve('@y', document.createElement('div'), ev)).toBe(0);
121
+ });
122
+
123
+ it('@key and @code resolve to escaped keyboard event properties', () => {
124
+ const ev = makeEvent({ key: '<', code: 'KeyA' });
125
+ expect(TokenHelper.resolve('@key', document.createElement('div'), ev)).toBe('&lt;');
126
+ expect(TokenHelper.resolve('@code', document.createElement('div'), ev)).toBe('KeyA');
127
+ });
128
+
129
+ it('@key and @code fall back to an empty string when key/code are missing', () => {
130
+ const ev = makeEvent({ key: undefined, code: undefined });
131
+ expect(TokenHelper.resolve('@key', document.createElement('div'), ev)).toBe('');
132
+ expect(TokenHelper.resolve('@code', document.createElement('div'), ev)).toBe('');
133
+ });
134
+
135
+ it('@attr: resolves an escaped attribute value', () => {
136
+ const el = document.createElement('div');
137
+ el.setAttribute('title', '<t>');
138
+ expect(TokenHelper.resolve('@attr:title', el, makeEvent())).toBe('&lt;t&gt;');
139
+ });
140
+
141
+ it('@attr: falls back to an empty string when the attribute is missing', () => {
142
+ const el = document.createElement('div');
143
+ expect(TokenHelper.resolve('@attr:title', el, makeEvent())).toBe('');
144
+ });
145
+
146
+ it('@dataset: resolves an escaped dataset value', () => {
147
+ const el = document.createElement('div');
148
+ el.dataset.foo = '<f>';
149
+ expect(TokenHelper.resolve('@dataset:foo', el, makeEvent())).toBe('&lt;f&gt;');
150
+ });
151
+
152
+ it('@dataset: falls back to an empty string when the dataset key is missing', () => {
153
+ const el = document.createElement('div');
154
+ expect(TokenHelper.resolve('@dataset:foo', el, makeEvent())).toBe('');
155
+ });
156
+
157
+ it('resolves a double-quoted string literal', () => {
158
+ expect(TokenHelper.resolve('"hello"', document.createElement('div'), makeEvent())).toBe('hello');
159
+ });
160
+
161
+ it('resolves a single-quoted string literal', () => {
162
+ expect(TokenHelper.resolve("'hello'", document.createElement('div'), makeEvent())).toBe('hello');
163
+ });
164
+
165
+ it('resolves a number literal', () => {
166
+ expect(TokenHelper.resolve('42', document.createElement('div'), makeEvent())).toBe(42);
167
+ });
168
+
169
+ it('falls back to the raw token string when nothing else matches', () => {
170
+ expect(TokenHelper.resolve('unknownToken', document.createElement('div'), makeEvent())).toBe('unknownToken');
171
+ });
172
+
173
+ it('registerCustomToken adds a resolvable exact token', () => {
174
+ TokenHelper.registerCustomToken('@custom', (el) => `custom:${el.tagName}`);
175
+ const el = document.createElement('span');
176
+ expect(TokenHelper.resolve('@custom', el, makeEvent())).toBe('custom:SPAN');
177
+ });
178
+
179
+ it('registerPrefixedToken adds a resolvable prefixed token', () => {
180
+ TokenHelper.registerPrefixedToken('@upper:', (suffix) => suffix.toUpperCase());
181
+ expect(TokenHelper.resolve('@upper:hi', document.createElement('div'), makeEvent())).toBe('HI');
182
+ });
183
+ });
@@ -0,0 +1,85 @@
1
+ import { Component } from '#root/dist/src/core/index.js';
2
+ import { DataBindingHelper } from '#root/dist/src/core/base-classes/component/helpers/data-binding.helper.js';
3
+
4
+ class TestComponent extends Component {
5
+ constructor(options = {}) {
6
+ super(options);
7
+ }
8
+ }
9
+
10
+ describe('Component', () => {
11
+ let app;
12
+
13
+ beforeEach(() => {
14
+ app = document.getElementById('app');
15
+ });
16
+
17
+ afterEach(() => {
18
+ app.innerHTML = '';
19
+ app = null;
20
+ });
21
+
22
+ it('getValues() delegates to DataBindingHelper.getDataBindingValues on the element', () => {
23
+ const component = new TestComponent();
24
+ component.getElement().innerHTML = '<input data-bind="name" value="Ada">';
25
+ expect(component.getValues()).toEqual({ name: 'Ada' });
26
+ });
27
+
28
+ it('generateTemplate() delegates to ConfigHelper.createNormalizedTemplate using config/defaults/normalizeKeys/templateFn', () => {
29
+ const component = new TestComponent({
30
+ config: { name: undefined },
31
+ defaults: { name: 'fallback', greeting: 'hi' },
32
+ normalizeKeys: ['name'],
33
+ templateFn: (cfg) => `<span>${cfg.greeting} ${cfg.name}</span>`,
34
+ });
35
+
36
+ component.render();
37
+
38
+ expect(component.getElement().innerHTML).toBe('<span>hi </span>');
39
+ });
40
+
41
+ it('onBeforeRender applies props.className to the element on render', () => {
42
+ const component = new TestComponent({ props: { className: 'highlight' } });
43
+ component.render();
44
+ expect(component.getElement().classList.contains('highlight')).toBe(true);
45
+ });
46
+
47
+ it('onBeforeRender applies props.style as cssText on render', () => {
48
+ const component = new TestComponent({ props: { style: 'color: red;' } });
49
+ component.render();
50
+ expect(component.getElement().style.color).toBe('red');
51
+ });
52
+
53
+ it('applies props-based data-bindings to the rendered template', () => {
54
+ const component = new TestComponent({
55
+ templateFn: () => '<span data-bind="name"></span>',
56
+ props: { name: 'Ada' },
57
+ });
58
+
59
+ component.render();
60
+
61
+ expect(component.getElement().querySelector('span').textContent).toBe('Ada');
62
+ });
63
+
64
+ it('onAfterRender calls DataBindingHelper.applyDataBindings with the element and props, after the template has been written', () => {
65
+ // onAfterRender runs after render() sets innerHTML from generateTemplate(),
66
+ // so the template's own [data-bind] nodes already exist by the time this fires.
67
+ const calls = [];
68
+ const spy = spyOn(DataBindingHelper, 'applyDataBindings');
69
+ spy.andCallFake((element, props) => {
70
+ calls.push({ props, htmlAtCallTime: element.innerHTML });
71
+ });
72
+
73
+ const component = new TestComponent({
74
+ templateFn: () => '<span data-bind="name"></span>',
75
+ props: { name: 'Ada' },
76
+ });
77
+ component.render();
78
+
79
+ expect(calls.length).toBe(1);
80
+ expect(calls[0].props).toEqual({ name: 'Ada' });
81
+ expect(calls[0].htmlAtCallTime).toBe('<span data-bind="name"></span>');
82
+
83
+ spy.restore();
84
+ });
85
+ });
@@ -1,24 +1,17 @@
1
- import { BaseComponent, ButtonManager, BaseButton, BaseComponentOptions } from '../../index.js';
1
+ import { BaseComponent, BaseComponentOptions } from '../../index.js';
2
2
  import { ConfigHelper } from './helpers/config.helper.js';
3
3
  import { DataBindingHelper } from './helpers/data-binding.helper.js';
4
4
 
5
- export interface ComponentButton extends BaseButton {
6
- // if needed for Component-specific properties
7
- }
8
-
9
5
  export interface ComponentProps {
10
6
  // Common HTML attributes - extend as needed
11
7
  className?: string;
12
8
  style?: string;
13
9
  textContent?: string;
14
-
10
+
15
11
  // Form field bindings - extend as needed
16
12
  name?: string;
17
13
  email?: string;
18
-
19
- // Dynamic buttons
20
- buttons?: ComponentButton[];
21
-
14
+
22
15
  // Allow for additional data-bind attributes
23
16
  [key: string]: any;
24
17
  }
@@ -45,7 +38,9 @@ interface ComponentOptions<K = any> extends BaseComponentOptions {
45
38
  export abstract class Component<T extends HTMLElement = HTMLElement, K = any> extends BaseComponent<T> {
46
39
  protected config: K;
47
40
  protected props: ComponentProps;
48
- private buttonManager: ButtonManager;
41
+ private _templateFn: (config?: K) => string;
42
+ private _normalizeKeys: (keyof K)[];
43
+ private _defaults: Partial<K>;
49
44
 
50
45
  constructor({
51
46
  templateFn = () => '',
@@ -55,35 +50,36 @@ export abstract class Component<T extends HTMLElement = HTMLElement, K = any> ex
55
50
  config,
56
51
  normalizeKeys = [],
57
52
  defaults = {},
53
+ trustLevel,
58
54
  }: ComponentOptions) {
59
- const template = ConfigHelper.createNormalizedTemplate({
60
- config, defaults, normalizeKeys, templateFn,
61
- });
62
- super({template, mountTarget, tagName});
55
+ super({ mountTarget, tagName, trustLevel });
63
56
 
64
57
  this.config = ConfigHelper.setConfigValue(config, normalizeKeys);
65
58
  this.props = props;
66
- this.buttonManager = this.createButtonManager();
59
+ this._templateFn = templateFn;
60
+ this._normalizeKeys = normalizeKeys as (keyof K)[];
61
+ this._defaults = defaults;
67
62
  }
68
63
 
69
64
  public getValues(): Record<string, string> {
70
65
  return DataBindingHelper.getDataBindingValues(this.element);
71
66
  }
72
67
 
73
- public override render(): HTMLElement {
68
+ protected override generateTemplate(): string {
69
+ return ConfigHelper.createNormalizedTemplate({
70
+ config: this.config,
71
+ defaults: this._defaults,
72
+ normalizeKeys: this._normalizeKeys,
73
+ templateFn: this._templateFn,
74
+ });
75
+ }
76
+
77
+ protected override onBeforeRender(): void {
74
78
  this.applyProps();
75
- this.appendDynamicButtons();
76
- this.autoBindEvents();
77
- this.parseDataAttributes();
78
- return super.render();
79
79
  }
80
80
 
81
- private createButtonManager(): ButtonManager {
82
- return new ButtonManager(
83
- this,
84
- this.props.buttons,
85
- { containerClassName: 'component-buttons' }
86
- );
81
+ protected override onAfterRender(): void {
82
+ this.applyDataBindings();
87
83
  }
88
84
 
89
85
  private applyProps(): void {
@@ -91,13 +87,8 @@ export abstract class Component<T extends HTMLElement = HTMLElement, K = any> ex
91
87
  this.element.classList.add(this.props.className);
92
88
  }
93
89
  if (this.props.style) {
94
- this.element.style = this.props.style;
90
+ this.element.style.cssText = this.props.style;
95
91
  }
96
- this.applyDataBindings();
97
- }
98
-
99
- private appendDynamicButtons(): void {
100
- this.buttonManager.appendTo(this.element);
101
92
  }
102
93
 
103
94
  private applyDataBindings(): void {
@@ -0,0 +1,58 @@
1
+ import { ConfigHelper } from '#root/dist/src/core/base-classes/component/helpers/config.helper.js';
2
+
3
+ describe('ConfigHelper', () => {
4
+ it('normalize shallow-merges defaults under config, letting config win', () => {
5
+ const result = ConfigHelper.normalize({ a: 1 }, { a: 0, b: 2 });
6
+ expect(result).toEqual({ a: 1, b: 2 });
7
+ });
8
+
9
+ it('normalize returns config untouched when there are no matching defaults', () => {
10
+ const result = ConfigHelper.normalize({ a: 1 }, {});
11
+ expect(result).toEqual({ a: 1 });
12
+ });
13
+
14
+ it('normalizeStrings replaces undefined and null values with an empty string for the given keys', () => {
15
+ const result = ConfigHelper.normalizeStrings({ a: undefined, b: null, c: 'x' }, ['a', 'b']);
16
+ expect(result).toEqual({ a: '', b: '', c: 'x' });
17
+ });
18
+
19
+ it('normalizeStrings leaves keys not in stringKeys untouched, even if undefined', () => {
20
+ const result = ConfigHelper.normalizeStrings({ a: undefined, b: undefined }, ['a']);
21
+ expect(result).toEqual({ a: '', b: undefined });
22
+ });
23
+
24
+ it('normalizeStrings does not mutate the original config object', () => {
25
+ const original = { a: undefined };
26
+ ConfigHelper.normalizeStrings(original, ['a']);
27
+ expect(original.a).toBeUndefined();
28
+ });
29
+
30
+ it('createNormalizedTemplate normalizes then calls templateFn with the result', () => {
31
+ const result = ConfigHelper.createNormalizedTemplate({
32
+ config: { name: undefined },
33
+ defaults: { name: 'fallback', extra: 1 },
34
+ normalizeKeys: ['name'],
35
+ templateFn: (cfg) => `name=${cfg.name},extra=${cfg.extra}`,
36
+ });
37
+ expect(result).toBe('name=,extra=1');
38
+ });
39
+
40
+ it('createNormalizedTemplate defaults templateFn to a function returning an empty string', () => {
41
+ const result = ConfigHelper.createNormalizedTemplate({ config: { a: 1 } });
42
+ expect(result).toBe('');
43
+ });
44
+
45
+ it('setConfigValue normalizes strings when normalizeKeys is truthy', () => {
46
+ const result = ConfigHelper.setConfigValue({ a: undefined }, ['a']);
47
+ expect(result).toEqual({ a: '' });
48
+ });
49
+
50
+ it('setConfigValue returns the config as-is when normalizeKeys is falsy and config is truthy', () => {
51
+ const config = { a: 1 };
52
+ expect(ConfigHelper.setConfigValue(config, null)).toBe(config);
53
+ });
54
+
55
+ it('setConfigValue returns an empty object when both normalizeKeys and config are falsy', () => {
56
+ expect(ConfigHelper.setConfigValue(undefined, null)).toEqual({});
57
+ });
58
+ });
@@ -0,0 +1,75 @@
1
+ import { DataBindingHelper } from '#root/dist/src/core/base-classes/component/helpers/data-binding.helper.js';
2
+
3
+ describe('DataBindingHelper', () => {
4
+ let container;
5
+
6
+ beforeEach(() => {
7
+ container = document.createElement('div');
8
+ document.body.appendChild(container);
9
+ });
10
+
11
+ afterEach(() => {
12
+ container.remove();
13
+ container = null;
14
+ });
15
+
16
+ it('applyDataBindings sets textContent for a plain bound element', () => {
17
+ container.innerHTML = '<span data-bind="name"></span>';
18
+ DataBindingHelper.applyDataBindings(container, { name: 'Ada' });
19
+ expect(container.querySelector('span').textContent).toBe('Ada');
20
+ });
21
+
22
+ it('applyDataBindings sets .value for a bound input element', () => {
23
+ container.innerHTML = '<input data-bind="name">';
24
+ DataBindingHelper.applyDataBindings(container, { name: 'Ada' });
25
+ expect(container.querySelector('input').value).toBe('Ada');
26
+ });
27
+
28
+ it('applyDataBindings sets .value for a bound textarea element', () => {
29
+ container.innerHTML = '<textarea data-bind="bio"></textarea>';
30
+ DataBindingHelper.applyDataBindings(container, { bio: 'hello' });
31
+ expect(container.querySelector('textarea').value).toBe('hello');
32
+ });
33
+
34
+ it('applyDataBindings coerces non-string values to strings', () => {
35
+ container.innerHTML = '<span data-bind="count"></span>';
36
+ DataBindingHelper.applyDataBindings(container, { count: 42 });
37
+ expect(container.querySelector('span').textContent).toBe('42');
38
+ });
39
+
40
+ it('applyDataBindings skips a bound element when props[key] is undefined', () => {
41
+ container.innerHTML = '<span data-bind="missing">original</span>';
42
+ DataBindingHelper.applyDataBindings(container, {});
43
+ expect(container.querySelector('span').textContent).toBe('original');
44
+ });
45
+
46
+ it('applyDataBindings skips a bound element when data-bind is empty', () => {
47
+ container.innerHTML = '<span data-bind="">original</span>';
48
+ DataBindingHelper.applyDataBindings(container, { '': 'ignored' });
49
+ expect(container.querySelector('span').textContent).toBe('original');
50
+ });
51
+
52
+ it('getDataBindingValues reads .value from bound input/textarea elements', () => {
53
+ container.innerHTML = '<input data-bind="name" value="Ada"><textarea data-bind="bio">hi</textarea>';
54
+ const values = DataBindingHelper.getDataBindingValues(container);
55
+ expect(values).toEqual({ name: 'Ada', bio: 'hi' });
56
+ });
57
+
58
+ it('getDataBindingValues reads textContent from bound plain elements', () => {
59
+ container.innerHTML = '<span data-bind="name">Ada</span>';
60
+ const values = DataBindingHelper.getDataBindingValues(container);
61
+ expect(values).toEqual({ name: 'Ada' });
62
+ });
63
+
64
+ it('getDataBindingValues defaults missing textContent to an empty string', () => {
65
+ container.innerHTML = '<span data-bind="name"></span>';
66
+ const values = DataBindingHelper.getDataBindingValues(container);
67
+ expect(values).toEqual({ name: '' });
68
+ });
69
+
70
+ it('getDataBindingValues skips an element when data-bind is empty', () => {
71
+ container.innerHTML = '<span data-bind="">hi</span>';
72
+ const values = DataBindingHelper.getDataBindingValues(container);
73
+ expect(values).toEqual({});
74
+ });
75
+ });