diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16855c7..1fa1299 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,9 @@ jobs: - run: npm run dep:check - run: npm run format:check - run: npm run check:tokens - - run: npm test + # Runs the full suite AND reports coverage (WP-46, report-only — no thresholds, so + # it can't fail on coverage; it still fails on a failing test, like `npm test` did). + - run: npm run test:coverage # --localize builds every configured locale (nl + en, angular.json's i18n # block) in one pass; i18nMissingTranslation:"error" (angular.json) fails # this step if messages.en.xlf is missing a unit the source (WP-20) gains. diff --git a/angular.json b/angular.json index c81bdef..c2f9e9e 100644 --- a/angular.json +++ b/angular.json @@ -91,7 +91,22 @@ "defaultConfiguration": "development" }, "test": { - "builder": "@angular/build:unit-test" + "builder": "@angular/build:unit-test", + "configurations": { + "coverage": { + "coverage": true, + "coverageReporters": ["text-summary", "html", "lcov"], + "coverageExclude": [ + "**/*.spec.ts", + "**/*.stories.ts", + "**/contracts/**", + "src/app/shared/infrastructure/api-client.ts", + "src/main.ts", + "src/test-setup.ts", + "**/*.d.ts" + ] + } + } }, "storybook": { "builder": "@storybook/angular:start-storybook", diff --git a/docs/project/backlog/README.md b/docs/project/backlog/README.md index f7e725d..a922e36 100644 --- a/docs/project/backlog/README.md +++ b/docs/project/backlog/README.md @@ -90,7 +90,7 @@ for its existing violations, so every WP ends green. | [WP-43](WP-43-scaffold-generators.md) | Runnable generators: value-object / form-machine / bff-endpoint / ui-component | 8 · platform/DX/showcase | todo | | [WP-44](WP-44-context-generator.md) | Runnable generator: `gen:context` | 8 · platform/DX/showcase | todo | | [WP-45](WP-45-create-ssp-generator.md) | `create-ssp` bootstrap generator (mechanise new-ssp) | 8 · platform/DX/showcase | todo | -| [WP-46](WP-46-vitest-coverage.md) | Vitest coverage (report + report-only thresholds) | 8 · platform/DX/showcase | todo | +| [WP-46](WP-46-vitest-coverage.md) | Vitest coverage (report + report-only thresholds) | 8 · platform/DX/showcase | done | Sequencing dependencies (stated in the WPs too): 01 before 10–15 (axe covers story churn); 03/04 before 05–09 (boundaries stop new violations during refactors); 06 before 07 (typed diff --git a/docs/project/backlog/WP-46-vitest-coverage.md b/docs/project/backlog/WP-46-vitest-coverage.md index 2c33025..9cc3766 100644 --- a/docs/project/backlog/WP-46-vitest-coverage.md +++ b/docs/project/backlog/WP-46-vitest-coverage.md @@ -1,9 +1,21 @@ # WP-46 — Vitest coverage (report + thresholds) -Status: todo +Status: done Phase: 8 — platform/DX/showcase Priority: P1 +## Outcome + +Coverage is first-class in the `@angular/build:unit-test` builder — no separate vitest config +needed. Added a `coverage` **configuration** to the `test` target in `angular.json` (`coverage: true`, +reporters `text-summary`/`html`/`lcov`, excludes for specs/stories/contracts/generated +`api-client.ts`/`main.ts`/`.d.ts`) + `@vitest/coverage-v8` (pinned to vitest's 4.1.9 to avoid the +mixed-version warning). `npm run test:coverage` → `ng test --configuration coverage`. **Report-only +(no thresholds)** per the ponytail default — visibility first. CI's test step now runs +`test:coverage` (a strict superset of `npm test`: fails on a failing test, never on coverage). +Baseline at delivery: **~71% statements / 69% branches / 68% functions / 74% lines.** Output dir +`coverage/` was already git-ignored. Path to enforce later: add `coverageThresholds` to the config. + ## Why Tests run via the `@angular/build:unit-test` (Vitest) builder but report no coverage — there's no @@ -29,6 +41,6 @@ visibility into what the house test rules actually cover. A template/showcase sh ## Acceptance criteria -- [ ] `npm run test:coverage` produces a report (text + HTML/lcov) with sensible excludes. -- [ ] Coverage runs in CI without failing the build (report-only). -- [ ] `npm run ci` green. +- [x] `npm run test:coverage` produces a report (text-summary + HTML/lcov) with sensible excludes. +- [x] Coverage runs in CI without failing the build (report-only). +- [x] `npm run ci` green. diff --git a/package-lock.json b/package-lock.json index 74090f7..24fb333 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@storybook/addon-onboarding": "^10.4.6", "@storybook/angular": "^10.4.6", "@storybook/test-runner": "^0.24.4", + "@vitest/coverage-v8": "^4.1.9", "angular-eslint": "^22.0.0", "axe-playwright": "^2.2.2", "concurrently": "^10.0.3", @@ -10409,6 +10410,92 @@ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.9.tgz", + "integrity": "sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.9", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.9", + "vitest": "4.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/@vitest/pretty-format": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/@vitest/utils": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/coverage-v8/node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -11241,6 +11328,25 @@ "node": ">=4" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -18686,6 +18792,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", diff --git a/package.json b/package.json index ef54059..634095c 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test", + "test:coverage": "ng test --configuration coverage", "storybook": "ng run atomic-design-poc:storybook", "build-storybook": "ng run atomic-design-poc:build-storybook", "test-storybook": "test-storybook", @@ -50,6 +51,7 @@ "@storybook/addon-onboarding": "^10.4.6", "@storybook/angular": "^10.4.6", "@storybook/test-runner": "^0.24.4", + "@vitest/coverage-v8": "^4.1.9", "angular-eslint": "^22.0.0", "axe-playwright": "^2.2.2", "concurrently": "^10.0.3", diff --git a/scripts/ci-local.sh b/scripts/ci-local.sh index 62f6035..b7dd3e6 100755 --- a/scripts/ci-local.sh +++ b/scripts/ci-local.sh @@ -15,7 +15,7 @@ step "lint"; npm run lint step "dependency boundaries"; npm run dep:check step "format:check (prettier)"; npm run format:check step "check:tokens"; npm run check:tokens -step "test (vitest)"; npm test +step "test (vitest + coverage)"; npm run test:coverage step "build --localize (nl+en)"; npx ng build --localize step "npm audit (shipped deps)"; npm audit --omit=dev step "backend format + tests"; ( cd backend && dotnet format BigRegister.slnx --verify-no-changes && dotnet test BigRegister.slnx )