@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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Service } from '#root/dist/src/core/base-classes/service/service.js';
|
|
2
|
+
|
|
3
|
+
class TestService extends Service {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
this.destroyCalls = 0;
|
|
7
|
+
this.cleanupCalls = [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
addCleanup(fn) {
|
|
11
|
+
this.registerCleanup(fn);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
onDestroy() {
|
|
15
|
+
this.destroyCalls++;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
ping() {
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class OtherTestService extends Service {
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.destroyCalls = 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onDestroy() {
|
|
30
|
+
this.destroyCalls++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('Service', () => {
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
TestService.testingResetAll();
|
|
37
|
+
OtherTestService.testingResetAll();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('throws when instantiated directly instead of via getInstance', () => {
|
|
41
|
+
expect(() => new TestService()).toThrow('TestService is a Service. Use TestService.getInstance() instead.');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('creates the instance lazily on first getInstance call', () => {
|
|
45
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
46
|
+
const instance = TestService.getInstance();
|
|
47
|
+
expect(instance).toBeInstanceOf(TestService);
|
|
48
|
+
expect(Service.hasInstance(TestService)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns the same instance on repeated getInstance calls without args', () => {
|
|
52
|
+
const instance1 = TestService.getInstance();
|
|
53
|
+
const instance2 = TestService.getInstance();
|
|
54
|
+
expect(instance1).toBe(instance2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('throws if getInstance is called with args on an already-initialized instance', () => {
|
|
58
|
+
TestService.getInstance();
|
|
59
|
+
expect(() => TestService.getInstance('arg')).toThrow('already initialized');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does not throw when getInstance is called with args for a fresh instance', () => {
|
|
63
|
+
expect(() => TestService.getInstance()).not.toThrow();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('hasInstance reflects whether an instance has been created', () => {
|
|
67
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
68
|
+
TestService.getInstance();
|
|
69
|
+
expect(Service.hasInstance(TestService)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('registerCleanup callbacks run on dispose and are cleared afterward', () => {
|
|
73
|
+
const instance = TestService.getInstance();
|
|
74
|
+
let cleaned = false;
|
|
75
|
+
instance.addCleanup(() => { cleaned = true; });
|
|
76
|
+
|
|
77
|
+
instance.dispose();
|
|
78
|
+
|
|
79
|
+
expect(cleaned).toBe(true);
|
|
80
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('dispose clears cleanup callbacks so they only run once', () => {
|
|
84
|
+
const instance = TestService.getInstance();
|
|
85
|
+
let calls = 0;
|
|
86
|
+
instance.addCleanup(() => { calls++; });
|
|
87
|
+
|
|
88
|
+
instance.dispose();
|
|
89
|
+
instance.dispose();
|
|
90
|
+
|
|
91
|
+
expect(calls).toBe(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('destroy calls onDestroy and removes the instance from the registry', async () => {
|
|
95
|
+
const instance = TestService.getInstance();
|
|
96
|
+
await TestService.destroy();
|
|
97
|
+
|
|
98
|
+
expect(instance.destroyCalls).toBe(1);
|
|
99
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('destroy is a no-op when no instance exists', async () => {
|
|
103
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
104
|
+
await TestService.destroy();
|
|
105
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('destroyAll calls onDestroy on every instance and clears the registry', async () => {
|
|
109
|
+
const a = TestService.getInstance();
|
|
110
|
+
const b = OtherTestService.getInstance();
|
|
111
|
+
|
|
112
|
+
await Service.destroyAll();
|
|
113
|
+
|
|
114
|
+
expect(a.destroyCalls).toBe(1);
|
|
115
|
+
expect(b.destroyCalls).toBe(1);
|
|
116
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
117
|
+
expect(Service.hasInstance(OtherTestService)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('autoBindMethods binds subclass methods so they work detached from the instance', () => {
|
|
121
|
+
const instance = TestService.getInstance();
|
|
122
|
+
const { ping } = instance;
|
|
123
|
+
expect(ping()).toBe(instance);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('testingReset removes only the targeted instance without running cleanup', () => {
|
|
127
|
+
const instance1 = TestService.getInstance();
|
|
128
|
+
let cleaned = false;
|
|
129
|
+
instance1.addCleanup(() => { cleaned = true; });
|
|
130
|
+
|
|
131
|
+
TestService.testingReset();
|
|
132
|
+
|
|
133
|
+
expect(cleaned).toBe(false);
|
|
134
|
+
const instance2 = TestService.getInstance();
|
|
135
|
+
expect(instance1).not.toBe(instance2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('testingResetAll clears every instance without running cleanup', () => {
|
|
139
|
+
const a = TestService.getInstance();
|
|
140
|
+
OtherTestService.getInstance();
|
|
141
|
+
let cleaned = false;
|
|
142
|
+
a.addCleanup(() => { cleaned = true; });
|
|
143
|
+
|
|
144
|
+
Service.testingResetAll();
|
|
145
|
+
|
|
146
|
+
expect(cleaned).toBe(false);
|
|
147
|
+
expect(Service.hasInstance(TestService)).toBe(false);
|
|
148
|
+
expect(Service.hasInstance(OtherTestService)).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -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,71 @@
|
|
|
1
|
+
import { View } from '#root/dist/src/core/index.js';
|
|
2
|
+
|
|
3
|
+
class HomeView extends View {}
|
|
4
|
+
class NamedView extends View {}
|
|
5
|
+
|
|
6
|
+
describe('View', () => {
|
|
7
|
+
let app;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
app = document.getElementById('app');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
app.innerHTML = '';
|
|
15
|
+
app = null;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('throws when viewName is omitted', () => {
|
|
19
|
+
expect(() => new HomeView({})).toThrow();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('throws when viewName is an empty string', () => {
|
|
23
|
+
expect(() => new HomeView({ viewName: '' })).toThrow();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('uses the explicit viewName option', () => {
|
|
27
|
+
const view = new NamedView({ viewName: 'custom-name' });
|
|
28
|
+
expect(view.viewName).toBe('custom-name');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('generateTemplate() returns the constructor template', () => {
|
|
32
|
+
const view = new HomeView({ viewName: 'home', template: '<p>hi</p>' });
|
|
33
|
+
expect(view.render().innerHTML).toBe('<p>hi</p>');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('defaults the template to an empty string', () => {
|
|
37
|
+
const view = new HomeView({ viewName: 'home' });
|
|
38
|
+
expect(view.render().innerHTML).toBe('');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('defaults the tagName to "section"', () => {
|
|
42
|
+
const view = new HomeView({ viewName: 'home' });
|
|
43
|
+
expect(view.getElement().tagName).toBe('SECTION');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('setRouteParams/getRouteParams/getRouteParam/hasRouteParam manage route params', () => {
|
|
47
|
+
const view = new HomeView({ viewName: 'home' });
|
|
48
|
+
expect(view.hasRouteParam('id')).toBe(false);
|
|
49
|
+
expect(view.getRouteParam('id')).toBeUndefined();
|
|
50
|
+
|
|
51
|
+
view.setRouteParams({ id: '42' });
|
|
52
|
+
|
|
53
|
+
expect(view.hasRouteParam('id')).toBe(true);
|
|
54
|
+
expect(view.getRouteParam('id')).toBe('42');
|
|
55
|
+
expect(view.getRouteParams()).toEqual({ id: '42' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('getRouteParams() returns a copy, not a live reference', () => {
|
|
59
|
+
const view = new HomeView({ viewName: 'home' });
|
|
60
|
+
view.setRouteParams({ id: '1' });
|
|
61
|
+
const params = view.getRouteParams();
|
|
62
|
+
params.id = 'mutated';
|
|
63
|
+
expect(view.getRouteParam('id')).toBe('1');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('onEnter and onExit are present as no-op hooks', () => {
|
|
67
|
+
const view = new HomeView({ viewName: 'home' });
|
|
68
|
+
expect(() => view.onEnter()).not.toThrow();
|
|
69
|
+
expect(() => view.onExit()).not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -1,77 +1,52 @@
|
|
|
1
1
|
import { BaseComponent, BaseComponentOptions } from '../../index.js';
|
|
2
2
|
|
|
3
3
|
export interface ViewOptions extends BaseComponentOptions {
|
|
4
|
-
viewName
|
|
4
|
+
viewName: string;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComponent<T> {
|
|
8
|
-
|
|
8
|
+
private _viewName: string;
|
|
9
9
|
protected routeParams: Record<string, string> = {};
|
|
10
|
+
private _template: string;
|
|
10
11
|
|
|
11
12
|
constructor({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
template,
|
|
14
|
+
tagName = 'section',
|
|
15
|
+
mountTarget = '#app',
|
|
16
|
+
viewName,
|
|
17
|
+
trustLevel,
|
|
18
|
+
}: ViewOptions) {
|
|
19
|
+
super({ mountTarget, tagName, trustLevel });
|
|
20
|
+
if (!viewName) throw new Error('View requires a viewName.');
|
|
21
|
+
this._template = template ?? '';
|
|
22
|
+
this._viewName = viewName;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public get viewName(): string {
|
|
26
|
+
return this._viewName;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected override generateTemplate(): string {
|
|
30
|
+
return this._template;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
/**
|
|
22
|
-
* Set route parameters for this view instance
|
|
23
|
-
* Called by the router when the view is instantiated
|
|
24
|
-
*/
|
|
25
33
|
public setRouteParams(params: Record<string, string>): void {
|
|
26
34
|
this.routeParams = { ...params };
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
/**
|
|
30
|
-
* Get all route parameters
|
|
31
|
-
*/
|
|
32
37
|
public getRouteParams(): Record<string, string> {
|
|
33
38
|
return { ...this.routeParams };
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
/**
|
|
37
|
-
* Get a specific route parameter
|
|
38
|
-
*/
|
|
39
41
|
public getRouteParam(key: string): string | undefined {
|
|
40
42
|
return this.routeParams[key];
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
/**
|
|
44
|
-
* Check if a route parameter exists and has a value
|
|
45
|
-
*/
|
|
46
45
|
public hasRouteParam(key: string): boolean {
|
|
47
46
|
return key in this.routeParams && this.routeParams[key] !== undefined;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
private getKebabCaseViewName(): string {
|
|
73
|
-
const className = this.constructor.name;
|
|
74
|
-
const baseName = className.replace(/View$/, '');
|
|
75
|
-
return baseName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
76
|
-
}
|
|
49
|
+
// Navigation hooks — called by router only, never by render lifecycle
|
|
50
|
+
public onEnter(): void {}
|
|
51
|
+
public onExit(): void {}
|
|
77
52
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import langs from '../../config/languages.json' with { type: 'json' };
|
|
2
|
+
import seo from '../../config/seo.json' with { type: 'json' };
|
|
3
|
+
import nutinConfig from '../../nutin.config.js';
|
|
4
|
+
|
|
5
|
+
export const CONFIG = {
|
|
6
|
+
langs,
|
|
7
|
+
seo,
|
|
8
|
+
i18n: nutinConfig.i18n,
|
|
9
|
+
generateSEOFiles: nutinConfig.generateSEOFiles,
|
|
10
|
+
} as const;
|
|
@@ -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 => {
|