@nutin/cli 1.3.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -9
- package/README.md +17 -35
- package/bin/add-feature.mjs +9 -0
- package/bin/cli.mjs +8 -0
- package/bin/create-app.mjs +5 -4
- package/bin/update-app.mjs +9 -0
- package/lib/index.mjs +76 -25
- package/lib/src/add-feature/feature-adder.mjs +83 -0
- package/lib/src/add-feature/feature-context-builder.mjs +25 -0
- package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
- package/lib/src/add-feature/package-json-updater.mjs +49 -0
- package/lib/src/common/feature-registry.mjs +11 -0
- package/lib/src/common/file-generator.mjs +159 -0
- package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
- package/lib/src/common/package-data.mjs +10 -0
- package/lib/src/common/package-json-helper.mjs +103 -0
- package/lib/src/common/project-meta.mjs +54 -0
- package/lib/src/create-app/context-builder.mjs +21 -0
- package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
- package/lib/src/create-app/json-manager.mjs +73 -0
- package/lib/src/{prompts.mjs → create-app/new-app-prompt.mjs} +6 -6
- package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +10 -8
- package/lib/src/create-app/success-message.mjs +13 -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 +142 -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 +168 -0
- package/templates/base/GETTING_STARTED.md.hbs +199 -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 +44 -0
- package/templates/base/src/app/components/index.ts.hbs +1 -2
- package/templates/base/src/app/globals.d.ts.hbs +3 -19
- package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
- package/templates/base/src/app/main.ts.hbs +6 -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 +2 -57
- 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 +5 -38
- package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +68 -35
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +9 -8
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +96 -0
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +6 -3
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +151 -0
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +8 -2
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +89 -0
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +4 -1
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
- package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
- package/templates/base/src/core/base-classes/component/component.ts.hbs +24 -33
- package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
- package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
- package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
- package/templates/base/src/core/base-classes/view/view.test.js.hbs +71 -0
- package/templates/base/src/core/base-classes/view/view.ts.hbs +24 -49
- package/templates/base/src/core/config.ts.hbs +10 -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 +187 -0
- package/templates/base/src/core/services/globals/globals.ts.hbs +104 -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 +73 -2
- package/templates/base/src/core/services/http-client/http-client.ts.hbs +4 -1
- package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
- package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
- package/templates/base/src/core/services/index.ts.hbs +5 -6
- package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
- package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
- package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +257 -6
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +56 -29
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
- package/templates/base/src/core/services/router/router.test.js.hbs +304 -2
- package/templates/base/src/core/services/router/router.ts.hbs +47 -12
- package/templates/base/src/index.html.hbs +2 -6
- package/templates/base/src/styles/_styles.scss.hbs +102 -43
- package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
- package/templates/base/src/styles/main.scss.hbs +1 -18
- package/templates/base/src/styles/tailwind.css.hbs +2 -0
- package/templates/base/tools/builder/builder.config.js.hbs +40 -0
- package/templates/base/tools/builder/builder.js.hbs +24 -26
- package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
- package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
- package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
- package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
- package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
- package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
- package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
- package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
- package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
- package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
- package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
- package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
- package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
- package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
- package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
- package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
- package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
- package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
- package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
- package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
- package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
- package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
- package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
- package/templates/base/tools/dev/serve.js.hbs +11 -1
- package/templates/base/tools/dev/watcher.js.hbs +14 -6
- package/templates/base/tools/generator/generator.js.hbs +44 -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 +8 -0
- package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +17 -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} +4 -3
- package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
- package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
- package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
- package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
- package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
- package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
- package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
- package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
- package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
- package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
- package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
- package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
- package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
- package/templates/base/tools/utils/error-exit.js.hbs +8 -0
- package/templates/base/tools/utils/format.js.hbs +10 -10
- package/templates/base/tools/utils/format.test.js.hbs +45 -0
- package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
- package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
- package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
- package/templates/base/tools/utils/index.js.hbs +1 -3
- package/templates/base/tools/utils/languages.js.hbs +15 -0
- package/templates/base/tools/utils/languages.test.js.hbs +16 -0
- package/templates/base/tools/utils/print.js.hbs +19 -13
- package/templates/base/tools/utils/print.test.js.hbs +60 -0
- package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
- package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
- package/templates/base/tools/utils/run-command.js.hbs +4 -2
- package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
- package/templates/base/tools/utils/run-script.js.hbs +14 -3
- package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
- package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
- package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
- package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
- package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
- package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
- package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
- package/lib/src/context-builder.mjs +0 -40
- package/lib/src/file-generator.mjs +0 -109
- package/lib/src/json-generator.mjs +0 -31
- package/lib/src/package-manager.mjs +0 -139
- package/lib/src/print.mjs +0 -32
- package/lib/src/utils.mjs +0 -18
- package/lib/src/version.mjs +0 -1
- package/templates/base/builder.config.js.hbs +0 -36
- package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
- package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
- package/templates/base/src/app/dom.test.js.hbs +0 -13
- package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
- package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
- package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
- package/templates/base/src/core/services/store/store.ts.hbs +0 -39
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -113
- package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
- package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -131
- package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
- package/templates/base/src/core/utils/index.ts.hbs +0 -2
- package/templates/base/src/libs/index.ts.hbs +0 -4
- package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +0 -197
- package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
- package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
- package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
- package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
- package/templates/base/src/styles/core/_index.scss.hbs +0 -6
- package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
- package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
- package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
- package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
- package/templates/base/tools/builder/core/sass.js.hbs +0 -22
- package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
- package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
- package/templates/base/tools/utils/export-flags.js.hbs +0 -6
- package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
- package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
- package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
- package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
- package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
- package/templates/features/i18n/src/core/config.ts.hbs +0 -3
- package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
- package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
- package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
- package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
- package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
- package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
- package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
- package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
- package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
- package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
- package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
- package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
- package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
- package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
- package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
- package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
- package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
- package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
- package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
- /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
- /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
- /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
- /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
- /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { NavigationManager } from '#root/dist/src/core/services/router/helpers/navigation-manager.helper.js';
|
|
2
|
+
import { I18nService } from '#root/dist/src/core/services/index.js';
|
|
3
|
+
import { CONFIG } from '#root/dist/src/core/config.js';
|
|
2
4
|
|
|
3
5
|
describe('NavigationManager', () => {
|
|
4
6
|
it('should normalize paths by removing trailing slashes', () => {
|
|
@@ -8,12 +10,6 @@ describe('NavigationManager', () => {
|
|
|
8
10
|
expect(NavigationManager.normalizePath('')).toBe('/');
|
|
9
11
|
});
|
|
10
12
|
|
|
11
|
-
it('should determine if pushState should be used', () => {
|
|
12
|
-
expect(NavigationManager.shouldPushState(true, '/home', '/about')).toBe(true);
|
|
13
|
-
expect(NavigationManager.shouldPushState(false, '/home', '/about')).toBe(false);
|
|
14
|
-
expect(NavigationManager.shouldPushState(true, '/about', '/about')).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
13
|
it('should call pushState when conditions are met', () => {
|
|
18
14
|
const originalPushState = window.history.pushState;
|
|
19
15
|
let called = false;
|
|
@@ -48,4 +44,259 @@ describe('NavigationManager', () => {
|
|
|
48
44
|
const currentPath = window.location.pathname;
|
|
49
45
|
expect(NavigationManager.getCurrentPath()).toBe(currentPath);
|
|
50
46
|
});
|
|
47
|
+
|
|
48
|
+
it('extractLocale returns the original path untouched when CONFIG.i18n is disabled', () => {
|
|
49
|
+
CONFIG.i18n = false;
|
|
50
|
+
expect(NavigationManager.extractLocale('/fr/about')).toEqual({ locale: null, strippedPath: '/fr/about' });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('extractLocale strips a recognized locale prefix when CONFIG.i18n is enabled', () => {
|
|
54
|
+
CONFIG.i18n = true;
|
|
55
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
56
|
+
try {
|
|
57
|
+
expect(NavigationManager.extractLocale('/fr/about')).toEqual({ locale: 'fr', strippedPath: '/about' });
|
|
58
|
+
expect(NavigationManager.extractLocale('/fr')).toEqual({ locale: 'fr', strippedPath: '/' });
|
|
59
|
+
} finally {
|
|
60
|
+
CONFIG.i18n = false;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('extractLocale leaves the path untouched when the first segment is not a recognized locale', () => {
|
|
65
|
+
CONFIG.i18n = true;
|
|
66
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
67
|
+
try {
|
|
68
|
+
expect(NavigationManager.extractLocale('/about')).toEqual({ locale: null, strippedPath: '/about' });
|
|
69
|
+
} finally {
|
|
70
|
+
CONFIG.i18n = false;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('addLocalePrefix returns the path unchanged when CONFIG.i18n is disabled', () => {
|
|
75
|
+
CONFIG.i18n = false;
|
|
76
|
+
expect(NavigationManager.addLocalePrefix('/about')).toBe('/about');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('addLocalePrefix prefixes the path with the current language when CONFIG.i18n is enabled', () => {
|
|
80
|
+
CONFIG.i18n = true;
|
|
81
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
82
|
+
I18nService['_currentLanguage'] = 'fr';
|
|
83
|
+
try {
|
|
84
|
+
expect(NavigationManager.addLocalePrefix('/about')).toBe('/fr/about');
|
|
85
|
+
expect(NavigationManager.addLocalePrefix('/')).toBe('/fr');
|
|
86
|
+
} finally {
|
|
87
|
+
CONFIG.i18n = false;
|
|
88
|
+
I18nService['_currentLanguage'] = 'en';
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('getCurrentLocale reads the locale from the current URL path when CONFIG.i18n is enabled', () => {
|
|
93
|
+
CONFIG.i18n = true;
|
|
94
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
95
|
+
window.history.pushState({}, '', '/fr/about');
|
|
96
|
+
try {
|
|
97
|
+
expect(NavigationManager.getCurrentLocale()).toBe('fr');
|
|
98
|
+
} finally {
|
|
99
|
+
CONFIG.i18n = false;
|
|
100
|
+
window.history.pushState({}, '', '/');
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('getCurrentLocale returns null when CONFIG.i18n is disabled', () => {
|
|
105
|
+
window.history.pushState({}, '', '/fr/about');
|
|
106
|
+
try {
|
|
107
|
+
expect(NavigationManager.getCurrentLocale()).toBe(null);
|
|
108
|
+
} finally {
|
|
109
|
+
window.history.pushState({}, '', '/');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('updateLocaleInUrl pushes a new URL when the locale-prefixed path differs from the current one', () => {
|
|
114
|
+
CONFIG.i18n = true;
|
|
115
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
116
|
+
I18nService['_currentLanguage'] = 'fr';
|
|
117
|
+
window.history.pushState({}, '', '/about');
|
|
118
|
+
|
|
119
|
+
const originalPushState = window.history.pushState;
|
|
120
|
+
const calls = [];
|
|
121
|
+
window.history.pushState = (...args) => {
|
|
122
|
+
calls.push(args);
|
|
123
|
+
originalPushState.apply(window.history, args);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
NavigationManager.updateLocaleInUrl();
|
|
128
|
+
expect(calls.length).toBe(1);
|
|
129
|
+
expect(calls[0][2]).toBe('/fr/about');
|
|
130
|
+
} finally {
|
|
131
|
+
window.history.pushState = originalPushState;
|
|
132
|
+
CONFIG.i18n = false;
|
|
133
|
+
I18nService['_currentLanguage'] = 'en';
|
|
134
|
+
window.history.pushState({}, '', '/');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('updateLocaleInUrl handles a root pathname that collapses to an empty string when stripped', () => {
|
|
139
|
+
CONFIG.i18n = false;
|
|
140
|
+
window.history.pushState({}, '', '/');
|
|
141
|
+
|
|
142
|
+
const originalPushState = window.history.pushState;
|
|
143
|
+
window.history.pushState = () => {};
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
expect(() => NavigationManager.updateLocaleInUrl()).not.toThrow();
|
|
147
|
+
} finally {
|
|
148
|
+
window.history.pushState = originalPushState;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('updateLocaleInUrl does nothing when the URL already has the correct locale prefix', () => {
|
|
153
|
+
CONFIG.i18n = true;
|
|
154
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
155
|
+
I18nService['_currentLanguage'] = 'fr';
|
|
156
|
+
window.history.pushState({}, '', '/fr/about');
|
|
157
|
+
|
|
158
|
+
const originalPushState = window.history.pushState;
|
|
159
|
+
let called = false;
|
|
160
|
+
window.history.pushState = () => { called = true; };
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
NavigationManager.updateLocaleInUrl();
|
|
164
|
+
expect(called).toBe(false);
|
|
165
|
+
} finally {
|
|
166
|
+
window.history.pushState = originalPushState;
|
|
167
|
+
CONFIG.i18n = false;
|
|
168
|
+
I18nService['_currentLanguage'] = 'en';
|
|
169
|
+
window.history.pushState({}, '', '/');
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('matchPattern matches a required param', () => {
|
|
174
|
+
expect(NavigationManager.matchPattern('/posts/:id', '/posts/123')).toEqual({ id: '123' });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('matchPattern does not match when a required param segment is missing', () => {
|
|
178
|
+
expect(NavigationManager.matchPattern('/posts/:id', '/posts')).toBe(null);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('matchPattern matches an optional param when present, and omits it when absent', () => {
|
|
182
|
+
expect(NavigationManager.matchPattern('/users/:id?', '/users/123')).toEqual({ id: '123' });
|
|
183
|
+
expect(NavigationManager.matchPattern('/users/:id?', '/users')).toEqual({});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('matchPattern matches multiple params in the same pattern', () => {
|
|
187
|
+
expect(NavigationManager.matchPattern('/posts/:postId/comments/:commentId', '/posts/1/comments/2'))
|
|
188
|
+
.toEqual({ postId: '1', commentId: '2' });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('matchPattern returns null when the path does not match the pattern at all', () => {
|
|
192
|
+
expect(NavigationManager.matchPattern('/posts/:id', '/other/123')).toBe(null);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('updateDocumentTitle falls back to the view\'s viewName when generateSEOFiles is disabled', () => {
|
|
196
|
+
CONFIG.generateSEOFiles = false;
|
|
197
|
+
CONFIG.seo = { routes: [{ path: '/', title: 'Should not be used' }] };
|
|
198
|
+
try {
|
|
199
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
200
|
+
expect(document.title).toBe('home');
|
|
201
|
+
} finally {
|
|
202
|
+
CONFIG.generateSEOFiles = false;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('updateDocumentTitle falls back to the view\'s viewName when no seo.json route matches the pattern', () => {
|
|
207
|
+
CONFIG.generateSEOFiles = true;
|
|
208
|
+
CONFIG.seo = { routes: [{ path: '/other', title: 'Other' }] };
|
|
209
|
+
try {
|
|
210
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
211
|
+
expect(document.title).toBe('home');
|
|
212
|
+
} finally {
|
|
213
|
+
CONFIG.generateSEOFiles = false;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('updateDocumentTitle uses a flat seo.json title when generateSEOFiles is enabled and the pattern matches', () => {
|
|
218
|
+
CONFIG.generateSEOFiles = true;
|
|
219
|
+
CONFIG.seo = { routes: [{ path: '/', title: 'My App — Home' }] };
|
|
220
|
+
try {
|
|
221
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
222
|
+
expect(document.title).toBe('My App — Home');
|
|
223
|
+
} finally {
|
|
224
|
+
CONFIG.generateSEOFiles = false;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('updateDocumentTitle resolves a per-language seo.json title to the current language', () => {
|
|
229
|
+
CONFIG.generateSEOFiles = true;
|
|
230
|
+
CONFIG.seo = { routes: [{ path: '/', title: { en: 'Home', fr: 'Accueil' } }] };
|
|
231
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
232
|
+
I18nService['_currentLanguage'] = 'fr';
|
|
233
|
+
try {
|
|
234
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
235
|
+
expect(document.title).toBe('Accueil');
|
|
236
|
+
} finally {
|
|
237
|
+
CONFIG.generateSEOFiles = false;
|
|
238
|
+
I18nService['_currentLanguage'] = 'en';
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('updateDocumentTitle falls back to the default language, then any available language, then viewName', () => {
|
|
243
|
+
CONFIG.generateSEOFiles = true;
|
|
244
|
+
I18nService['_LANGUAGES'] = ['en', 'fr'];
|
|
245
|
+
|
|
246
|
+
CONFIG.seo = { routes: [{ path: '/', title: { fr: 'Accueil' } }] };
|
|
247
|
+
I18nService['_currentLanguage'] = 'en';
|
|
248
|
+
try {
|
|
249
|
+
// current lang ('en') missing -> falls back to default lang's value, present here as 'fr' only,
|
|
250
|
+
// so it falls further to the first available value
|
|
251
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
252
|
+
expect(document.title).toBe('Accueil');
|
|
253
|
+
|
|
254
|
+
CONFIG.seo = { routes: [{ path: '/', title: {} }] };
|
|
255
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
256
|
+
expect(document.title).toBe('home');
|
|
257
|
+
} finally {
|
|
258
|
+
CONFIG.generateSEOFiles = false;
|
|
259
|
+
I18nService['_currentLanguage'] = 'en';
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('updateDocumentTitle uses the view\'s locale title when i18n is enabled and no seo.json route matches', () => {
|
|
264
|
+
CONFIG.i18n = true;
|
|
265
|
+
CONFIG.generateSEOFiles = false;
|
|
266
|
+
I18nService['_translations'] = { home: { title: 'Accueil' } };
|
|
267
|
+
try {
|
|
268
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
269
|
+
expect(document.title).toBe('Accueil');
|
|
270
|
+
} finally {
|
|
271
|
+
CONFIG.i18n = false;
|
|
272
|
+
I18nService['_translations'] = {};
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('updateDocumentTitle prefers seo.json title over the locale title when both are present', () => {
|
|
277
|
+
CONFIG.i18n = true;
|
|
278
|
+
CONFIG.generateSEOFiles = true;
|
|
279
|
+
CONFIG.seo = { routes: [{ path: '/', title: 'From seo.json' }] };
|
|
280
|
+
I18nService['_translations'] = { home: { title: 'From locale' } };
|
|
281
|
+
try {
|
|
282
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
283
|
+
expect(document.title).toBe('From seo.json');
|
|
284
|
+
} finally {
|
|
285
|
+
CONFIG.i18n = false;
|
|
286
|
+
CONFIG.generateSEOFiles = false;
|
|
287
|
+
I18nService['_translations'] = {};
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('updateDocumentTitle ignores the locale title when i18n is disabled, falling back to viewName', () => {
|
|
292
|
+
CONFIG.i18n = false;
|
|
293
|
+
CONFIG.generateSEOFiles = false;
|
|
294
|
+
I18nService['_translations'] = { home: { title: 'Should not be used' } };
|
|
295
|
+
try {
|
|
296
|
+
NavigationManager.updateDocumentTitle({ viewName: 'home' }, '/');
|
|
297
|
+
expect(document.title).toBe('home');
|
|
298
|
+
} finally {
|
|
299
|
+
I18nService['_translations'] = {};
|
|
300
|
+
}
|
|
301
|
+
});
|
|
51
302
|
});
|
|
@@ -1,13 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I18nService, View } from "../../../index.js";
|
|
2
|
+
import { Language } from "../../i18n/languages.js";
|
|
3
|
+
import { CONFIG } from "../../../config.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Navigation - handles path normalization and history management
|
|
5
7
|
*/
|
|
6
8
|
export class NavigationManager {
|
|
9
|
+
public static extractLocale(path: string): { locale: Language | null; strippedPath: string } {
|
|
10
|
+
if (!CONFIG.i18n) return { locale: null, strippedPath: path };
|
|
11
|
+
|
|
12
|
+
const segments = path.split('/').filter(Boolean);
|
|
13
|
+
const first = segments[0] as Language;
|
|
14
|
+
if (first && I18nService.languages.includes(first)) {
|
|
15
|
+
const rest = segments.slice(1).join('/');
|
|
16
|
+
return { locale: first, strippedPath: rest ? `/${rest}` : '/' };
|
|
17
|
+
}
|
|
18
|
+
return { locale: null, strippedPath: path };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static addLocalePrefix(strippedPath: string): string {
|
|
22
|
+
if (!CONFIG.i18n) return strippedPath;
|
|
23
|
+
|
|
24
|
+
const lang = I18nService.currentLanguage;
|
|
25
|
+
return strippedPath === '/' ? `/${lang}` : `/${lang}${strippedPath}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static getCurrentLocale(): Language | null {
|
|
29
|
+
return this.extractLocale(window.location.pathname).locale;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static updateLocaleInUrl(): void {
|
|
33
|
+
const rawPathname = (new URL(window.location.pathname, window.location.origin).pathname || '/').replace(/\/+$/, '') || '/';
|
|
34
|
+
const { strippedPath } = this.extractLocale(rawPathname);
|
|
35
|
+
const newUrl = this.addLocalePrefix(strippedPath);
|
|
36
|
+
if (newUrl !== window.location.pathname) {
|
|
37
|
+
window.history.pushState({}, '', newUrl);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
7
41
|
public static normalizePath(path: string): string {
|
|
8
42
|
const collapsed = path.replace(/\/\/+/g, '/');
|
|
9
43
|
const url = new URL(collapsed, window.location.origin);
|
|
10
|
-
|
|
44
|
+
const pathname = (url.pathname || '/').replace(/\/+$/, '') || '/';
|
|
45
|
+
return this.extractLocale(pathname).strippedPath;
|
|
11
46
|
}
|
|
12
47
|
|
|
13
48
|
public static updateHistory(
|
|
@@ -15,32 +50,32 @@ export class NavigationManager {
|
|
|
15
50
|
currentPath: string,
|
|
16
51
|
pushState: boolean
|
|
17
52
|
): void {
|
|
18
|
-
|
|
19
|
-
|
|
53
|
+
const localizedPath = this.addLocalePrefix(normalizedPath);
|
|
54
|
+
if (pushState && window.location.pathname !== localizedPath) {
|
|
55
|
+
window.history.pushState({}, '', localizedPath);
|
|
20
56
|
}
|
|
21
57
|
}
|
|
22
58
|
|
|
23
|
-
public static updateMetaContent(currentView: View): void {
|
|
24
|
-
if (!currentView.shouldUpdateMetaContent()) return;
|
|
25
|
-
|
|
26
|
-
const strippedName = currentView.viewName.replace('-view', '');
|
|
27
|
-
{{#if i18n}}
|
|
28
|
-
const title = `${strippedName}.meta.title`;
|
|
29
|
-
const description = `${strippedName}.meta.description`;
|
|
30
|
-
|
|
31
|
-
document.title = I18nService.translate(title);
|
|
32
|
-
document.querySelector('meta[name="description"]')
|
|
33
|
-
?.setAttribute('content', I18nService.translate(description));
|
|
34
|
-
{{else}}
|
|
35
|
-
|
|
36
|
-
document.title = strippedName.charAt(0).toUpperCase() + strippedName.slice(1).toLowerCase();
|
|
37
|
-
{{/if}}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
59
|
public static getCurrentPath(): string {
|
|
41
60
|
return this.normalizePath(window.location.pathname);
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
public static updateDocumentTitle(view: View, pattern: string): void {
|
|
64
|
+
const route = CONFIG.generateSEOFiles
|
|
65
|
+
? CONFIG.seo?.routes?.find((r) => r.path === pattern)
|
|
66
|
+
: undefined;
|
|
67
|
+
const seoTitle = route?.title ? this.resolveSeoTitle(route.title) : undefined;
|
|
68
|
+
const localeTitle = CONFIG.i18n ? I18nService.getTranslationObject<string>(`${view.viewName}.title`) : null;
|
|
69
|
+
document.title = seoTitle || localeTitle || view.viewName;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private static resolveSeoTitle(title: string | Record<string, string>): string | undefined {
|
|
73
|
+
if (typeof title !== 'object') return title;
|
|
74
|
+
const lang = I18nService.currentLanguage;
|
|
75
|
+
const defaultLang = I18nService.defaultLanguage;
|
|
76
|
+
return title[lang] ?? title[defaultLang] ?? Object.values(title)[0];
|
|
77
|
+
}
|
|
78
|
+
|
|
44
79
|
public static matchPattern(pattern: string, path: string): Record<string, string> | null {
|
|
45
80
|
const paramNames: string[] = [];
|
|
46
81
|
|
|
@@ -68,12 +103,4 @@ export class NavigationManager {
|
|
|
68
103
|
});
|
|
69
104
|
return params;
|
|
70
105
|
}
|
|
71
|
-
|
|
72
|
-
private static shouldPushState(
|
|
73
|
-
pushState: boolean,
|
|
74
|
-
currentPath: string,
|
|
75
|
-
normalizedPath: string
|
|
76
|
-
): boolean {
|
|
77
|
-
return pushState && currentPath !== normalizedPath;
|
|
78
|
-
}
|
|
79
106
|
}
|
package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs
CHANGED
|
@@ -29,6 +29,12 @@ describe('RouteGuardsManager', () => {
|
|
|
29
29
|
expect(JSON.stringify(result)).toBe(JSON.stringify([guard]));
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
it('should default to an empty guards array when an object route config has no guards', () => {
|
|
33
|
+
const config = { view: mockViewConstructor };
|
|
34
|
+
const result = RouteGuardsManager.getRouteGuards(config);
|
|
35
|
+
expect(result).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
|
|
32
38
|
it('should pass all guards and return true from runGuards', async () => {
|
|
33
39
|
const guards = [() => true, () => Promise.resolve(true)];
|
|
34
40
|
const result = await RouteGuardsManager.runGuards(guards);
|
|
@@ -70,4 +76,25 @@ describe('RouteGuardsManager', () => {
|
|
|
70
76
|
expect(typeof result.viewConstructor).toBe('function');
|
|
71
77
|
expect(result.redirectTo).toBeUndefined();
|
|
72
78
|
});
|
|
79
|
+
|
|
80
|
+
it('passes the actual route params through processRouteGuards to each guard', async () => {
|
|
81
|
+
const receivedParams = [];
|
|
82
|
+
const guard = (params) => { receivedParams.push(params); return true; };
|
|
83
|
+
const config = { view: mockViewConstructor, guards: [guard] };
|
|
84
|
+
|
|
85
|
+
await RouteGuardsManager.processRouteGuards(config, '/users/42', { id: '42' });
|
|
86
|
+
|
|
87
|
+
expect(receivedParams.length).toBe(1);
|
|
88
|
+
expect(receivedParams[0]).toEqual({ id: '42' });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('defaults params to an empty object when none are passed to processRouteGuards', async () => {
|
|
92
|
+
const receivedParams = [];
|
|
93
|
+
const guard = (params) => { receivedParams.push(params); return true; };
|
|
94
|
+
const config = { view: mockViewConstructor, guards: [guard] };
|
|
95
|
+
|
|
96
|
+
await RouteGuardsManager.processRouteGuards(config, '/dashboard');
|
|
97
|
+
|
|
98
|
+
expect(receivedParams[0]).toEqual({});
|
|
99
|
+
});
|
|
73
100
|
});
|
package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs
CHANGED
|
@@ -26,24 +26,11 @@ describe('ViewRenderManager', () => {
|
|
|
26
26
|
return el;
|
|
27
27
|
},
|
|
28
28
|
destroy: () => {},
|
|
29
|
+
onEnter: () => {},
|
|
30
|
+
onExit: () => {},
|
|
29
31
|
};
|
|
30
32
|
};
|
|
31
33
|
|
|
32
|
-
{{#if animation}}
|
|
33
|
-
it('should add exit class and destroy current view on transition out', async () => {
|
|
34
|
-
const view = mockView();
|
|
35
|
-
const el = view.render();
|
|
36
|
-
container.appendChild(el);
|
|
37
|
-
|
|
38
|
-
// Spy on destroy
|
|
39
|
-
const spy = spyOn(view, 'destroy');
|
|
40
|
-
|
|
41
|
-
await ViewRenderManager.transitionOutCurrentView(view);
|
|
42
|
-
|
|
43
|
-
expect(spy).toHaveBeenCalled();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
{{/if}}
|
|
47
34
|
it('should do nothing if no current view is passed to transitionOutCurrentView', async () => {
|
|
48
35
|
const result = await ViewRenderManager.transitionOutCurrentView(null);
|
|
49
36
|
expect(result).toBe(null);
|
|
@@ -51,7 +38,8 @@ describe('ViewRenderManager', () => {
|
|
|
51
38
|
|
|
52
39
|
it('should emit view-unmount event on transition out', async () => {
|
|
53
40
|
const emitted = [];
|
|
54
|
-
|
|
41
|
+
const emitSpy = spyOn(AppEventBus, 'emit');
|
|
42
|
+
emitSpy.andCallFake((event, data) => emitted.push({ event, data }));
|
|
55
43
|
|
|
56
44
|
const view = mockView('SettingsView');
|
|
57
45
|
const el = view.render();
|
|
@@ -59,6 +47,84 @@ describe('ViewRenderManager', () => {
|
|
|
59
47
|
|
|
60
48
|
await ViewRenderManager.transitionOutCurrentView(view);
|
|
61
49
|
|
|
62
|
-
expect(JSON.stringify(emitted)).toContain(JSON.stringify({ event: 'view-unmount', data: 'SettingsView' }));
|
|
50
|
+
expect(JSON.stringify(emitted)).toContain(JSON.stringify({ event: 'view-unmount', data: { viewName: 'SettingsView' } }));
|
|
51
|
+
emitSpy.restore();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should call destroy() and onExit() on the current view when transitioning out', async () => {
|
|
55
|
+
let destroyed = false;
|
|
56
|
+
let exited = false;
|
|
57
|
+
const view = mockView('SettingsView');
|
|
58
|
+
view.destroy = () => { destroyed = true; };
|
|
59
|
+
view.onExit = () => { exited = true; };
|
|
60
|
+
|
|
61
|
+
await ViewRenderManager.transitionOutCurrentView(view);
|
|
62
|
+
|
|
63
|
+
expect(destroyed).toBe(true);
|
|
64
|
+
expect(exited).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should set route params, render, call onEnter and emit view-mount when rendering a new view', () => {
|
|
68
|
+
const emitted = [];
|
|
69
|
+
const emitSpy = spyOn(AppEventBus, 'emit');
|
|
70
|
+
emitSpy.andCallFake((event, data) => emitted.push({ event, data }));
|
|
71
|
+
|
|
72
|
+
const calls = [];
|
|
73
|
+
const viewElement = document.createElement('div');
|
|
74
|
+
const view = {
|
|
75
|
+
viewName: 'HomeView',
|
|
76
|
+
getElement: () => viewElement,
|
|
77
|
+
setRouteParams: (params) => calls.push(['setRouteParams', params]),
|
|
78
|
+
render: () => { calls.push(['render']); return viewElement; },
|
|
79
|
+
onEnter: () => calls.push(['onEnter']),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
ViewRenderManager.renderNewView(() => view, { id: '42' });
|
|
83
|
+
|
|
84
|
+
expect(calls).toEqual([
|
|
85
|
+
['setRouteParams', { id: '42' }],
|
|
86
|
+
['render'],
|
|
87
|
+
['onEnter'],
|
|
88
|
+
]);
|
|
89
|
+
expect(JSON.stringify(emitted)).toContain(JSON.stringify({ event: 'view-mount', data: { viewName: 'HomeView' } }));
|
|
90
|
+
emitSpy.restore();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should not attempt to clear stale content when the new view has no parent element yet', () => {
|
|
94
|
+
const viewElement = document.createElement('div');
|
|
95
|
+
// Note: viewElement is intentionally never attached to the container.
|
|
96
|
+
const view = {
|
|
97
|
+
viewName: 'HomeView',
|
|
98
|
+
getElement: () => viewElement,
|
|
99
|
+
setRouteParams: () => {},
|
|
100
|
+
render: () => viewElement,
|
|
101
|
+
onEnter: () => {},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
expect(() => ViewRenderManager.renderNewView(() => view)).not.toThrow();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should clear stale mount content before rendering the new view', () => {
|
|
108
|
+
const stale = document.createElement('div');
|
|
109
|
+
stale.className = 'stale-seo-content';
|
|
110
|
+
container.appendChild(stale);
|
|
111
|
+
|
|
112
|
+
const viewElement = document.createElement('div');
|
|
113
|
+
viewElement.className = 'mock-view';
|
|
114
|
+
container.appendChild(viewElement);
|
|
115
|
+
|
|
116
|
+
const view = {
|
|
117
|
+
viewName: 'HomeView',
|
|
118
|
+
getElement: () => viewElement,
|
|
119
|
+
setRouteParams: () => {},
|
|
120
|
+
render: () => viewElement,
|
|
121
|
+
onEnter: () => {},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
ViewRenderManager.renderNewView(() => view);
|
|
125
|
+
|
|
126
|
+
expect(container.children.length).toBe(1);
|
|
127
|
+
expect(container.contains(stale)).toBe(false);
|
|
128
|
+
expect(container.contains(viewElement)).toBe(true);
|
|
63
129
|
});
|
|
64
130
|
});
|
|
@@ -1,35 +1,14 @@
|
|
|
1
|
-
import { View,
|
|
1
|
+
import { View, Lifecycle } from '../../../index.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* handles all view rendering
|
|
4
|
+
* handles all view rendering.
|
|
5
5
|
*/
|
|
6
6
|
export class ViewRenderManager {
|
|
7
|
-
{{#if transition}}
|
|
8
|
-
public static renderAnimatedView(view: View): void {
|
|
9
|
-
const container = document.getElementById('app');
|
|
10
|
-
const el = view.render();
|
|
11
|
-
el.classList.add('app-view-transition');
|
|
12
|
-
container?.appendChild(el);
|
|
13
|
-
|
|
14
|
-
requestAnimationFrame(() => {
|
|
15
|
-
el.classList.add('enter-active');
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
{{/if}}
|
|
20
7
|
public static async transitionOutCurrentView(currentView: View | null): Promise<null> {
|
|
21
8
|
if (!currentView) return null;
|
|
22
|
-
{{#if transition}}
|
|
23
|
-
const container = document.getElementById('app');
|
|
24
|
-
const el = container?.firstElementChild as HTMLElement;
|
|
25
|
-
|
|
26
|
-
if (el) {
|
|
27
|
-
el.classList.add('exit-active');
|
|
28
|
-
await this.delay(300); // match transition duration in _libs.scss
|
|
29
|
-
}
|
|
30
|
-
{{/if}}
|
|
31
9
|
currentView.destroy();
|
|
32
|
-
|
|
10
|
+
currentView.onExit();
|
|
11
|
+
Lifecycle.viewUnmount(currentView.viewName);
|
|
33
12
|
return null;
|
|
34
13
|
}
|
|
35
14
|
|
|
@@ -38,28 +17,25 @@ export class ViewRenderManager {
|
|
|
38
17
|
params: Record<string, string> = {}
|
|
39
18
|
): View {
|
|
40
19
|
const view = viewConstructor();
|
|
20
|
+
ViewRenderManager.clearStaleMountContent(view);
|
|
41
21
|
|
|
42
22
|
// Set route parameters before rendering
|
|
43
23
|
view.setRouteParams(params);
|
|
44
24
|
|
|
45
|
-
{{#if transition}}
|
|
46
|
-
this.renderAnimatedView(view);
|
|
47
|
-
|
|
48
|
-
{{else}}
|
|
49
25
|
view.render();
|
|
26
|
+
view.onEnter();
|
|
50
27
|
|
|
51
|
-
|
|
52
|
-
this.emitEvent('view-mount', view.viewName);
|
|
28
|
+
Lifecycle.viewMount(view.viewName);
|
|
53
29
|
return view;
|
|
54
30
|
}
|
|
55
31
|
|
|
56
|
-
private static
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
32
|
+
private static clearStaleMountContent(view: View): void {
|
|
33
|
+
const viewElement = view.getElement();
|
|
34
|
+
const container = viewElement.parentElement;
|
|
35
|
+
if (!container) return;
|
|
60
36
|
|
|
61
|
-
|
|
62
|
-
|
|
37
|
+
Array.from(container.childNodes).forEach((node) => {
|
|
38
|
+
if (node !== viewElement) node.remove();
|
|
39
|
+
});
|
|
63
40
|
}
|
|
64
|
-
{{/if}}
|
|
65
41
|
}
|