@nutin/cli 1.3.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -9
- package/README.md +20 -35
- package/bin/add-feature.mjs +9 -0
- package/bin/cli.mjs +8 -0
- package/bin/create-app.mjs +5 -4
- package/bin/update-app.mjs +9 -0
- package/lib/index.mjs +76 -25
- package/lib/src/add-feature/feature-adder.mjs +83 -0
- package/lib/src/add-feature/feature-context-builder.mjs +25 -0
- package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
- package/lib/src/add-feature/package-json-updater.mjs +49 -0
- package/lib/src/common/feature-registry.mjs +11 -0
- package/lib/src/common/file-generator.mjs +159 -0
- package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
- package/lib/src/common/package-data.mjs +10 -0
- package/lib/src/common/package-json-helper.mjs +103 -0
- package/lib/src/common/project-meta.mjs +54 -0
- package/lib/src/create-app/context-builder.mjs +21 -0
- package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
- package/lib/src/create-app/json-manager.mjs +73 -0
- package/lib/src/{prompts.mjs → create-app/new-app-prompt.mjs} +6 -6
- package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +10 -8
- package/lib/src/create-app/success-message.mjs +14 -0
- package/lib/src/update-app/conflict-report-writer.mjs +31 -0
- package/lib/src/update-app/fetch-old-templates.mjs +61 -0
- package/lib/src/update-app/project-updater.mjs +136 -0
- package/lib/src/update-app/template-differ.mjs +124 -0
- package/lib/src/update-app/update-context-builder.mjs +25 -0
- package/lib/src/update-app/update-summary-printer.mjs +22 -0
- package/lib/src/update-app/version-compare.mjs +12 -0
- package/lib/src/utils/print.mjs +36 -0
- package/lib/src/utils/promise-exec-alias.mjs +4 -0
- package/package.json +17 -12
- package/templates/base/.gitignore.hbs +6 -1
- package/templates/base/AGENTS.md.hbs +169 -0
- package/templates/base/GETTING_STARTED.md.hbs +227 -0
- package/templates/base/README.md.hbs +8 -17
- package/templates/base/config/languages.json.hbs +6 -0
- package/templates/base/config/seo.json.hbs +12 -0
- package/templates/base/nutin.config.js.hbs +51 -0
- package/templates/base/src/app/components/index.ts.hbs +1 -2
- package/templates/base/src/app/globals.d.ts.hbs +3 -19
- package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
- package/templates/base/src/app/main.ts.hbs +7 -26
- package/templates/base/src/app/services/index.ts.hbs +1 -1
- package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
- package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
- package/templates/base/src/app/views/index.ts.hbs +1 -0
- package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
- package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +68 -35
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +9 -8
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +7 -1
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
- package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
- package/templates/base/src/core/base-classes/component/component.ts.hbs +24 -33
- package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
- package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
- package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
- package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
- package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
- package/templates/base/src/core/config.ts.hbs +7 -0
- package/templates/base/src/core/index.ts.hbs +0 -3
- package/templates/base/src/core/internals.d.ts.hbs +27 -0
- package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
- package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
- package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
- package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
- package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
- package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
- package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
- package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
- package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
- package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
- package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
- package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
- package/templates/base/src/core/services/index.ts.hbs +5 -6
- package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
- package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
- package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +40 -29
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
- package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
- package/templates/base/src/core/services/router/router.ts.hbs +40 -8
- package/templates/base/src/index.html.hbs +2 -6
- package/templates/base/src/styles/_styles.scss.hbs +102 -43
- package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
- package/templates/base/src/styles/main.scss.hbs +1 -18
- package/templates/base/src/styles/tailwind.css.hbs +2 -0
- package/templates/base/tools/builder/builder.config.js.hbs +40 -0
- package/templates/base/tools/builder/builder.js.hbs +24 -26
- package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
- package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
- package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
- package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
- package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
- package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
- package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
- package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
- package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
- package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
- package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
- package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
- package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
- package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
- package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
- package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
- package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
- package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
- package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
- package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
- package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
- package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
- package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
- package/templates/base/tools/dev/serve.js.hbs +11 -1
- package/templates/base/tools/dev/watcher.js.hbs +14 -6
- package/templates/base/tools/generator/generator.js.hbs +64 -47
- package/templates/base/tools/generator/handle-file.js.hbs +17 -44
- package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
- package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
- package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
- package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
- package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/index.js.hbs +7 -12
- package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
- package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
- package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
- package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
- package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
- package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
- package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
- package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
- package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
- package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
- package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
- package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
- package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
- package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
- package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
- package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
- package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
- package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
- package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
- package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
- package/templates/base/tools/utils/error-exit.js.hbs +8 -0
- package/templates/base/tools/utils/format.js.hbs +10 -10
- package/templates/base/tools/utils/format.test.js.hbs +45 -0
- package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
- package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
- package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
- package/templates/base/tools/utils/index.js.hbs +1 -3
- package/templates/base/tools/utils/languages.js.hbs +15 -0
- package/templates/base/tools/utils/languages.test.js.hbs +16 -0
- package/templates/base/tools/utils/print.js.hbs +19 -13
- package/templates/base/tools/utils/print.test.js.hbs +60 -0
- package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
- package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
- package/templates/base/tools/utils/run-command.js.hbs +4 -2
- package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
- package/templates/base/tools/utils/run-script.js.hbs +14 -3
- package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
- package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
- package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
- package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
- package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
- package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
- package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
- package/lib/src/context-builder.mjs +0 -40
- package/lib/src/file-generator.mjs +0 -109
- package/lib/src/json-generator.mjs +0 -31
- package/lib/src/package-manager.mjs +0 -139
- package/lib/src/print.mjs +0 -32
- package/lib/src/utils.mjs +0 -18
- package/lib/src/version.mjs +0 -1
- package/templates/base/builder.config.js.hbs +0 -36
- package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
- package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
- package/templates/base/src/app/dom.test.js.hbs +0 -13
- package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
- package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
- package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
- package/templates/base/src/core/services/store/store.ts.hbs +0 -39
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -113
- package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
- package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -131
- package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
- package/templates/base/src/core/utils/index.ts.hbs +0 -2
- package/templates/base/src/libs/index.ts.hbs +0 -4
- package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +0 -197
- package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
- package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
- package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
- package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
- package/templates/base/src/styles/core/_index.scss.hbs +0 -6
- package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
- package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
- package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
- package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
- package/templates/base/tools/builder/core/sass.js.hbs +0 -22
- package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
- package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
- package/templates/base/tools/utils/export-flags.js.hbs +0 -6
- package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
- package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
- package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
- package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
- package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
- package/templates/features/i18n/src/core/config.ts.hbs +0 -3
- package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
- package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
- package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
- package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
- package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
- package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
- package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
- package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
- package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
- package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
- package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
- package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
- package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
- package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
- package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
- package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
- package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
- package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
- package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
- /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
- /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
- /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
- /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
- /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
|
@@ -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('<x>');
|
|
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('<b>');
|
|
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('<i>');
|
|
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('<i>');
|
|
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('<i>x</i>');
|
|
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('<');
|
|
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('<t>');
|
|
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('<f>');
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
+
});
|
package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs
ADDED
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Service } from '#root/dist/src/core/base-classes/service/service.js';
|
|
2
|
+
|
|
3
|
+
class TestService extends Service {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
this.destroyCalls = 0;
|
|
7
|
+
this.cleanupCalls = [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
addCleanup(fn) {
|
|
11
|
+
this.registerCleanup(fn);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
onDestroy() {
|
|
15
|
+
this.destroyCalls++;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
ping() {
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class OtherTestService extends Service {
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.destroyCalls = 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onDestroy() {
|
|
30
|
+
this.destroyCalls++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('Service', () => {
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
TestService.testingResetAll();
|
|
37
|
+
OtherTestService.testingResetAll();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('throws when instantiated directly instead of via getInstance', () => {
|
|
41
|
+
expect(() => new TestService()).toThrow('TestService is a Service. Use TestService.getInstance() instead.');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('creates the instance lazily on first getInstance call', () => {
|
|
45
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
46
|
+
const instance = TestService.getInstance();
|
|
47
|
+
expect(instance).toBeInstanceOf(TestService);
|
|
48
|
+
expect(Service.hasInstance(TestService)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns the same instance on repeated getInstance calls without args', () => {
|
|
52
|
+
const instance1 = TestService.getInstance();
|
|
53
|
+
const instance2 = TestService.getInstance();
|
|
54
|
+
expect(instance1).toBe(instance2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('throws if getInstance is called with args on an already-initialized instance', () => {
|
|
58
|
+
TestService.getInstance();
|
|
59
|
+
expect(() => TestService.getInstance('arg')).toThrow('already initialized');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does not throw when getInstance is called with args for a fresh instance', () => {
|
|
63
|
+
expect(() => TestService.getInstance()).not.toThrow();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('hasInstance reflects whether an instance has been created', () => {
|
|
67
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
68
|
+
TestService.getInstance();
|
|
69
|
+
expect(Service.hasInstance(TestService)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('registerCleanup callbacks run on dispose and are cleared afterward', () => {
|
|
73
|
+
const instance = TestService.getInstance();
|
|
74
|
+
let cleaned = false;
|
|
75
|
+
instance.addCleanup(() => { cleaned = true; });
|
|
76
|
+
|
|
77
|
+
instance.dispose();
|
|
78
|
+
|
|
79
|
+
expect(cleaned).toBe(true);
|
|
80
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('dispose clears cleanup callbacks so they only run once', () => {
|
|
84
|
+
const instance = TestService.getInstance();
|
|
85
|
+
let calls = 0;
|
|
86
|
+
instance.addCleanup(() => { calls++; });
|
|
87
|
+
|
|
88
|
+
instance.dispose();
|
|
89
|
+
instance.dispose();
|
|
90
|
+
|
|
91
|
+
expect(calls).toBe(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('destroy calls onDestroy and removes the instance from the registry', async () => {
|
|
95
|
+
const instance = TestService.getInstance();
|
|
96
|
+
await TestService.destroy();
|
|
97
|
+
|
|
98
|
+
expect(instance.destroyCalls).toBe(1);
|
|
99
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('destroy is a no-op when no instance exists', async () => {
|
|
103
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
104
|
+
await TestService.destroy();
|
|
105
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('destroyAll calls onDestroy on every instance and clears the registry', async () => {
|
|
109
|
+
const a = TestService.getInstance();
|
|
110
|
+
const b = OtherTestService.getInstance();
|
|
111
|
+
|
|
112
|
+
await Service.destroyAll();
|
|
113
|
+
|
|
114
|
+
expect(a.destroyCalls).toBe(1);
|
|
115
|
+
expect(b.destroyCalls).toBe(1);
|
|
116
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
117
|
+
expect(Service.hasInstance(OtherTestService)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('autoBindMethods binds subclass methods so they work detached from the instance', () => {
|
|
121
|
+
const instance = TestService.getInstance();
|
|
122
|
+
const { ping } = instance;
|
|
123
|
+
expect(ping()).toBe(instance);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('testingReset removes only the targeted instance without running cleanup', () => {
|
|
127
|
+
const instance1 = TestService.getInstance();
|
|
128
|
+
let cleaned = false;
|
|
129
|
+
instance1.addCleanup(() => { cleaned = true; });
|
|
130
|
+
|
|
131
|
+
TestService.testingReset();
|
|
132
|
+
|
|
133
|
+
expect(cleaned).toBe(false);
|
|
134
|
+
const instance2 = TestService.getInstance();
|
|
135
|
+
expect(instance1).not.toBe(instance2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('testingResetAll clears every instance without running cleanup', () => {
|
|
139
|
+
const a = TestService.getInstance();
|
|
140
|
+
OtherTestService.getInstance();
|
|
141
|
+
let cleaned = false;
|
|
142
|
+
a.addCleanup(() => { cleaned = true; });
|
|
143
|
+
|
|
144
|
+
Service.testingResetAll();
|
|
145
|
+
|
|
146
|
+
expect(cleaned).toBe(false);
|
|
147
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
148
|
+
expect(Service.hasInstance(OtherTestService)).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
});
|