@nutin/cli 1.3.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -9
- package/README.md +20 -35
- package/bin/add-feature.mjs +9 -0
- package/bin/cli.mjs +8 -0
- package/bin/create-app.mjs +5 -4
- package/bin/update-app.mjs +9 -0
- package/lib/index.mjs +76 -25
- package/lib/src/add-feature/feature-adder.mjs +83 -0
- package/lib/src/add-feature/feature-context-builder.mjs +25 -0
- package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
- package/lib/src/add-feature/package-json-updater.mjs +49 -0
- package/lib/src/common/feature-registry.mjs +11 -0
- package/lib/src/common/file-generator.mjs +159 -0
- package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
- package/lib/src/common/package-data.mjs +10 -0
- package/lib/src/common/package-json-helper.mjs +103 -0
- package/lib/src/common/project-meta.mjs +54 -0
- package/lib/src/create-app/context-builder.mjs +21 -0
- package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
- package/lib/src/create-app/json-manager.mjs +73 -0
- package/lib/src/{prompts.mjs → create-app/new-app-prompt.mjs} +6 -6
- package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +10 -8
- package/lib/src/create-app/success-message.mjs +14 -0
- package/lib/src/update-app/conflict-report-writer.mjs +31 -0
- package/lib/src/update-app/fetch-old-templates.mjs +61 -0
- package/lib/src/update-app/project-updater.mjs +136 -0
- package/lib/src/update-app/template-differ.mjs +124 -0
- package/lib/src/update-app/update-context-builder.mjs +25 -0
- package/lib/src/update-app/update-summary-printer.mjs +22 -0
- package/lib/src/update-app/version-compare.mjs +12 -0
- package/lib/src/utils/print.mjs +36 -0
- package/lib/src/utils/promise-exec-alias.mjs +4 -0
- package/package.json +17 -12
- package/templates/base/.gitignore.hbs +6 -1
- package/templates/base/AGENTS.md.hbs +169 -0
- package/templates/base/GETTING_STARTED.md.hbs +227 -0
- package/templates/base/README.md.hbs +8 -17
- package/templates/base/config/languages.json.hbs +6 -0
- package/templates/base/config/seo.json.hbs +12 -0
- package/templates/base/nutin.config.js.hbs +51 -0
- package/templates/base/src/app/components/index.ts.hbs +1 -2
- package/templates/base/src/app/globals.d.ts.hbs +3 -19
- package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
- package/templates/base/src/app/main.ts.hbs +7 -26
- package/templates/base/src/app/services/index.ts.hbs +1 -1
- package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
- package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
- package/templates/base/src/app/views/index.ts.hbs +1 -0
- package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
- package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +68 -35
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +9 -8
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +7 -1
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
- package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
- package/templates/base/src/core/base-classes/component/component.ts.hbs +24 -33
- package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
- package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
- package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
- package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
- package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
- package/templates/base/src/core/config.ts.hbs +7 -0
- package/templates/base/src/core/index.ts.hbs +0 -3
- package/templates/base/src/core/internals.d.ts.hbs +27 -0
- package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
- package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
- package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
- package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
- package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
- package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
- package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
- package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
- package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
- package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
- package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
- package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
- package/templates/base/src/core/services/index.ts.hbs +5 -6
- package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
- package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
- package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +40 -29
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
- package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
- package/templates/base/src/core/services/router/router.ts.hbs +40 -8
- package/templates/base/src/index.html.hbs +2 -6
- package/templates/base/src/styles/_styles.scss.hbs +102 -43
- package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
- package/templates/base/src/styles/main.scss.hbs +1 -18
- package/templates/base/src/styles/tailwind.css.hbs +2 -0
- package/templates/base/tools/builder/builder.config.js.hbs +40 -0
- package/templates/base/tools/builder/builder.js.hbs +24 -26
- package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
- package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
- package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
- package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
- package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
- package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
- package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
- package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
- package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
- package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
- package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
- package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
- package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
- package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
- package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
- package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
- package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
- package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
- package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
- package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
- package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
- package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
- package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
- package/templates/base/tools/dev/serve.js.hbs +11 -1
- package/templates/base/tools/dev/watcher.js.hbs +14 -6
- package/templates/base/tools/generator/generator.js.hbs +64 -47
- package/templates/base/tools/generator/handle-file.js.hbs +17 -44
- package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
- package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
- package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
- package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
- package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/index.js.hbs +7 -12
- package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
- package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
- package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
- package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
- package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
- package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
- package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
- package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
- package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
- package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
- package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
- package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
- package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
- package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
- package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
- package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
- package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
- package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
- package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
- package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
- package/templates/base/tools/utils/error-exit.js.hbs +8 -0
- package/templates/base/tools/utils/format.js.hbs +10 -10
- package/templates/base/tools/utils/format.test.js.hbs +45 -0
- package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
- package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
- package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
- package/templates/base/tools/utils/index.js.hbs +1 -3
- package/templates/base/tools/utils/languages.js.hbs +15 -0
- package/templates/base/tools/utils/languages.test.js.hbs +16 -0
- package/templates/base/tools/utils/print.js.hbs +19 -13
- package/templates/base/tools/utils/print.test.js.hbs +60 -0
- package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
- package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
- package/templates/base/tools/utils/run-command.js.hbs +4 -2
- package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
- package/templates/base/tools/utils/run-script.js.hbs +14 -3
- package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
- package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
- package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
- package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
- package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
- package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
- package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
- package/lib/src/context-builder.mjs +0 -40
- package/lib/src/file-generator.mjs +0 -109
- package/lib/src/json-generator.mjs +0 -31
- package/lib/src/package-manager.mjs +0 -139
- package/lib/src/print.mjs +0 -32
- package/lib/src/utils.mjs +0 -18
- package/lib/src/version.mjs +0 -1
- package/templates/base/builder.config.js.hbs +0 -36
- package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
- package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
- package/templates/base/src/app/dom.test.js.hbs +0 -13
- package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
- package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
- package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
- package/templates/base/src/core/services/store/store.ts.hbs +0 -39
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -113
- package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
- package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -131
- package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
- package/templates/base/src/core/utils/index.ts.hbs +0 -2
- package/templates/base/src/libs/index.ts.hbs +0 -4
- package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +0 -197
- package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
- package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
- package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
- package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
- package/templates/base/src/styles/core/_index.scss.hbs +0 -6
- package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
- package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
- package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
- package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
- package/templates/base/tools/builder/core/sass.js.hbs +0 -22
- package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
- package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
- package/templates/base/tools/utils/export-flags.js.hbs +0 -6
- package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
- package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
- package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
- package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
- package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
- package/templates/features/i18n/src/core/config.ts.hbs +0 -3
- package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
- package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
- package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
- package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
- package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
- package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
- package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
- package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
- package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
- package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
- package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
- package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
- package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
- package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
- package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
- package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
- package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
- package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
- package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
- /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
- /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
- /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
- /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
- /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Abstract
|
|
2
|
+
* Abstract class for implementing the Singleton pattern.
|
|
3
3
|
* Supports constructor parameters for initialization.
|
|
4
4
|
*/
|
|
5
5
|
export abstract class Service<T extends Service<T>> {
|
|
6
6
|
private static _instances = new Map<Function, any>();
|
|
7
7
|
private static _instantiating = new Set<Function>();
|
|
8
8
|
private _cleanupCallbacks: (() => void)[] = [];
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
constructor(...args: any[]) {
|
|
11
11
|
const constructor = this.constructor as new (...args: any[]) => T;
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
if (!Service._instantiating.has(constructor)) {
|
|
14
14
|
throw new Error(
|
|
15
15
|
`${constructor.name} is a Service. Use ${constructor.name}.getInstance() instead.`
|
|
@@ -19,31 +19,39 @@ export abstract class Service<T extends Service<T>> {
|
|
|
19
19
|
window.addEventListener('beforeunload', this.dispose);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Returns the singleton instance, creating it with the provided args if it
|
|
24
|
+
* doesn't exist yet. Throws if args are passed to an already-initialized instance,
|
|
25
|
+
* preventing silent re-initialization mismatches.
|
|
26
|
+
*/
|
|
27
|
+
public static getInstance<T extends Service<T>>(
|
|
28
|
+
this: new (...args: any[]) => T,
|
|
24
29
|
...args: any[]
|
|
25
30
|
): T {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
const alreadyExists = Service._instances.has(this);
|
|
32
|
+
|
|
33
|
+
if (alreadyExists && args.length > 0) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`${this.name} is already initialized. ` +
|
|
36
|
+
`getInstance() was called with arguments on an existing instance, ` +
|
|
37
|
+
`which would silently ignore them. ` +
|
|
38
|
+
`Call getInstance() without arguments to retrieve the existing instance.`
|
|
39
|
+
);
|
|
30
40
|
}
|
|
31
|
-
return Service._instances.get(constructor);
|
|
32
|
-
}
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
if (!Service._instances.has(this)) {
|
|
42
|
+
if (!alreadyExists) {
|
|
36
43
|
Service._instantiating.add(this);
|
|
37
|
-
Service._instances.set(this, new this());
|
|
44
|
+
Service._instances.set(this, new this(...args));
|
|
38
45
|
Service._instantiating.delete(this);
|
|
39
46
|
}
|
|
47
|
+
|
|
40
48
|
return Service._instances.get(this);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
public static hasInstance<T extends Service<T>>(
|
|
44
52
|
constructor: new (...args: any[]) => T
|
|
45
53
|
): boolean {
|
|
46
|
-
return Service._instances.has(constructor);
|
|
54
|
+
return Service._instances.has(constructor);
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
public static async destroy<T extends Service<T>>(
|
|
@@ -65,7 +73,7 @@ export abstract class Service<T extends Service<T>> {
|
|
|
65
73
|
public static async destroyAll(): Promise<void> {
|
|
66
74
|
const destroyPromises = Array.from(Service._instances.values())
|
|
67
75
|
.map(instance => instance.onDestroy());
|
|
68
|
-
|
|
76
|
+
|
|
69
77
|
await Promise.all(destroyPromises);
|
|
70
78
|
Service._instances.clear();
|
|
71
79
|
Service._instantiating.clear();
|
|
@@ -76,11 +84,9 @@ export abstract class Service<T extends Service<T>> {
|
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
/**
|
|
79
|
-
* For async and custom cleanup operations
|
|
87
|
+
* For async and custom cleanup operations.
|
|
80
88
|
*/
|
|
81
|
-
protected onDestroy
|
|
82
|
-
console.log(`No onDestroy operations.`)
|
|
83
|
-
}
|
|
89
|
+
protected onDestroy(): void {}
|
|
84
90
|
|
|
85
91
|
private autoBindMethods(): void {
|
|
86
92
|
let proto = Object.getPrototypeOf(this);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { View } from '#root/dist/src/core/index.js';
|
|
2
|
+
|
|
3
|
+
class HomeView extends View {}
|
|
4
|
+
class MyFancyView extends View {}
|
|
5
|
+
class NamedView extends View {}
|
|
6
|
+
|
|
7
|
+
describe('View', () => {
|
|
8
|
+
let app;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
app = document.getElementById('app');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
app.innerHTML = '';
|
|
16
|
+
app = null;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('derives a kebab-case viewName from the class name, stripping a trailing "View"', () => {
|
|
20
|
+
const view = new HomeView({});
|
|
21
|
+
expect(view.viewName).toBe('home');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('derives a kebab-case viewName for a multi-word class name', () => {
|
|
25
|
+
const view = new MyFancyView({});
|
|
26
|
+
expect(view.viewName).toBe('my-fancy');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('uses an explicit viewName option instead of deriving one', () => {
|
|
30
|
+
const view = new NamedView({ viewName: 'custom-name' });
|
|
31
|
+
expect(view.viewName).toBe('custom-name');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('generateTemplate() returns the constructor template', () => {
|
|
35
|
+
const view = new HomeView({ template: '<p>hi</p>' });
|
|
36
|
+
expect(view.render().innerHTML).toBe('<p>hi</p>');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('defaults the template to an empty string', () => {
|
|
40
|
+
const view = new HomeView({});
|
|
41
|
+
expect(view.render().innerHTML).toBe('');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('defaults the tagName to "section"', () => {
|
|
45
|
+
const view = new HomeView({});
|
|
46
|
+
expect(view.getElement().tagName).toBe('SECTION');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('setRouteParams/getRouteParams/getRouteParam/hasRouteParam manage route params', () => {
|
|
50
|
+
const view = new HomeView({});
|
|
51
|
+
expect(view.hasRouteParam('id')).toBe(false);
|
|
52
|
+
expect(view.getRouteParam('id')).toBeUndefined();
|
|
53
|
+
|
|
54
|
+
view.setRouteParams({ id: '42' });
|
|
55
|
+
|
|
56
|
+
expect(view.hasRouteParam('id')).toBe(true);
|
|
57
|
+
expect(view.getRouteParam('id')).toBe('42');
|
|
58
|
+
expect(view.getRouteParams()).toEqual({ id: '42' });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('getRouteParams() returns a copy, not a live reference', () => {
|
|
62
|
+
const view = new HomeView({});
|
|
63
|
+
view.setRouteParams({ id: '1' });
|
|
64
|
+
const params = view.getRouteParams();
|
|
65
|
+
params.id = 'mutated';
|
|
66
|
+
expect(view.getRouteParam('id')).toBe('1');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('onEnter and onExit are present as no-op hooks', () => {
|
|
70
|
+
const view = new HomeView({});
|
|
71
|
+
expect(() => view.onEnter()).not.toThrow();
|
|
72
|
+
expect(() => view.onExit()).not.toThrow();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -7,71 +7,47 @@ export interface ViewOptions extends BaseComponentOptions {
|
|
|
7
7
|
export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComponent<T> {
|
|
8
8
|
public viewName: string;
|
|
9
9
|
protected routeParams: Record<string, string> = {};
|
|
10
|
+
private _template: string;
|
|
10
11
|
|
|
11
12
|
constructor({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
template,
|
|
14
|
+
tagName = 'section',
|
|
15
|
+
mountTarget = '#app',
|
|
16
|
+
viewName,
|
|
17
|
+
trustLevel,
|
|
18
|
+
}: ViewOptions) {
|
|
19
|
+
super({ mountTarget, tagName, trustLevel });
|
|
20
|
+
this._template = template ?? '';
|
|
18
21
|
this.viewName = viewName || this.getKebabCaseViewName();
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
protected override generateTemplate(): string {
|
|
25
|
+
return this._template;
|
|
26
|
+
}
|
|
27
|
+
|
|
25
28
|
public setRouteParams(params: Record<string, string>): void {
|
|
26
29
|
this.routeParams = { ...params };
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
/**
|
|
30
|
-
* Get all route parameters
|
|
31
|
-
*/
|
|
32
32
|
public getRouteParams(): Record<string, string> {
|
|
33
33
|
return { ...this.routeParams };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Get a specific route parameter
|
|
38
|
-
*/
|
|
39
36
|
public getRouteParam(key: string): string | undefined {
|
|
40
37
|
return this.routeParams[key];
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
/**
|
|
44
|
-
* Check if a route parameter exists and has a value
|
|
45
|
-
*/
|
|
46
40
|
public hasRouteParam(key: string): boolean {
|
|
47
41
|
return key in this.routeParams && this.routeParams[key] !== undefined;
|
|
48
42
|
}
|
|
49
43
|
|
|
50
|
-
public shouldUpdateMetaContent(): boolean {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public onEnter(): void {
|
|
55
|
-
console.log(`${this.viewName} mounted with params:`, this.routeParams);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public onExit(): void {
|
|
59
|
-
console.log(`${this.viewName} unmounted`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public override render(): HTMLElement {
|
|
63
|
-
this.onEnter();
|
|
64
|
-
return super.render();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public override destroy(): void {
|
|
68
|
-
super.destroy();
|
|
69
|
-
this.onExit();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
44
|
private getKebabCaseViewName(): string {
|
|
73
45
|
const className = this.constructor.name;
|
|
74
46
|
const baseName = className.replace(/View$/, '');
|
|
75
47
|
return baseName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
76
48
|
}
|
|
49
|
+
|
|
50
|
+
// Navigation hooks — called by router only, never by render lifecycle
|
|
51
|
+
public onEnter(): void {}
|
|
52
|
+
public onExit(): void {}
|
|
77
53
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
declare interface NavigationEventMap {
|
|
2
|
+
'navigate': { path: string };
|
|
3
|
+
'reload': {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare interface LifecycleEventMap {
|
|
7
|
+
'before-render': {};
|
|
8
|
+
'after-render': {};
|
|
9
|
+
'before-destroy': {};
|
|
10
|
+
'after-destroy': {};
|
|
11
|
+
'view-mount': { viewName: string };
|
|
12
|
+
'view-unmount': { viewName: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare interface I18nEventMap {
|
|
16
|
+
'language-changed': {
|
|
17
|
+
lang: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare interface FrameworkEventMap extends NavigationEventMap, LifecycleEventMap, I18nEventMap {}
|
|
22
|
+
|
|
23
|
+
declare interface AppEventMap {}
|
|
24
|
+
|
|
25
|
+
declare interface EventMap extends FrameworkEventMap, AppEventMap {}
|
|
26
|
+
|
|
27
|
+
declare type EventKey = keyof EventMap;
|
|
@@ -127,11 +127,75 @@ describe('EventBus', () => {
|
|
|
127
127
|
});
|
|
128
128
|
it('should reset all service instances', () => {
|
|
129
129
|
const eventBus1 = EventBus.getInstance();
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
EventBus.testingResetAll();
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
const eventBus2 = EventBus.getInstance();
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
expect(eventBus1).not.toBe(eventBus2);
|
|
136
136
|
});
|
|
137
|
+
|
|
138
|
+
it('should only invoke a once() handler on the first emit', () => {
|
|
139
|
+
let callCount = 0;
|
|
140
|
+
eventBus.once('user-login', () => { callCount++; });
|
|
141
|
+
|
|
142
|
+
eventBus.emit('user-login', { userId: '1' });
|
|
143
|
+
expect(callCount).toBe(1);
|
|
144
|
+
|
|
145
|
+
eventBus.emit('user-login', { userId: '2' });
|
|
146
|
+
expect(callCount).toBe(1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should auto-unsubscribe a once() handler from _subscriptions after it fires', () => {
|
|
150
|
+
eventBus.once('user-login', () => {});
|
|
151
|
+
expect(eventBus._subscriptions.length).toBe(1);
|
|
152
|
+
|
|
153
|
+
eventBus.emit('user-login', {});
|
|
154
|
+
expect(eventBus._subscriptions.length).toBe(0);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should not remove a once() subscription before it has fired', () => {
|
|
158
|
+
eventBus.once('user-login', () => {});
|
|
159
|
+
eventBus.emit('notification', {});
|
|
160
|
+
expect(eventBus._subscriptions.length).toBe(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should let a once() handler and a regular subscriber on the same event coexist', () => {
|
|
164
|
+
let onceCalls = 0;
|
|
165
|
+
let regularCalls = 0;
|
|
166
|
+
eventBus.once('notification', () => { onceCalls++; });
|
|
167
|
+
eventBus.subscribe('notification', () => { regularCalls++; });
|
|
168
|
+
|
|
169
|
+
eventBus.emit('notification', {});
|
|
170
|
+
expect(onceCalls).toBe(1);
|
|
171
|
+
expect(regularCalls).toBe(1);
|
|
172
|
+
|
|
173
|
+
eventBus.emit('notification', {});
|
|
174
|
+
expect(onceCalls).toBe(1);
|
|
175
|
+
expect(regularCalls).toBe(2);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('off(event) with no callback removes every handler for that event', () => {
|
|
179
|
+
let callsA = 0;
|
|
180
|
+
let callsB = 0;
|
|
181
|
+
eventBus.subscribe('notification', () => { callsA++; });
|
|
182
|
+
eventBus.subscribe('notification', () => { callsB++; });
|
|
183
|
+
|
|
184
|
+
eventBus.off('notification');
|
|
185
|
+
eventBus.emit('notification', {});
|
|
186
|
+
|
|
187
|
+
expect(callsA).toBe(0);
|
|
188
|
+
expect(callsB).toBe(0);
|
|
189
|
+
expect(eventBus.handlers['notification']).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('off(event) with no callback does not affect other events', () => {
|
|
193
|
+
let called = false;
|
|
194
|
+
eventBus.subscribe('user-login', () => { called = true; });
|
|
195
|
+
|
|
196
|
+
eventBus.off('notification');
|
|
197
|
+
eventBus.emit('user-login', {});
|
|
198
|
+
|
|
199
|
+
expect(called).toBe(true);
|
|
200
|
+
});
|
|
137
201
|
});
|
|
@@ -2,7 +2,7 @@ import { Service } from "../../index.js";
|
|
|
2
2
|
|
|
3
3
|
export type IEventBus = InstanceType<typeof EventBus>;
|
|
4
4
|
|
|
5
|
-
type Subscription<K extends EventKey = EventKey> = {
|
|
5
|
+
export type Subscription<K extends EventKey = EventKey> = {
|
|
6
6
|
event: K;
|
|
7
7
|
callback: (data: EventMap[K]) => void;
|
|
8
8
|
once?: boolean;
|
|
@@ -58,7 +58,10 @@ export class EventBus extends Service<EventBus> {
|
|
|
58
58
|
if (!handlers) return;
|
|
59
59
|
|
|
60
60
|
if (callback) {
|
|
61
|
-
this.handlers
|
|
61
|
+
(this.handlers as Record<
|
|
62
|
+
EventKey,
|
|
63
|
+
Array<(data: EventMap[K]) => void>
|
|
64
|
+
>)[event] = handlers.filter(h => h !== callback);
|
|
62
65
|
} else {
|
|
63
66
|
delete this.handlers[event];
|
|
64
67
|
}
|
|
@@ -76,8 +79,10 @@ export class EventBus extends Service<EventBus> {
|
|
|
76
79
|
if (!this.handlers[event]) {
|
|
77
80
|
this.handlers[event] = [];
|
|
78
81
|
}
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
(
|
|
83
|
+
this.handlers as Record<EventKey, Array<(data: EventMap[K]) => void>>
|
|
84
|
+
)[event].push(callback);
|
|
85
|
+
this._subscriptions.push({ event, callback, once } as Subscription);
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
private cleanupEventListeners = (): void => {
|
|
@@ -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);
|