@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
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
let _installed = false;
|
|
2
|
+
let _now = 0; // virtual clock, ms since epoch
|
|
3
|
+
let _idSeq = 1; // fake timer id counter — starts at 1, never falsy
|
|
4
|
+
const _timers = new Map(); // id -> { id, type: 'timeout'|'interval', callback, args, delay, due }
|
|
5
|
+
let _originals = null; // real fns captured at install time
|
|
6
|
+
const MAX_LOOP_ITERATIONS = 100000;
|
|
7
|
+
|
|
8
|
+
function _schedule(type, cb, delay, args) {
|
|
9
|
+
if (typeof cb !== 'function') {
|
|
10
|
+
throw new Error(`${type === 'timeout' ? 'setTimeout' : 'setInterval'} requires a function callback`);
|
|
11
|
+
}
|
|
12
|
+
const id = _idSeq++;
|
|
13
|
+
const safeDelay = Math.max(0, Number(delay) || 0);
|
|
14
|
+
_timers.set(id, { id, type, callback: cb, args, delay: safeDelay, due: _now + safeDelay });
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fakeSetTimeout = (cb, delay = 0, ...args) => _schedule('timeout', cb, delay, args);
|
|
19
|
+
const fakeSetInterval = (cb, delay = 0, ...args) => _schedule('interval', cb, delay, args);
|
|
20
|
+
const fakeClearTimeout = (id) => { _timers.delete(id); };
|
|
21
|
+
const fakeClearInterval = fakeClearTimeout;
|
|
22
|
+
|
|
23
|
+
function _makeFakeDate(RealDate) {
|
|
24
|
+
return class FakeDate extends RealDate {
|
|
25
|
+
constructor(...args) {
|
|
26
|
+
if (args.length === 0) {
|
|
27
|
+
super(_now);
|
|
28
|
+
} else {
|
|
29
|
+
super(...args);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
static now() {
|
|
33
|
+
return _now;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Sugar over the fake timers rather than relying on jsdom-setup.js's
|
|
39
|
+
// `window.requestAnimationFrame ??= (cb) => setTimeout(cb, 0)` shim, which is
|
|
40
|
+
// dead code with `pretendToBeVisual: true` jsdom config
|
|
41
|
+
const fakeRequestAnimationFrame = (cb) => fakeSetTimeout(() => cb(_now), 16);
|
|
42
|
+
const fakeCancelAnimationFrame = (id) => fakeClearTimeout(id);
|
|
43
|
+
|
|
44
|
+
function _assertFake(name) {
|
|
45
|
+
if (!_installed) {
|
|
46
|
+
throw new Error(`${name}() requires useFakeTimers() to be called first`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function _earliestDue(predicate) {
|
|
51
|
+
let best = null;
|
|
52
|
+
for (const timer of _timers.values()) {
|
|
53
|
+
if (predicate(timer) && (!best || timer.due < best.due || (timer.due === best.due && timer.id < best.id))) {
|
|
54
|
+
best = timer;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return best;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function _fire(timer) {
|
|
61
|
+
if (timer.type === 'timeout') {
|
|
62
|
+
_timers.delete(timer.id);
|
|
63
|
+
timer.callback(...timer.args);
|
|
64
|
+
} else {
|
|
65
|
+
timer.due += timer.delay;
|
|
66
|
+
timer.callback(...timer.args);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
global.useFakeTimers = function useFakeTimers() {
|
|
71
|
+
if (_installed) return;
|
|
72
|
+
|
|
73
|
+
_originals = {
|
|
74
|
+
setTimeout: global.setTimeout,
|
|
75
|
+
clearTimeout: global.clearTimeout,
|
|
76
|
+
setInterval: global.setInterval,
|
|
77
|
+
clearInterval: global.clearInterval,
|
|
78
|
+
Date: global.Date,
|
|
79
|
+
requestAnimationFrame: global.requestAnimationFrame,
|
|
80
|
+
cancelAnimationFrame: global.cancelAnimationFrame,
|
|
81
|
+
windowSetTimeout: typeof window !== 'undefined' ? window.setTimeout : undefined,
|
|
82
|
+
windowClearTimeout: typeof window !== 'undefined' ? window.clearTimeout : undefined,
|
|
83
|
+
windowSetInterval: typeof window !== 'undefined' ? window.setInterval : undefined,
|
|
84
|
+
windowClearInterval: typeof window !== 'undefined' ? window.clearInterval : undefined,
|
|
85
|
+
windowDate: typeof window !== 'undefined' ? window.Date : undefined,
|
|
86
|
+
windowRequestAnimationFrame: typeof window !== 'undefined' ? window.requestAnimationFrame : undefined,
|
|
87
|
+
windowCancelAnimationFrame: typeof window !== 'undefined' ? window.cancelAnimationFrame : undefined,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
_now = _originals.Date.now();
|
|
91
|
+
_idSeq = 1;
|
|
92
|
+
_timers.clear();
|
|
93
|
+
|
|
94
|
+
const FakeDate = _makeFakeDate(_originals.Date);
|
|
95
|
+
|
|
96
|
+
global.setTimeout = fakeSetTimeout;
|
|
97
|
+
global.clearTimeout = fakeClearTimeout;
|
|
98
|
+
global.setInterval = fakeSetInterval;
|
|
99
|
+
global.clearInterval = fakeClearInterval;
|
|
100
|
+
global.Date = FakeDate;
|
|
101
|
+
// Compiled app code calls the bare `requestAnimationFrame` identifier, which
|
|
102
|
+
// resolves via normal scope lookup to `global.requestAnimationFrame` — not
|
|
103
|
+
// `window.requestAnimationFrame` — so both must be faked, not just window's.
|
|
104
|
+
global.requestAnimationFrame = fakeRequestAnimationFrame;
|
|
105
|
+
global.cancelAnimationFrame = fakeCancelAnimationFrame;
|
|
106
|
+
|
|
107
|
+
if (typeof window !== 'undefined') {
|
|
108
|
+
window.setTimeout = fakeSetTimeout;
|
|
109
|
+
window.clearTimeout = fakeClearTimeout;
|
|
110
|
+
window.setInterval = fakeSetInterval;
|
|
111
|
+
window.clearInterval = fakeClearInterval;
|
|
112
|
+
window.Date = FakeDate;
|
|
113
|
+
window.requestAnimationFrame = fakeRequestAnimationFrame;
|
|
114
|
+
window.cancelAnimationFrame = fakeCancelAnimationFrame;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_installed = true;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
global.useRealTimers = function useRealTimers() {
|
|
121
|
+
if (!_installed) return;
|
|
122
|
+
|
|
123
|
+
global.setTimeout = _originals.setTimeout;
|
|
124
|
+
global.clearTimeout = _originals.clearTimeout;
|
|
125
|
+
global.setInterval = _originals.setInterval;
|
|
126
|
+
global.clearInterval = _originals.clearInterval;
|
|
127
|
+
global.Date = _originals.Date;
|
|
128
|
+
global.requestAnimationFrame = _originals.requestAnimationFrame;
|
|
129
|
+
global.cancelAnimationFrame = _originals.cancelAnimationFrame;
|
|
130
|
+
|
|
131
|
+
if (typeof window !== 'undefined') {
|
|
132
|
+
window.setTimeout = _originals.windowSetTimeout;
|
|
133
|
+
window.clearTimeout = _originals.windowClearTimeout;
|
|
134
|
+
window.setInterval = _originals.windowSetInterval;
|
|
135
|
+
window.clearInterval = _originals.windowClearInterval;
|
|
136
|
+
window.Date = _originals.windowDate;
|
|
137
|
+
window.requestAnimationFrame = _originals.windowRequestAnimationFrame;
|
|
138
|
+
window.cancelAnimationFrame = _originals.windowCancelAnimationFrame;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_timers.clear();
|
|
142
|
+
_installed = false;
|
|
143
|
+
_originals = null;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
global.advanceTimersByTime = function advanceTimersByTime(ms) {
|
|
147
|
+
_assertFake('advanceTimersByTime');
|
|
148
|
+
const target = _now + Math.max(0, Number(ms) || 0);
|
|
149
|
+
let iterations = 0;
|
|
150
|
+
|
|
151
|
+
for (;;) {
|
|
152
|
+
const next = _earliestDue(timer => timer.due <= target);
|
|
153
|
+
if (!next) break;
|
|
154
|
+
if (++iterations > MAX_LOOP_ITERATIONS) {
|
|
155
|
+
throw new Error(`advanceTimersByTime: exceeded ${MAX_LOOP_ITERATIONS} timer firings — likely an interval rescheduling itself with delay 0`);
|
|
156
|
+
}
|
|
157
|
+
_now = next.due;
|
|
158
|
+
_fire(next);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_now = target;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
global.tick = global.advanceTimersByTime;
|
|
165
|
+
|
|
166
|
+
global.runAllTimers = function runAllTimers() {
|
|
167
|
+
_assertFake('runAllTimers');
|
|
168
|
+
let iterations = 0;
|
|
169
|
+
|
|
170
|
+
while (_timers.size > 0) {
|
|
171
|
+
if (++iterations > MAX_LOOP_ITERATIONS) {
|
|
172
|
+
throw new Error(`runAllTimers: aborting after ${MAX_LOOP_ITERATIONS} timers — likely an infinite loop (e.g. an uncleared setInterval)`);
|
|
173
|
+
}
|
|
174
|
+
const next = _earliestDue(() => true);
|
|
175
|
+
_now = Math.max(_now, next.due);
|
|
176
|
+
_fire(next);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
global.runOnlyPendingTimers = function runOnlyPendingTimers() {
|
|
181
|
+
_assertFake('runOnlyPendingTimers');
|
|
182
|
+
const pendingIds = new Set(_timers.keys());
|
|
183
|
+
|
|
184
|
+
for (;;) {
|
|
185
|
+
const next = _earliestDue(timer => pendingIds.has(timer.id));
|
|
186
|
+
if (!next) break;
|
|
187
|
+
pendingIds.delete(next.id);
|
|
188
|
+
_now = Math.max(_now, next.due);
|
|
189
|
+
_fire(next);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
global.clearAllTimers = function clearAllTimers() {
|
|
194
|
+
_assertFake('clearAllTimers');
|
|
195
|
+
_timers.clear();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
global.getTimerCount = function getTimerCount() {
|
|
199
|
+
_assertFake('getTimerCount');
|
|
200
|
+
return _timers.size;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
global.setSystemTime = function setSystemTime(msOrDate) {
|
|
204
|
+
_assertFake('setSystemTime');
|
|
205
|
+
_now = msOrDate instanceof Date ? msOrDate.getTime() : Number(msOrDate);
|
|
206
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { JSDOM } from 'jsdom';
|
|
4
|
-
import
|
|
4
|
+
import { print } from '../../../utils/index.js';
|
|
5
|
+
import config from '#root/nutin.config.js';
|
|
6
|
+
|
|
5
7
|
let currentDom = null;
|
|
8
|
+
const nativeGlobalKeys = new Set(Object.getOwnPropertyNames(global));
|
|
6
9
|
|
|
7
10
|
export function setupJsdom(htmlPath = 'dist/src/index.html') {
|
|
8
11
|
const html = fs.readFileSync(path.resolve(process.cwd(), htmlPath), 'utf8');
|
|
@@ -12,7 +15,7 @@ export function setupJsdom(htmlPath = 'dist/src/index.html') {
|
|
|
12
15
|
resources,
|
|
13
16
|
freezeGlobals,
|
|
14
17
|
pretendToBeVisual,
|
|
15
|
-
} = config.jsdomOptions;
|
|
18
|
+
} = config.testinNutin.jsdomOptions;
|
|
16
19
|
|
|
17
20
|
const dom = new JSDOM(html, {
|
|
18
21
|
url: 'http://localhost',
|
|
@@ -26,9 +29,11 @@ export function setupJsdom(htmlPath = 'dist/src/index.html') {
|
|
|
26
29
|
global.window = dom.window;
|
|
27
30
|
global.document = dom.window.document;
|
|
28
31
|
|
|
29
|
-
// DOM globals
|
|
32
|
+
// DOM globals — re-synced every call so classes like HTMLInputElement always
|
|
33
|
+
// match the current jsdom realm, not whichever realm happened to run first.
|
|
34
|
+
// Real Node globals (console, setTimeout, fetch, Event, ...) are left untouched.
|
|
30
35
|
Object.getOwnPropertyNames(dom.window)
|
|
31
|
-
.filter(prop => !(prop
|
|
36
|
+
.filter(prop => !nativeGlobalKeys.has(prop))
|
|
32
37
|
.forEach(prop => {
|
|
33
38
|
global[prop] = dom.window[prop];
|
|
34
39
|
});
|
|
@@ -41,7 +46,13 @@ export function setupJsdom(htmlPath = 'dist/src/index.html') {
|
|
|
41
46
|
removeEventListener: () => {},
|
|
42
47
|
});
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
// jsdom already ships a real (but "not implemented") scrollTo, so ??= never
|
|
50
|
+
// fires here — always override it with a working no-op.
|
|
51
|
+
window.scrollTo = () => {};
|
|
52
|
+
|
|
53
|
+
// jsdom doesn't implement Element.prototype.scrollIntoView at all (no layout
|
|
54
|
+
// engine to compute a real scroll position), so any code calling it throws.
|
|
55
|
+
window.Element.prototype.scrollIntoView ??= function () {};
|
|
45
56
|
|
|
46
57
|
window.requestAnimationFrame ??= (cb) => setTimeout(cb, 0);
|
|
47
58
|
window.cancelAnimationFrame ??= (id) => clearTimeout(id);
|
|
@@ -72,7 +83,9 @@ export function teardownJsdom() {
|
|
|
72
83
|
if (currentDom) {
|
|
73
84
|
try {
|
|
74
85
|
currentDom.window.close();
|
|
75
|
-
} catch {
|
|
86
|
+
} catch (err) {
|
|
87
|
+
print.grayError(`[jsdom] window.close() failed: ${err.message}`);
|
|
88
|
+
}
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
currentDom = null;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { print, addTest } from '
|
|
2
|
-
import config from '#root/testin-nutin.config.js';
|
|
1
|
+
import { print, addTest } from '../index.js';
|
|
3
2
|
import { setupJsdom, teardownJsdom, resetDom, flushPromises } from './jsdom-setup.js';
|
|
3
|
+
import { silenceConsole } from './silence-console.js';
|
|
4
4
|
import './assertion-lib.js';
|
|
5
5
|
import './spyon.js';
|
|
6
|
+
import './clock.js';
|
|
7
|
+
|
|
8
|
+
const verboseEnabled = process.argv.includes('--verbose');
|
|
6
9
|
|
|
7
10
|
export function registerTestGlobals() {
|
|
8
11
|
let currentSuite = '';
|
|
@@ -16,7 +19,7 @@ export function registerTestGlobals() {
|
|
|
16
19
|
afterEach: null,
|
|
17
20
|
afterAll: null
|
|
18
21
|
};
|
|
19
|
-
if (
|
|
22
|
+
if (verboseEnabled) print.boldSuccess('📂 ' + name);
|
|
20
23
|
fn();
|
|
21
24
|
};
|
|
22
25
|
|
|
@@ -31,6 +34,9 @@ export function registerTestGlobals() {
|
|
|
31
34
|
afterAll: currentSuiteHooks.afterAll
|
|
32
35
|
});
|
|
33
36
|
};
|
|
37
|
+
global.it.todo = (s) => {
|
|
38
|
+
addTest({ suiteName: currentSuite, testName: s, isTodo: true });
|
|
39
|
+
};
|
|
34
40
|
|
|
35
41
|
global.beforeAll = (fn) => {
|
|
36
42
|
currentSuiteHooks.beforeAll = fn;
|
|
@@ -49,11 +55,14 @@ export function registerTestGlobals() {
|
|
|
49
55
|
global.teardownJsdom = teardownJsdom;
|
|
50
56
|
global.resetDom = resetDom;
|
|
51
57
|
global.flushPromises = flushPromises;
|
|
58
|
+
global.silenceConsole = silenceConsole;
|
|
52
59
|
|
|
53
60
|
global.$ = (selector) => document.querySelector(selector);
|
|
54
61
|
global.$$ = (selector) => [...document.querySelectorAll(selector)];
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
// cancelable: true matches a real browser's native click event, so code
|
|
64
|
+
// under test that calls preventDefault() actually sees defaultPrevented.
|
|
65
|
+
global.click = (el) => el.dispatchEvent(new window.MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
57
66
|
global.type = (el, text) => { el.value = text; el.dispatchEvent(new window.InputEvent("input", { bubbles: true })); };
|
|
58
67
|
|
|
59
68
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function silenceConsole(methodOrMethods, fn) {
|
|
2
|
+
const methods = Array.isArray(methodOrMethods) ? methodOrMethods : [methodOrMethods];
|
|
3
|
+
const spies = methods.map((method) => spyOn(console, method).andCallFake(() => {}));
|
|
4
|
+
const restore = () => spies.forEach((spy) => spy.restore());
|
|
5
|
+
|
|
6
|
+
let result;
|
|
7
|
+
try {
|
|
8
|
+
result = fn();
|
|
9
|
+
} catch (err) {
|
|
10
|
+
restore();
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (result && typeof result.then === 'function') {
|
|
15
|
+
return result.then(
|
|
16
|
+
(value) => { restore(); return value; },
|
|
17
|
+
(err) => { restore(); throw err; }
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
restore();
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -47,11 +47,13 @@ global.spyOn = (obj, methodName) => {
|
|
|
47
47
|
andCallFake(fn) {
|
|
48
48
|
spyFunction._fake = fn;
|
|
49
49
|
spyFunction._hasReturn = false;
|
|
50
|
+
return handle;
|
|
50
51
|
},
|
|
51
52
|
andReturn(value) {
|
|
52
53
|
spyFunction._returnValue = value;
|
|
53
54
|
spyFunction._hasReturn = true;
|
|
54
55
|
spyFunction._fake = null;
|
|
56
|
+
return handle;
|
|
55
57
|
}
|
|
56
58
|
};
|
|
57
59
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './globals/jsdom-setup.js';
|
|
2
|
+
export * from './globals/register-test-globals.js';
|
|
3
|
+
export * from './queue/test-discovery.js';
|
|
4
|
+
export * from './queue/test-queue.js';
|
|
5
|
+
export * from './printer.js';
|
|
6
|
+
export * from './coverage/collect-coverage.js';
|
|
7
|
+
export * from './coverage/compute-coverage.js';
|
|
8
|
+
export * from './coverage/write-uncovered-report.js';
|
|
9
|
+
export * from './coverage/write-summary-report.js';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { print, chalk } from '../../utils/index.js';
|
|
2
|
+
import config from '#root/nutin.config.js';
|
|
3
|
+
|
|
4
|
+
export { chalk, print } from '../../utils/index.js';
|
|
5
|
+
|
|
6
|
+
const verboseEnabled = process.argv.includes('--verbose');
|
|
7
|
+
|
|
8
|
+
export function printSummary(todo, passed, failed, time) {
|
|
9
|
+
const totalTime = time.toFixed(2);
|
|
10
|
+
const total = passed + failed;
|
|
11
|
+
|
|
12
|
+
print.boldBlue(`Test Summary`);
|
|
13
|
+
print.boldInfo(`✓ ${passed} passed`);
|
|
14
|
+
print.boldError(`✗ ${failed} failed`);
|
|
15
|
+
print.info(`📖 ${total} total`);
|
|
16
|
+
print.info(`⌚️ Time: ${totalTime}ms\n`);
|
|
17
|
+
if (todo > 0) print.boldWarn(`⚠️ To do: ${todo}\n`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function printResults(results) {
|
|
21
|
+
const statusOrder = { passed: 0, todo: 1, failed: 2 };
|
|
22
|
+
const sortedResults = results.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
|
|
23
|
+
|
|
24
|
+
sortedResults.forEach(result => {
|
|
25
|
+
if (result.status === 'passed') {
|
|
26
|
+
if (verboseEnabled) console.log(`${chalk.green('✓')} ${chalk.cyan(`${result.suiteName}`)} - ${chalk.green(`${result.name}`)}`);
|
|
27
|
+
} else if (result.status === 'todo') {
|
|
28
|
+
print.warn(`TODO: ${result.name} in ${toTsPath(result.file)}`);
|
|
29
|
+
} else {
|
|
30
|
+
print.error(`✗ ${result.suiteName} - ${result.name}`);
|
|
31
|
+
print.grayError(result.error);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function printStart() {
|
|
37
|
+
print.boldHead('\nRunning tests...\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function printCoverageReport(report) {
|
|
41
|
+
if (!report.length) {
|
|
42
|
+
print.warn('No app source files were covered.');
|
|
43
|
+
return { globalLinesPct: 100, globalFunctionsPct: 100, globalBranchesPct: 100 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
print.boldBlue('\nCoverage Summary');
|
|
47
|
+
console.table(
|
|
48
|
+
report
|
|
49
|
+
.sort((a, b) => a.file.localeCompare(b.file))
|
|
50
|
+
.map(r => ({
|
|
51
|
+
File: toTsPath(r.file),
|
|
52
|
+
'Lines %': r.linesPct.toFixed(1),
|
|
53
|
+
'Functions %': r.functionsPct.toFixed(1),
|
|
54
|
+
'Branches %': r.branchesPct.toFixed(1),
|
|
55
|
+
}))
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const totals = report.reduce((acc, r) => ({
|
|
59
|
+
coveredLines: acc.coveredLines + r.coveredLines,
|
|
60
|
+
totalLines: acc.totalLines + r.totalLines,
|
|
61
|
+
coveredBranches: acc.coveredBranches + r.coveredBranches,
|
|
62
|
+
totalBranches: acc.totalBranches + r.totalBranches,
|
|
63
|
+
coveredFunctions: acc.coveredFunctions + r.coveredFunctions,
|
|
64
|
+
totalFunctions: acc.totalFunctions + r.totalFunctions,
|
|
65
|
+
}), { coveredLines: 0, totalLines: 0, coveredBranches: 0, totalBranches: 0, coveredFunctions: 0, totalFunctions: 0 });
|
|
66
|
+
|
|
67
|
+
const globalLinesPct = totals.totalLines ? (totals.coveredLines / totals.totalLines) * 100 : 100;
|
|
68
|
+
const globalFunctionsPct = totals.totalFunctions ? (totals.coveredFunctions / totals.totalFunctions) * 100 : 100;
|
|
69
|
+
const globalBranchesPct = totals.totalBranches ? (totals.coveredBranches / totals.totalBranches) * 100 : 100;
|
|
70
|
+
|
|
71
|
+
const threshold = config.testinNutin.coverage?.threshold;
|
|
72
|
+
const thresholdSuffix = typeof threshold === 'number' ? ` (threshold: ${threshold}%)` : '';
|
|
73
|
+
print.boldInfo(`Global: ${globalBranchesPct.toFixed(1)}% branches, ${globalFunctionsPct.toFixed(1)}% functions, ${globalLinesPct.toFixed(1)}% lines${thresholdSuffix}\n`);
|
|
74
|
+
|
|
75
|
+
return { globalLinesPct, globalFunctionsPct, globalBranchesPct };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function toTsPath(input) {
|
|
79
|
+
return input
|
|
80
|
+
.split('/')
|
|
81
|
+
.slice(1)
|
|
82
|
+
.join('/')
|
|
83
|
+
.replace(/\.js$/, '.ts');
|
|
84
|
+
}
|
|
85
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import Queue from './queue.js';
|
|
2
|
+
|
|
3
|
+
describe('Queue', () => {
|
|
4
|
+
it('starts empty', () => {
|
|
5
|
+
const queue = new Queue();
|
|
6
|
+
expect(queue.length).toBe(0);
|
|
7
|
+
expect(queue.deque()).toBe(undefined);
|
|
8
|
+
expect(queue.peek()).toBe(undefined);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('dequeues items in FIFO order', () => {
|
|
12
|
+
const queue = new Queue();
|
|
13
|
+
queue.enqueue('a');
|
|
14
|
+
queue.enqueue('b');
|
|
15
|
+
queue.enqueue('c');
|
|
16
|
+
|
|
17
|
+
expect(queue.length).toBe(3);
|
|
18
|
+
expect(queue.deque()).toBe('a');
|
|
19
|
+
expect(queue.deque()).toBe('b');
|
|
20
|
+
expect(queue.length).toBe(1);
|
|
21
|
+
expect(queue.deque()).toBe('c');
|
|
22
|
+
expect(queue.length).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('peek returns the next item without removing it', () => {
|
|
26
|
+
const queue = new Queue();
|
|
27
|
+
queue.enqueue('first');
|
|
28
|
+
queue.enqueue('second');
|
|
29
|
+
|
|
30
|
+
expect(queue.peek()).toBe('first');
|
|
31
|
+
expect(queue.peek()).toBe('first');
|
|
32
|
+
expect(queue.length).toBe(2);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('handles a single enqueue/deque cycle and can be reused afterward', () => {
|
|
36
|
+
const queue = new Queue();
|
|
37
|
+
queue.enqueue('only');
|
|
38
|
+
expect(queue.deque()).toBe('only');
|
|
39
|
+
expect(queue.length).toBe(0);
|
|
40
|
+
expect(queue.deque()).toBe(undefined);
|
|
41
|
+
|
|
42
|
+
queue.enqueue('again');
|
|
43
|
+
expect(queue.length).toBe(1);
|
|
44
|
+
expect(queue.deque()).toBe('again');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { getTestFiles } from './test-discovery.js';
|
|
6
|
+
|
|
7
|
+
// getTestFiles resolves its own `projectRoot` anchor one directory short of the real
|
|
8
|
+
// project root (see tools/testin-nutin/runner.js, which always calls it as
|
|
9
|
+
// `getTestFiles(path.join('..', origin))` to compensate). This test file lives in the
|
|
10
|
+
// same directory as test-discovery.js, so it can recompute that same anchor to build
|
|
11
|
+
// `dir` arguments the same way the real runner does.
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const relativeToCwd = path.relative(process.cwd(), __dirname);
|
|
14
|
+
const levelsUp = relativeToCwd.split(path.sep).length - 1;
|
|
15
|
+
const projectRoot = path.resolve(__dirname, ...Array(levelsUp).fill('..'));
|
|
16
|
+
|
|
17
|
+
describe('getTestFiles', () => {
|
|
18
|
+
let tmpDir;
|
|
19
|
+
let relDir;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-test-discovery-'));
|
|
23
|
+
relDir = path.relative(projectRoot, tmpDir);
|
|
24
|
+
|
|
25
|
+
fs.mkdirSync(path.join(tmpDir, 'nested'), { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(tmpDir, 'a.test.js'), '');
|
|
27
|
+
fs.writeFileSync(path.join(tmpDir, 'b.js'), '');
|
|
28
|
+
fs.writeFileSync(path.join(tmpDir, 'nested', 'c.test.js'), '');
|
|
29
|
+
fs.writeFileSync(path.join(tmpDir, 'nested', 'readme.md'), '');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('recursively finds only files ending in .test.js', () => {
|
|
37
|
+
const files = getTestFiles(relDir).sort();
|
|
38
|
+
expect(files).toEqual(
|
|
39
|
+
[path.join(tmpDir, 'a.test.js'), path.join(tmpDir, 'nested', 'c.test.js')].sort()
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns an empty array for a directory that does not exist', () => {
|
|
44
|
+
expect(getTestFiles(path.join(relDir, 'does-not-exist'))).toEqual([]);
|
|
45
|
+
});
|
|
46
|
+
});
|