@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,54 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { print, toTsPath } from '../printer.js';
|
|
4
|
+
import { errorExit } from '../../../utils/index.js';
|
|
5
|
+
import config from '#root/nutin.config.js';
|
|
6
|
+
|
|
7
|
+
const REPORT_PATH = path.join('coverage', 'summary.md');
|
|
8
|
+
|
|
9
|
+
export function writeSummaryReport(report, globals, testResults) {
|
|
10
|
+
if (!report.length) return;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
|
|
14
|
+
fs.writeFileSync(REPORT_PATH, buildMarkdown(report, globals, testResults));
|
|
15
|
+
} catch (err) {
|
|
16
|
+
errorExit(err, 'coverage/write-summary-report');
|
|
17
|
+
}
|
|
18
|
+
print.info(`Wrote ${REPORT_PATH}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildMarkdown(report, globals, testResults) {
|
|
22
|
+
const rows = report
|
|
23
|
+
.slice()
|
|
24
|
+
.sort((a, b) => a.file.localeCompare(b.file))
|
|
25
|
+
.map(r => `| ${toTsPath(r.file)} | ${r.linesPct.toFixed(1)} | ${r.functionsPct.toFixed(1)} | ${r.branchesPct.toFixed(1)} |`)
|
|
26
|
+
.join('\n');
|
|
27
|
+
|
|
28
|
+
const threshold = config.testinNutin.coverage?.threshold;
|
|
29
|
+
const thresholdLine = typeof threshold === 'number' ? `\n- Threshold: ${threshold}%` : '';
|
|
30
|
+
|
|
31
|
+
const { todo, passed, failed, elapsedTime } = testResults;
|
|
32
|
+
const total = passed + failed;
|
|
33
|
+
const todoLine = todo > 0 ? `\n- ⚠️ To do: ${todo}` : '';
|
|
34
|
+
|
|
35
|
+
return `# Coverage Summary
|
|
36
|
+
|
|
37
|
+
## Test Summary
|
|
38
|
+
|
|
39
|
+
- ✓ ${passed} passed
|
|
40
|
+
- ✗ ${failed} failed
|
|
41
|
+
- 📖 ${total} total
|
|
42
|
+
- ⌚️ Time: ${elapsedTime.toFixed(2)}ms${todoLine}
|
|
43
|
+
|
|
44
|
+
| File | Lines % | Functions % | Branches % |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
${rows}
|
|
47
|
+
|
|
48
|
+
## Global
|
|
49
|
+
|
|
50
|
+
- Lines: ${globals.globalLinesPct.toFixed(1)}%
|
|
51
|
+
- Functions: ${globals.globalFunctionsPct.toFixed(1)}%
|
|
52
|
+
- Branches: ${globals.globalBranchesPct.toFixed(1)}%${thresholdLine}
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { print } from '../printer.js';
|
|
4
|
+
import { errorExit } from '../../../utils/index.js';
|
|
5
|
+
import config from '#root/nutin.config.js';
|
|
6
|
+
|
|
7
|
+
const REPORT_PATH = path.join('coverage', 'uncovered.md');
|
|
8
|
+
|
|
9
|
+
export async function maybeWriteUncoveredReport(report) {
|
|
10
|
+
const uncovered = report.filter(r => r.uncoveredLines.length || r.uncoveredBranches.length || r.uncoveredFunctions.length);
|
|
11
|
+
if (!uncovered.length) {
|
|
12
|
+
print.info('🎉 No uncovered code found.\n');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!config.testinNutin.coverage.reportUncovered) return;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
|
|
20
|
+
fs.writeFileSync(REPORT_PATH, buildMarkdown(uncovered));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
errorExit(err, 'coverage/write-uncovered-report');
|
|
23
|
+
}
|
|
24
|
+
print.info(`Wrote ${REPORT_PATH}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildMarkdown(uncovered) {
|
|
28
|
+
const sections = uncovered
|
|
29
|
+
.sort((a, b) => a.file.localeCompare(b.file))
|
|
30
|
+
.map(r => {
|
|
31
|
+
const branches = r.uncoveredBranches.length
|
|
32
|
+
? `Uncovered branches (lines): ${r.uncoveredBranches.join(', ')}\n`
|
|
33
|
+
: '';
|
|
34
|
+
const fns = r.uncoveredFunctions.length
|
|
35
|
+
? `Uncovered functions:\n${r.uncoveredFunctions.map(f => `- \`${f.name}\` (line ${f.line})`).join('\n')}\n`
|
|
36
|
+
: '';
|
|
37
|
+
const lines = r.uncoveredLines.length
|
|
38
|
+
? `Uncovered lines: ${r.uncoveredLines.join(', ')}\n`
|
|
39
|
+
: '';
|
|
40
|
+
return `## ${r.file}\n\n${branches}${fns}${lines}`;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return `# Uncovered Code\n\nIMPORTANT: Line numbers refer to **compiled** .js code (\`dist/src/\`).\n\n${sections.join('\n')}`;
|
|
44
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
class AssertionError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'AssertionError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
global.AssertionError = AssertionError;
|
|
9
|
+
|
|
1
10
|
global.expect = (actual) => {
|
|
2
11
|
const expectObj = {
|
|
3
12
|
toBe(expected) {
|
|
4
13
|
if (actual !== expected) {
|
|
5
|
-
throw new
|
|
14
|
+
throw new AssertionError(`Expected ${actual} to be strictly equal to ${expected}`);
|
|
6
15
|
}
|
|
7
16
|
},
|
|
8
17
|
toEqual(expected) {
|
|
@@ -22,42 +31,42 @@ global.expect = (actual) => {
|
|
|
22
31
|
};
|
|
23
32
|
|
|
24
33
|
if (!deepEqual(actual, expected)) {
|
|
25
|
-
throw new
|
|
34
|
+
throw new AssertionError(`Expected ${JSON.stringify(actual)} to deeply equal ${JSON.stringify(expected)}`);
|
|
26
35
|
}
|
|
27
36
|
},
|
|
28
37
|
toContain(substring) {
|
|
29
38
|
if (!actual.includes(substring)) {
|
|
30
|
-
throw new
|
|
39
|
+
throw new AssertionError(`Expected '${actual}' to contain '${substring}'`);
|
|
31
40
|
}
|
|
32
41
|
},
|
|
33
42
|
toBeTruthy() {
|
|
34
43
|
if (!actual) {
|
|
35
|
-
throw new
|
|
44
|
+
throw new AssertionError(`Expected ${actual} to be truthy`);
|
|
36
45
|
}
|
|
37
46
|
},
|
|
38
47
|
toBeFalsy() {
|
|
39
48
|
if (actual) {
|
|
40
|
-
throw new
|
|
49
|
+
throw new AssertionError(`Expected ${actual} to be falsy`);
|
|
41
50
|
}
|
|
42
51
|
},
|
|
43
52
|
toBeDefined() {
|
|
44
53
|
if (actual === null || actual === undefined) {
|
|
45
|
-
throw new
|
|
54
|
+
throw new AssertionError(`Expected value to be defined, but got ${actual}`);
|
|
46
55
|
}
|
|
47
56
|
},
|
|
48
57
|
toBeUndefined() {
|
|
49
58
|
if (actual !== null && actual !== undefined) {
|
|
50
|
-
throw new
|
|
59
|
+
throw new AssertionError(`Expected value to be undefined, but got ${actual}`);
|
|
51
60
|
}
|
|
52
61
|
},
|
|
53
62
|
toBeInstanceOf(constructor) {
|
|
54
63
|
if (!(actual instanceof constructor)) {
|
|
55
|
-
throw new
|
|
64
|
+
throw new AssertionError(`Expected ${actual} to be instance of ${constructor.name}`);
|
|
56
65
|
}
|
|
57
66
|
},
|
|
58
67
|
toHaveBeenCalled() {
|
|
59
68
|
if (!actual?.calls || actual.calls.length === 0) {
|
|
60
|
-
throw new
|
|
69
|
+
throw new AssertionError(`Expected function to have been called, but it was not`);
|
|
61
70
|
}
|
|
62
71
|
},
|
|
63
72
|
toHaveBeenCalledWith(...expectedArgs) {
|
|
@@ -85,7 +94,7 @@ global.expect = (actual) => {
|
|
|
85
94
|
);
|
|
86
95
|
|
|
87
96
|
if (!matched) {
|
|
88
|
-
throw new
|
|
97
|
+
throw new AssertionError(
|
|
89
98
|
`Expected function to have been called with ${JSON.stringify(expectedArgs)}, but it was not.\n` +
|
|
90
99
|
`Actual calls:\n${JSON.stringify(actual?.calls, null, 2)}`
|
|
91
100
|
);
|
|
@@ -93,27 +102,40 @@ global.expect = (actual) => {
|
|
|
93
102
|
},
|
|
94
103
|
toBeLessThan(expected) {
|
|
95
104
|
if (actual > expected) {
|
|
96
|
-
throw new
|
|
105
|
+
throw new AssertionError(`Expected ${actual} to be less than ${expected}`)
|
|
97
106
|
}
|
|
98
107
|
},
|
|
99
108
|
toBeGreaterThan(expected) {
|
|
100
109
|
if (actual < expected) {
|
|
101
|
-
throw new
|
|
110
|
+
throw new AssertionError(`Expected ${actual} to be greater than ${expected}`)
|
|
102
111
|
}
|
|
103
112
|
},
|
|
104
113
|
toThrow(expectedMessage) {
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
actual();
|
|
108
|
-
} catch (err) {
|
|
109
|
-
threw = true;
|
|
114
|
+
const checkMessage = (err) => {
|
|
110
115
|
if (expectedMessage && !err.message.includes(expectedMessage)) {
|
|
111
|
-
throw new
|
|
116
|
+
throw new AssertionError(`Expected error message to include "${expectedMessage}", but got "${err.message}"`);
|
|
112
117
|
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
let result;
|
|
121
|
+
try {
|
|
122
|
+
result = actual();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
checkMessage(err);
|
|
125
|
+
return;
|
|
113
126
|
}
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
|
|
128
|
+
// actual() returned a promise (async function) — it can only reject
|
|
129
|
+
// asynchronously, so return a promise the caller must await instead of
|
|
130
|
+
// throwing synchronously.
|
|
131
|
+
if (result && typeof result.then === 'function') {
|
|
132
|
+
return result.then(
|
|
133
|
+
() => { throw new AssertionError(`Expected function to throw, but it did not`); },
|
|
134
|
+
(err) => { checkMessage(err); }
|
|
135
|
+
);
|
|
116
136
|
}
|
|
137
|
+
|
|
138
|
+
throw new AssertionError(`Expected function to throw, but it did not`);
|
|
117
139
|
},
|
|
118
140
|
};
|
|
119
141
|
|
|
@@ -124,12 +146,28 @@ global.expect = (actual) => {
|
|
|
124
146
|
if (typeof fn !== 'function') continue;
|
|
125
147
|
|
|
126
148
|
expectObj.not[name] = (...args) => {
|
|
149
|
+
const settle = (err) => {
|
|
150
|
+
if (err) {
|
|
151
|
+
if (err instanceof AssertionError) return;
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
throw new AssertionError(`Expected NOT: ${name}(${JSON.stringify(args)})`);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
let result;
|
|
127
158
|
try {
|
|
128
|
-
fn(...args);
|
|
129
|
-
} catch {
|
|
130
|
-
return;
|
|
159
|
+
result = fn(...args);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return settle(err);
|
|
131
162
|
}
|
|
132
|
-
|
|
163
|
+
|
|
164
|
+
// Only toThrow(), given an async function, can return a promise here —
|
|
165
|
+
// every other matcher (and toThrow given a sync function) is synchronous.
|
|
166
|
+
if (result && typeof result.then === 'function') {
|
|
167
|
+
return result.then(() => settle(), (err) => settle(err));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return settle();
|
|
133
171
|
};
|
|
134
172
|
}
|
|
135
173
|
return expectObj;
|
|
@@ -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';
|