@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,123 @@
|
|
|
1
|
+
import { Lifecycle, AppEventBus } from '#root/dist/src/core/index.js';
|
|
2
|
+
|
|
3
|
+
describe('LifecycleFacade (Lifecycle)', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
AppEventBus.cleanupEventListeners();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('beforeRender emits "before-render"', () => {
|
|
9
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
10
|
+
Lifecycle.beforeRender();
|
|
11
|
+
expect(emit).toHaveBeenCalledWith('before-render');
|
|
12
|
+
emit.restore();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('onBeforeRender subscribes to "before-render", and the returned unsubscribe removes it', () => {
|
|
16
|
+
let called = 0;
|
|
17
|
+
const unsubscribe = Lifecycle.onBeforeRender(() => called++);
|
|
18
|
+
|
|
19
|
+
Lifecycle.beforeRender();
|
|
20
|
+
expect(called).toBe(1);
|
|
21
|
+
|
|
22
|
+
unsubscribe();
|
|
23
|
+
Lifecycle.beforeRender();
|
|
24
|
+
expect(called).toBe(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('afterRender emits "after-render"', () => {
|
|
28
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
29
|
+
Lifecycle.afterRender();
|
|
30
|
+
expect(emit).toHaveBeenCalledWith('after-render');
|
|
31
|
+
emit.restore();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('onAfterRender subscribes to "after-render", and the returned unsubscribe removes it', () => {
|
|
35
|
+
let called = 0;
|
|
36
|
+
const unsubscribe = Lifecycle.onAfterRender(() => called++);
|
|
37
|
+
|
|
38
|
+
Lifecycle.afterRender();
|
|
39
|
+
expect(called).toBe(1);
|
|
40
|
+
|
|
41
|
+
unsubscribe();
|
|
42
|
+
Lifecycle.afterRender();
|
|
43
|
+
expect(called).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('beforeDestroy emits "before-destroy"', () => {
|
|
47
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
48
|
+
Lifecycle.beforeDestroy();
|
|
49
|
+
expect(emit).toHaveBeenCalledWith('before-destroy');
|
|
50
|
+
emit.restore();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('onBeforeDestroy subscribes to "before-destroy", and the returned unsubscribe removes it', () => {
|
|
54
|
+
let called = 0;
|
|
55
|
+
const unsubscribe = Lifecycle.onBeforeDestroy(() => called++);
|
|
56
|
+
|
|
57
|
+
Lifecycle.beforeDestroy();
|
|
58
|
+
expect(called).toBe(1);
|
|
59
|
+
|
|
60
|
+
unsubscribe();
|
|
61
|
+
Lifecycle.beforeDestroy();
|
|
62
|
+
expect(called).toBe(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('afterDestroy emits "after-destroy"', () => {
|
|
66
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
67
|
+
Lifecycle.afterDestroy();
|
|
68
|
+
expect(emit).toHaveBeenCalledWith('after-destroy');
|
|
69
|
+
emit.restore();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('onAfterDestroy subscribes to "after-destroy", and the returned unsubscribe removes it', () => {
|
|
73
|
+
let called = 0;
|
|
74
|
+
const unsubscribe = Lifecycle.onAfterDestroy(() => called++);
|
|
75
|
+
|
|
76
|
+
Lifecycle.afterDestroy();
|
|
77
|
+
expect(called).toBe(1);
|
|
78
|
+
|
|
79
|
+
unsubscribe();
|
|
80
|
+
Lifecycle.afterDestroy();
|
|
81
|
+
expect(called).toBe(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('viewMount emits "view-mount" with { viewName }', () => {
|
|
85
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
86
|
+
Lifecycle.viewMount('home');
|
|
87
|
+
expect(emit).toHaveBeenCalledWith('view-mount', { viewName: 'home' });
|
|
88
|
+
emit.restore();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('onViewMount subscribes to "view-mount", and the returned unsubscribe removes it', () => {
|
|
92
|
+
let received = null;
|
|
93
|
+
const unsubscribe = Lifecycle.onViewMount((data) => { received = data; });
|
|
94
|
+
|
|
95
|
+
Lifecycle.viewMount('home');
|
|
96
|
+
expect(received).toEqual({ viewName: 'home' });
|
|
97
|
+
|
|
98
|
+
unsubscribe();
|
|
99
|
+
received = null;
|
|
100
|
+
Lifecycle.viewMount('other');
|
|
101
|
+
expect(received).toBe(null);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('viewUnmount emits "view-unmount" with { viewName }', () => {
|
|
105
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
106
|
+
Lifecycle.viewUnmount('home');
|
|
107
|
+
expect(emit).toHaveBeenCalledWith('view-unmount', { viewName: 'home' });
|
|
108
|
+
emit.restore();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('onViewUnmount subscribes to "view-unmount", and the returned unsubscribe removes it', () => {
|
|
112
|
+
let received = null;
|
|
113
|
+
const unsubscribe = Lifecycle.onViewUnmount((data) => { received = data; });
|
|
114
|
+
|
|
115
|
+
Lifecycle.viewUnmount('home');
|
|
116
|
+
expect(received).toEqual({ viewName: 'home' });
|
|
117
|
+
|
|
118
|
+
unsubscribe();
|
|
119
|
+
received = null;
|
|
120
|
+
Lifecycle.viewUnmount('other');
|
|
121
|
+
expect(received).toBe(null);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AppEventBus, IEventBus } from "./event-bus.js";
|
|
2
|
+
|
|
3
|
+
class LifecycleFacade {
|
|
4
|
+
constructor(private bus: IEventBus) {}
|
|
5
|
+
|
|
6
|
+
beforeRender(): void {
|
|
7
|
+
this.bus.emit('before-render');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onBeforeRender(callback: () => void): () => void {
|
|
11
|
+
this.bus.subscribe('before-render', callback);
|
|
12
|
+
return () => this.bus.off('before-render', callback);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
afterRender(): void {
|
|
16
|
+
this.bus.emit('after-render');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
onAfterRender(callback: () => void): () => void {
|
|
20
|
+
this.bus.subscribe('after-render', callback);
|
|
21
|
+
return () => this.bus.off('after-render', callback);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
beforeDestroy(): void {
|
|
25
|
+
this.bus.emit('before-destroy');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onBeforeDestroy(callback: () => void): () => void {
|
|
29
|
+
this.bus.subscribe('before-destroy', callback);
|
|
30
|
+
return () => this.bus.off('before-destroy', callback);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
afterDestroy(): void {
|
|
34
|
+
this.bus.emit('after-destroy');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onAfterDestroy(callback: () => void): () => void {
|
|
38
|
+
this.bus.subscribe('after-destroy', callback);
|
|
39
|
+
return () => this.bus.off('after-destroy', callback);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
viewMount(viewName: string): void {
|
|
43
|
+
this.bus.emit('view-mount', {viewName});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onViewMount(callback: (data: { viewName: string }) => void): () => void {
|
|
47
|
+
this.bus.subscribe('view-mount', callback);
|
|
48
|
+
return () => this.bus.off('view-mount', callback);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
viewUnmount(viewName: string): void {
|
|
52
|
+
this.bus.emit('view-unmount', { viewName });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onViewUnmount(callback: (data: { viewName: string }) => void): () => void {
|
|
56
|
+
this.bus.subscribe('view-unmount', callback);
|
|
57
|
+
return () => this.bus.off('view-unmount', callback);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Lifecycle = new LifecycleFacade(AppEventBus);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Navigation, AppEventBus } from '#root/dist/src/core/index.js';
|
|
2
|
+
|
|
3
|
+
describe('NavigationFacade (Navigation)', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
AppEventBus.cleanupEventListeners();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('navigateTo emits "navigate" with { path }', () => {
|
|
9
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
10
|
+
Navigation.navigateTo('/about');
|
|
11
|
+
expect(emit).toHaveBeenCalledWith('navigate', { path: '/about' });
|
|
12
|
+
emit.restore();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('onNavigate subscribes to "navigate", and the returned unsubscribe removes it', () => {
|
|
16
|
+
let received = null;
|
|
17
|
+
const unsubscribe = Navigation.onNavigate((data) => { received = data; });
|
|
18
|
+
|
|
19
|
+
Navigation.navigateTo('/about');
|
|
20
|
+
expect(received).toEqual({ path: '/about' });
|
|
21
|
+
|
|
22
|
+
unsubscribe();
|
|
23
|
+
received = null;
|
|
24
|
+
Navigation.navigateTo('/contact');
|
|
25
|
+
expect(received).toBe(null);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('reload emits "reload"', () => {
|
|
29
|
+
const emit = spyOn(AppEventBus, 'emit');
|
|
30
|
+
Navigation.reload();
|
|
31
|
+
expect(emit).toHaveBeenCalledWith('reload');
|
|
32
|
+
emit.restore();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('onReload subscribes to "reload", and the returned unsubscribe removes it', () => {
|
|
36
|
+
let called = 0;
|
|
37
|
+
const unsubscribe = Navigation.onReload(() => called++);
|
|
38
|
+
|
|
39
|
+
Navigation.reload();
|
|
40
|
+
expect(called).toBe(1);
|
|
41
|
+
|
|
42
|
+
unsubscribe();
|
|
43
|
+
Navigation.reload();
|
|
44
|
+
expect(called).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AppEventBus, IEventBus } from "./event-bus.js";
|
|
2
|
+
|
|
3
|
+
class NavigationFacade {
|
|
4
|
+
constructor(private bus: IEventBus) {}
|
|
5
|
+
|
|
6
|
+
navigateTo(path: string): void {
|
|
7
|
+
this.bus.emit('navigate', { path });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onNavigate(callback: (data: { path: string }) => void): () => void {
|
|
11
|
+
this.bus.subscribe('navigate', callback);
|
|
12
|
+
return () => this.bus.off('navigate', callback);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
reload(): void {
|
|
16
|
+
this.bus.emit('reload');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
onReload(callback: () => void): () => void {
|
|
20
|
+
this.bus.subscribe('reload', callback);
|
|
21
|
+
return () => this.bus.off('reload', callback);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const Navigation = new NavigationFacade(AppEventBus);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { registerGlobals, hideGlobals, revealGlobals, Globals } from '#root/dist/src/core/services/globals/globals.js';
|
|
2
|
+
|
|
3
|
+
function makeComponent(tag = 'div') {
|
|
4
|
+
return class {
|
|
5
|
+
constructor(mountTarget) {
|
|
6
|
+
this.mountTarget = mountTarget;
|
|
7
|
+
}
|
|
8
|
+
render() {
|
|
9
|
+
return document.createElement(tag);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('globals (registerGlobals)', () => {
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
// registerGlobals mounts directly onto document.body (a sibling of the
|
|
17
|
+
// pre-existing <main id="app">), so only strip what each test added.
|
|
18
|
+
[...document.body.children].forEach((el) => {
|
|
19
|
+
if (el.id !== 'app') el.remove();
|
|
20
|
+
});
|
|
21
|
+
// Globals is a process-wide singleton (like every other Service) - drop
|
|
22
|
+
// its registered-id state so a later test's ids can't collide with an
|
|
23
|
+
// earlier one's.
|
|
24
|
+
Globals.testingResetAll();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('is defined', () => {
|
|
28
|
+
expect(registerGlobals).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('touches nothing when called with no options', () => {
|
|
32
|
+
registerGlobals({});
|
|
33
|
+
expect(document.body.children.length).toBe(1); // just #app
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('prepends a single "before" entry ahead of the existing #app, stamping its id', () => {
|
|
37
|
+
registerGlobals({ before: [{ component: makeComponent('header'), id: 'header' }] });
|
|
38
|
+
expect(document.body.firstElementChild.tagName).toBe('HEADER');
|
|
39
|
+
expect(document.body.firstElementChild.id).toBe('header');
|
|
40
|
+
expect(document.body.children[1].id).toBe('app');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('appends a single "after" entry behind the existing #app, stamping its id', () => {
|
|
44
|
+
registerGlobals({ after: [{ component: makeComponent('footer'), id: 'footer' }] });
|
|
45
|
+
expect(document.body.lastElementChild.tagName).toBe('FOOTER');
|
|
46
|
+
expect(document.body.lastElementChild.id).toBe('footer');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('mounts multiple "before" entries in the given order (not reversed)', () => {
|
|
50
|
+
registerGlobals({
|
|
51
|
+
before: [
|
|
52
|
+
{ component: makeComponent('div'), id: 'first' },
|
|
53
|
+
{ component: makeComponent('div'), id: 'second' },
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
const ids = [...document.body.children].filter((el) => el.id !== 'app').map((el) => el.id);
|
|
57
|
+
expect(ids).toEqual(['first', 'second']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('mounts multiple "after" entries in the given order (not reversed)', () => {
|
|
61
|
+
registerGlobals({
|
|
62
|
+
after: [
|
|
63
|
+
{ component: makeComponent('div'), id: 'first' },
|
|
64
|
+
{ component: makeComponent('div'), id: 'second' },
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
const ids = [...document.body.children].filter((el) => el.id !== 'app').map((el) => el.id);
|
|
68
|
+
expect(ids).toEqual(['first', 'second']);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('passes the same mountTarget value through to every constructed component', () => {
|
|
72
|
+
const seen = [];
|
|
73
|
+
class TrackingComponent {
|
|
74
|
+
constructor(mountTarget) {
|
|
75
|
+
seen.push(mountTarget);
|
|
76
|
+
}
|
|
77
|
+
render() {
|
|
78
|
+
return document.createElement('div');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
registerGlobals({
|
|
82
|
+
before: [{ component: TrackingComponent, id: 'a' }],
|
|
83
|
+
after: [{ component: TrackingComponent, id: 'b' }],
|
|
84
|
+
});
|
|
85
|
+
expect(seen).toEqual(['body', 'body']);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('registering a duplicate id warns and skips the second mount', () => {
|
|
89
|
+
const warnSpy = spyOn(console, 'warn');
|
|
90
|
+
warnSpy.andCallFake(() => {});
|
|
91
|
+
|
|
92
|
+
registerGlobals({ before: [{ component: makeComponent('header'), id: 'dup' }] });
|
|
93
|
+
registerGlobals({ after: [{ component: makeComponent('footer'), id: 'dup' }] });
|
|
94
|
+
|
|
95
|
+
const matches = [...document.body.children].filter((el) => el.id === 'dup');
|
|
96
|
+
expect(matches.length).toBe(1);
|
|
97
|
+
expect(matches[0].tagName).toBe('HEADER');
|
|
98
|
+
expect(warnSpy.callCount).toBe(1);
|
|
99
|
+
|
|
100
|
+
warnSpy.restore();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('globals (hideGlobals/revealGlobals)', () => {
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
[...document.body.children].forEach((el) => {
|
|
107
|
+
if (el.id !== 'app') el.remove();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('toggle display for every given id that is present', () => {
|
|
112
|
+
const header = document.createElement('div');
|
|
113
|
+
header.id = 'header';
|
|
114
|
+
document.body.appendChild(header);
|
|
115
|
+
const footer = document.createElement('div');
|
|
116
|
+
footer.id = 'footer';
|
|
117
|
+
document.body.appendChild(footer);
|
|
118
|
+
|
|
119
|
+
hideGlobals(['header', 'footer']);
|
|
120
|
+
expect(header.style.display).toBe('none');
|
|
121
|
+
expect(footer.style.display).toBe('none');
|
|
122
|
+
|
|
123
|
+
revealGlobals(['header', 'footer']);
|
|
124
|
+
expect(header.style.display).toBe('block');
|
|
125
|
+
expect(footer.style.display).toBe('block');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('only touch the display property, leaving other inline styles intact', () => {
|
|
129
|
+
const header = document.createElement('div');
|
|
130
|
+
header.id = 'header';
|
|
131
|
+
header.style.cssText = 'background: coral; padding: 8px';
|
|
132
|
+
document.body.appendChild(header);
|
|
133
|
+
|
|
134
|
+
hideGlobals(['header']);
|
|
135
|
+
expect(header.style.display).toBe('none');
|
|
136
|
+
expect(header.style.background).toBe('coral');
|
|
137
|
+
expect(header.style.padding).toBe('8px');
|
|
138
|
+
|
|
139
|
+
revealGlobals(['header']);
|
|
140
|
+
expect(header.style.display).toBe('block');
|
|
141
|
+
expect(header.style.background).toBe('coral');
|
|
142
|
+
expect(header.style.padding).toBe('8px');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('are no-ops for ids that are absent', () => {
|
|
146
|
+
expect(() => hideGlobals(['missing'])).not.toThrow();
|
|
147
|
+
expect(() => revealGlobals(['missing'])).not.toThrow();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Service } from '../../base-classes/index.js';
|
|
2
|
+
|
|
3
|
+
export interface GlobalMountable {
|
|
4
|
+
render(): HTMLElement;
|
|
5
|
+
destroy?(): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface GlobalConfig<T extends GlobalMountable = GlobalMountable> {
|
|
9
|
+
/** The component's own class — constructed internally as `new component(mountTarget)`. */
|
|
10
|
+
component: new (mountTarget: HTMLElement) => T;
|
|
11
|
+
/** Stamped onto the mounted root element; pass this same id to `hideGlobals`/`revealGlobals`. */
|
|
12
|
+
id: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RegisterGlobalsOptions {
|
|
16
|
+
/** Rendered and prepended to `<body>`, in the given order (e.g. a header). */
|
|
17
|
+
before?: GlobalConfig[];
|
|
18
|
+
/** Rendered and appended to `<body>`, in the given order (e.g. a footer). */
|
|
19
|
+
after?: GlobalConfig[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class Globals extends Service<Globals> {
|
|
23
|
+
private _mounted: Record<string, GlobalMountable> = {};
|
|
24
|
+
|
|
25
|
+
constructor() { super(); }
|
|
26
|
+
|
|
27
|
+
public register({ before = [], after = [] }: RegisterGlobalsOptions): void {
|
|
28
|
+
// Component subclasses generated via `nutin generate component` type their
|
|
29
|
+
// mountTarget param as HTMLElement, but DomHelper.mountElement only
|
|
30
|
+
// *appends* (rather than replacing) the target when mountTarget is a string
|
|
31
|
+
// selector. This cast satisfies the stricter constructor type while still
|
|
32
|
+
// passing the string 'body' through at runtime, so components append into
|
|
33
|
+
// <body> instead of replacing it.
|
|
34
|
+
const mountTarget = 'body' as unknown as HTMLElement;
|
|
35
|
+
const isElement = (el: HTMLElement | null): el is HTMLElement => el !== null;
|
|
36
|
+
|
|
37
|
+
const mount = ({ component: Component, id }: GlobalConfig): HTMLElement | null => {
|
|
38
|
+
if (this._mounted[id]) {
|
|
39
|
+
console.warn(`Global "${id}" is already registered - skipping.`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const instance = new Component(mountTarget);
|
|
43
|
+
const element = instance.render();
|
|
44
|
+
element.id = id;
|
|
45
|
+
this._mounted[id] = instance;
|
|
46
|
+
return element;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
document.body.prepend(...before.map(mount).filter(isElement));
|
|
50
|
+
document.body.append(...after.map(mount).filter(isElement));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public hide(globalIds: string[]): void {
|
|
54
|
+
this.setDisplay(globalIds, 'none');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public reveal(globalIds: string[]): void {
|
|
58
|
+
this.setDisplay(globalIds, 'block');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private setDisplay(globalIds: string[], display: 'none' | 'block'): void {
|
|
62
|
+
globalIds.forEach((id) => {
|
|
63
|
+
const el = document.getElementById(id) as HTMLElement | null;
|
|
64
|
+
if (el) el.style.display = display;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected onDestroy(): void {
|
|
69
|
+
Object.values(this._mounted).forEach((instance) => instance.destroy?.());
|
|
70
|
+
this._mounted = {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Mounts components outside the router's render target (`<main id="app">`),
|
|
76
|
+
* e.g. a header/footer that should persist across every route. Each entry's
|
|
77
|
+
* `component` is constructed internally — callers never see or handle
|
|
78
|
+
* `mountTarget` themselves — and its rendered root element is given the
|
|
79
|
+
* entry's `id`, so `hideGlobals(ids)`/`revealGlobals(ids)` can target it
|
|
80
|
+
* later by that same id. Registering an `id` that's already registered logs
|
|
81
|
+
* a `console.warn` and skips it.
|
|
82
|
+
*/
|
|
83
|
+
export const registerGlobals = (options: RegisterGlobalsOptions): void => {
|
|
84
|
+
Globals.getInstance().register(options);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Hide the elements with the given ids (as registered via `registerGlobals`). */
|
|
88
|
+
export const hideGlobals = (globalIds: string[]): void => {
|
|
89
|
+
Globals.getInstance().hide(globalIds);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Reveal the elements with the given ids (as registered via `registerGlobals`). */
|
|
93
|
+
export const revealGlobals = (globalIds: string[]): void => {
|
|
94
|
+
Globals.getInstance().reveal(globalIds);
|
|
95
|
+
};
|
package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs
CHANGED
|
@@ -75,6 +75,11 @@ describe('HttpBuilder', () => {
|
|
|
75
75
|
expect(url.search).toBe('?page=1&limit=10');
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
it('buildRequestUrl leaves an existing query in the endpoint untouched when no new params are passed', () => {
|
|
79
|
+
const url = HttpBuilder.buildRequestUrl('https://api.example.com?page=1');
|
|
80
|
+
expect(url.search).toBe('?page=1');
|
|
81
|
+
});
|
|
82
|
+
|
|
78
83
|
// Tests for buildRequestOptions
|
|
79
84
|
it('buildRequestOptions sets GET without body', () => {
|
|
80
85
|
const options = HttpBuilder.buildRequestOptions(
|
|
@@ -106,4 +111,24 @@ describe('HttpBuilder', () => {
|
|
|
106
111
|
expect(options.body).toBeUndefined();
|
|
107
112
|
expect(options.headers).toBeUndefined();
|
|
108
113
|
});
|
|
114
|
+
|
|
115
|
+
it('buildRequestOptions sets PUT with body', () => {
|
|
116
|
+
const data = { id: 1, name: 'Updated' };
|
|
117
|
+
const options = HttpBuilder.buildRequestOptions('PUT', data, {}, mockAbortSignal);
|
|
118
|
+
expect(options.method).toBe('PUT');
|
|
119
|
+
expect(options.body).toBe(JSON.stringify(data));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('buildRequestOptions sets PATCH with body', () => {
|
|
123
|
+
const data = { name: 'Patched' };
|
|
124
|
+
const options = HttpBuilder.buildRequestOptions('PATCH', data, {}, mockAbortSignal);
|
|
125
|
+
expect(options.method).toBe('PATCH');
|
|
126
|
+
expect(options.body).toBe(JSON.stringify(data));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('buildRequestOptions sets DELETE without body', () => {
|
|
130
|
+
const options = HttpBuilder.buildRequestOptions('DELETE', undefined, {}, mockAbortSignal);
|
|
131
|
+
expect(options.method).toBe('DELETE');
|
|
132
|
+
expect(options.body).toBeUndefined();
|
|
133
|
+
});
|
|
109
134
|
})
|
package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs
CHANGED
|
@@ -55,6 +55,26 @@ describe('HttpManager', () => {
|
|
|
55
55
|
expect(thrown).toBe(true);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
it('should throw HttpError with a null response body when the error body is not valid JSON', async () => {
|
|
59
|
+
const mockResponse = new Response('plain text failure', {
|
|
60
|
+
status: 500,
|
|
61
|
+
statusText: 'Internal Server Error',
|
|
62
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
let thrown = false;
|
|
66
|
+
try {
|
|
67
|
+
await HttpManager.validateResponse(mockResponse);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
thrown = true;
|
|
70
|
+
expect(e).toBeInstanceOf(HttpError);
|
|
71
|
+
expect(e.status).toBe(500);
|
|
72
|
+
expect(e.statusText).toBe('Internal Server Error');
|
|
73
|
+
expect(e.response).toBe(null);
|
|
74
|
+
}
|
|
75
|
+
expect(thrown).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
58
78
|
it('should detect JSON content types', () => {
|
|
59
79
|
expect(HttpManager.isJsonResponse('application/json')).toBe(true);
|
|
60
80
|
expect(HttpManager.isJsonResponse('application/json; charset=utf-8')).toBe(true);
|
|
@@ -204,6 +204,49 @@ describe('HttpClient', () => {
|
|
|
204
204
|
expect(lastCall.options.headers['Authorization']).toBe('Bearer custom-token');
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
+
it('should invoke request interceptors with the final url and options before sending', async () => {
|
|
208
|
+
fetchMock.mockJsonResponse('https://api.example.com/users', []);
|
|
209
|
+
const calls = [];
|
|
210
|
+
httpClient.addRequestInterceptor((url, options) => calls.push({ url, options }));
|
|
211
|
+
|
|
212
|
+
await httpClient.get('https://api.example.com/users');
|
|
213
|
+
|
|
214
|
+
expect(calls.length).toBe(1);
|
|
215
|
+
expect(calls[0].url).toBe('https://api.example.com/users');
|
|
216
|
+
expect(calls[0].options.method).toBe('GET');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should invoke response interceptors with the raw fetch response', async () => {
|
|
220
|
+
fetchMock.mockJsonResponse('https://api.example.com/users', []);
|
|
221
|
+
const received = [];
|
|
222
|
+
httpClient.addResponseInterceptor((response) => received.push(response));
|
|
223
|
+
|
|
224
|
+
await httpClient.get('https://api.example.com/users');
|
|
225
|
+
|
|
226
|
+
expect(received.length).toBe(1);
|
|
227
|
+
expect(received[0].ok).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should propagate a non-timeout error thrown while parsing the response', async () => {
|
|
231
|
+
fetchMock.mockResponse('https://api.example.com/broken', {
|
|
232
|
+
ok: true,
|
|
233
|
+
status: 200,
|
|
234
|
+
statusText: 'OK',
|
|
235
|
+
headers: { get: () => 'application/json' },
|
|
236
|
+
json: () => Promise.reject(new Error('Malformed JSON')),
|
|
237
|
+
text: () => Promise.resolve('not json'),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
let thrown = false;
|
|
241
|
+
try {
|
|
242
|
+
await httpClient.get('https://api.example.com/broken');
|
|
243
|
+
} catch (error) {
|
|
244
|
+
thrown = true;
|
|
245
|
+
expect(error.message).toBe('Malformed JSON');
|
|
246
|
+
}
|
|
247
|
+
expect(thrown).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
207
250
|
it('should handle HTTP errors', async () => {
|
|
208
251
|
fetchMock.mockError('https://api.example.com/users', 404, { message: 'Not found' });
|
|
209
252
|
|