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