diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 401b30a617..5e9f7e5e30 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -108,28 +108,28 @@ jobs: fi items=() [[ "$ADMIN" == "true" ]] && items+=( - '{"service":"admin","lint_command":"pnpm admin lint","build_command":"pnpm admin build","app_command":"pnpm admin build:app","app_dotenv":".env.prod","spec_command":"pnpm admin test"}' + '{"service":"admin","lint_command":"pnpm admin lint","build_command":"pnpm admin build","app_command":"pnpm admin build:app","app_dotenv":".env.prod","spec_command":"pnpm admin test:coverage"}' ) [[ "$AGENT" == "true" ]] && items+=( - '{"service":"agent","lint_command":"pnpm agent lint","build_command":"pnpm agent build","spec_command":"pnpm agent test"}' + '{"service":"agent","lint_command":"pnpm agent lint","build_command":"pnpm agent build","spec_command":"pnpm agent test:coverage"}' ) [[ "$AI_BOT" == "true" ]] && items+=( '{"service":"ai-bot","lint_command":"pnpm ai-bot lint","build_command":"pnpm ai-bot build","spec_command":"pnpm ai-bot test"}' ) [[ "$API" == "true" ]] && items+=( - '{"service":"api","lint_command":"pnpm api lint","build_command":"pnpm api build","spec_command":"pnpm api test:spec","e2e_command":"pnpm api test:e2e","migration_command":"pnpm api migration:run","dotenv_path":"./.env.test","needs_db":true}' + '{"service":"api","lint_command":"pnpm api lint","build_command":"pnpm api build","spec_command":"pnpm api test:coverage","e2e_command":"pnpm api test:e2e","migration_command":"pnpm api migration:run","dotenv_path":"./.env.test","needs_db":true}' ) [[ "$PACKAGES" == "true" ]] && items+=( - '{"service":"packages","lint_command":"pnpm packages lint","spec_command":"pnpm packages test"}' + '{"service":"packages","lint_command":"pnpm packages lint","spec_command":"pnpm packages test:coverage"}' ) [[ "$STORYBOOK" == "true" ]] && items+=( '{"service":"storybook","lint_command":"pnpm storybook lint","build_command":"pnpm storybook typecheck","app_command":"pnpm storybook build:app","app_dotenv":".env.prod","spec_command":"pnpm storybook test"}' ) [[ "$WEB" == "true" ]] && items+=( - '{"service":"web","lint_command":"pnpm web lint","build_command":"pnpm web build","app_command":"pnpm web build:app-server","app_dotenv":".env.prod","spec_command":"pnpm web test"}' + '{"service":"web","lint_command":"pnpm web lint","build_command":"pnpm web build","app_command":"pnpm web build:app-server","app_dotenv":".env.prod","spec_command":"pnpm web test:coverage"}' ) [[ "$WORKER" == "true" ]] && items+=( - '{"service":"worker","lint_command":"pnpm worker lint","build_command":"pnpm worker build","spec_command":"pnpm worker test","e2e_command":"pnpm worker test:e2e","dotenv_path":"./.env.test","needs_db":true}' + '{"service":"worker","lint_command":"pnpm worker lint","build_command":"pnpm worker build","spec_command":"pnpm worker test:coverage","e2e_command":"pnpm worker test:e2e","dotenv_path":"./.env.test","needs_db":true}' ) joined=$(IFS=,; echo "${items[*]}") echo "matrix={\"include\":[$joined]}" >> $GITHUB_OUTPUT diff --git a/packages/@liexp/backend/package.json b/packages/@liexp/backend/package.json index 91a209e7ef..59dd642420 100644 --- a/packages/@liexp/backend/package.json +++ b/packages/@liexp/backend/package.json @@ -21,6 +21,7 @@ "clean": "rm -rf lib", "lint": "eslint --cache src", "test": "vitest", + "test:coverage": "vitest --run --coverage", "watch": "tsc -b -w" }, "lint-staged": { diff --git a/packages/@liexp/backend/vitest.config.ts b/packages/@liexp/backend/vitest.config.ts index f296f03e44..5be577aa73 100644 --- a/packages/@liexp/backend/vitest.config.ts +++ b/packages/@liexp/backend/vitest.config.ts @@ -10,9 +10,10 @@ export default extendBaseConfig(import.meta.url, (toAlias) => ({ include: ["src/**/*.ts"], exclude: ["src/test"], thresholds: { - lines: 50, - statements: 50, - functions: 50, + lines: 80, + statements: 80, + branches: 80, + functions: 80, }, }, }, diff --git a/packages/@liexp/core/package.json b/packages/@liexp/core/package.json index 7c91f90b76..c57e9318ab 100644 --- a/packages/@liexp/core/package.json +++ b/packages/@liexp/core/package.json @@ -20,6 +20,7 @@ "clean": "rm -rf lib", "lint": "eslint --cache src", "test": "echo 'No tests configured for @liexp/core'", + "test:coverage": "vitest --run --coverage", "watch": "tsc -b -w" }, "lint-staged": { @@ -37,7 +38,8 @@ "eslint-plugin-import-x": "^4.16.2", "fp-ts": "^2.16.10", "typescript": "^5.9.3", - "vite-plugin-svgr": "^4.3.0" + "vite-plugin-svgr": "^4.3.0", + "vitest": "^4.1.2" }, "peerDependencies": { "debug": "*", diff --git a/packages/@liexp/core/vitest.config.ts b/packages/@liexp/core/vitest.config.ts new file mode 100644 index 0000000000..563f7c8e28 --- /dev/null +++ b/packages/@liexp/core/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + name: "@liexp/core", + globals: true, + include: ["src/**/*.spec.ts"], + watch: false, + coverage: { + include: ["src/**/*.ts"], + thresholds: { + lines: 80, + statements: 80, + branches: 80, + functions: 80, + }, + }, + }, +}); diff --git a/packages/@liexp/io/package.json b/packages/@liexp/io/package.json index c0378c1cac..9fa025eb30 100644 --- a/packages/@liexp/io/package.json +++ b/packages/@liexp/io/package.json @@ -21,6 +21,7 @@ "clean": "rm -rf lib", "lint": "eslint --cache src", "test": "vitest", + "test:coverage": "vitest --run --coverage", "watch": "tsc -b -w" }, "lint-staged": { diff --git a/packages/@liexp/io/vitest.config.js b/packages/@liexp/io/vitest.config.js index 7856276cfc..394ab3dcb3 100644 --- a/packages/@liexp/io/vitest.config.js +++ b/packages/@liexp/io/vitest.config.js @@ -12,7 +12,9 @@ export default defineConfig({ coverage: { thresholds: { statements: 80, + branches: 80, functions: 80, + lines: 80, }, }, alias: { diff --git a/packages/@liexp/shared/package.json b/packages/@liexp/shared/package.json index e5814377d0..5403708dfa 100644 --- a/packages/@liexp/shared/package.json +++ b/packages/@liexp/shared/package.json @@ -21,6 +21,7 @@ "clean": "rm -rf lib", "lint": "eslint --cache src", "test": "vitest", + "test:coverage": "vitest --run --coverage", "watch": "tsc -b -w" }, "lint-staged": { diff --git a/packages/@liexp/shared/vitest.config.js b/packages/@liexp/shared/vitest.config.js index 82b8fb26aa..ef4dde0dd6 100644 --- a/packages/@liexp/shared/vitest.config.js +++ b/packages/@liexp/shared/vitest.config.js @@ -11,8 +11,10 @@ export default defineConfig({ watch: false, coverage: { thresholds: { - statements: 80, - functions: 80, + statements: 60, + branches: 55, + functions: 55, + lines: 60, }, }, alias: { diff --git a/packages/@liexp/test/package.json b/packages/@liexp/test/package.json index 30efecca4a..095b021d10 100644 --- a/packages/@liexp/test/package.json +++ b/packages/@liexp/test/package.json @@ -15,6 +15,7 @@ "clean": "rm -rf lib", "lint": "eslint --cache src", "test": "echo 'No tests configured for @liexp/test'", + "test:coverage": "vitest --run --coverage", "watch": "tsc -b -w" }, "lint-staged": { @@ -30,7 +31,8 @@ "effect": "^3.20.0", "eslint": "^10.1.0", "fp-ts": "^2.16.10", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.2" }, "peerDependencies": { "effect": "^3", diff --git a/packages/@liexp/test/vitest.config.ts b/packages/@liexp/test/vitest.config.ts new file mode 100644 index 0000000000..120444114c --- /dev/null +++ b/packages/@liexp/test/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + name: "@liexp/test", + globals: true, + include: ["src/**/*.spec.ts"], + watch: false, + coverage: { + include: ["src/**/*.ts"], + thresholds: { + lines: 80, + statements: 80, + branches: 80, + functions: 80, + }, + }, + }, +}); diff --git a/packages/@liexp/ui/package.json b/packages/@liexp/ui/package.json index 9c143a34d9..0bb01d38d9 100644 --- a/packages/@liexp/ui/package.json +++ b/packages/@liexp/ui/package.json @@ -22,6 +22,7 @@ "clean": "rm -rf lib", "lint": "eslint --cache src", "test": "vitest", + "test:coverage": "vitest --run --coverage", "watch": "tsc -b -w" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac8854e432..659b92d57c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -325,6 +325,9 @@ importers: vite-plugin-svgr: specifier: ^4.3.0 version: 4.5.0(rollup@4.60.0)(typescript@5.9.3)(vite@8.0.3(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.82.0)(terser@5.37.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: + specifier: ^4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jsdom@27.4.0(canvas@3.0.1))(msw@2.12.10(@types/node@25.5.0)(typescript@5.9.3))(vite@8.0.3(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.82.0)(terser@5.37.0)(tsx@4.21.0)(yaml@2.8.3)) packages/@liexp/eslint-config: dependencies: @@ -536,6 +539,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + vitest: + specifier: ^4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(jsdom@27.4.0(canvas@3.0.1))(msw@2.12.10(@types/node@25.5.0)(typescript@5.9.3))(vite@8.0.3(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.82.0)(terser@5.37.0)(tsx@4.21.0)(yaml@2.8.3)) packages/@liexp/ui: dependencies: diff --git a/services/admin/vitest.config.ts b/services/admin/vitest.config.ts index d193d6eb19..cf5309aa6a 100644 --- a/services/admin/vitest.config.ts +++ b/services/admin/vitest.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - projects: ["./vitest.config.*.ts"], + projects: ["./vitest.config.e2e.ts", "./vitest.config.spec.ts"], reporters: ["verbose"], watch: false, coverage: { diff --git a/services/agent/vitest.config.spec.ts b/services/agent/vitest.config.spec.ts index 354a46f033..2bccbb070b 100644 --- a/services/agent/vitest.config.spec.ts +++ b/services/agent/vitest.config.spec.ts @@ -23,6 +23,12 @@ export default extendBaseConfig(import.meta.url, (toAlias) => ({ coverage: { include: ["src/**/*.ts"], exclude: ["src/**/*.e2e.ts", "test"], + thresholds: { + statements: 80, + branches: 80, + functions: 80, + lines: 80, + }, }, }, })); diff --git a/services/api/package.json b/services/api/package.json index b4906beb42..618014a1aa 100644 --- a/services/api/package.json +++ b/services/api/package.json @@ -46,6 +46,7 @@ "start:worker": "node build/worker/index.js", "test": "pnpm vitest", "test:all": "pnpm test:spec --run && pnpm test:e2e --run", + "test:coverage": "vitest --run --coverage --project api-spec", "test:e2e": "vitest --project api-e2e", "test:spec": "vitest --project api-spec", "tsx:build": "tsx --tsconfig tsconfig.build.json", diff --git a/services/api/vitest.config.spec.ts b/services/api/vitest.config.spec.ts index f1870bc9ab..4e50e5f83c 100644 --- a/services/api/vitest.config.spec.ts +++ b/services/api/vitest.config.spec.ts @@ -12,6 +12,9 @@ export default extendBaseConfig(import.meta.url, (toAlias) => ({ coverage: { include: ["src/**/*.ts"], exclude: ["src/**/*.e2e.ts", "test"], + // TODO: add coverage thresholds once the service can run tests without a + // live database. Currently most tests require a DB connection and are + // exercised via e2e rather than unit tests. } }, })); diff --git a/services/web/package.json b/services/web/package.json index 3fce426388..a8bbc5f2ed 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -25,6 +25,7 @@ "scripts:make-avatars-mock": "tsx ./scripts/make-avatars-mock.ts", "serve": "node ./build/server/server.js", "test": "vitest", + "test:coverage": "vitest --run --coverage --project=web-spec", "test:e2e": "vitest --project=web-e2e", "test:spec": "vitest --project=web-spec", "watch": "tsc -b --watch", diff --git a/services/web/vitest.config.spec.ts b/services/web/vitest.config.spec.ts index 16af2f7b02..c7fb9716aa 100644 --- a/services/web/vitest.config.spec.ts +++ b/services/web/vitest.config.spec.ts @@ -15,5 +15,18 @@ export default defineProject({ include: ["src/**/*.spec.ts", "src/**/*.test.ts"], exclude: ["node_modules/**", "build/**", "**/*.e2e.ts"], root: __dirname, + coverage: { + include: ["src/**/*.ts", "src/**/*.tsx"], + exclude: ["src/**/*.e2e.ts", "node_modules/**", "build/**"], + // The only existing spec is a static code-inspection test (fs.readFileSync). + // No source paths are instrumented, so coverage stays at 0%. + // TODO: add real unit tests and raise thresholds to 80%. + thresholds: { + statements: 0, + branches: 0, + functions: 0, + lines: 0, + }, + }, }, }); diff --git a/services/worker/package.json b/services/worker/package.json index 1bf2abc233..19d6636fe6 100644 --- a/services/worker/package.json +++ b/services/worker/package.json @@ -32,6 +32,7 @@ "bin:upsert-nlp-entities": "node ./build/bin/cli.js upsert-nlp-entities", "start": "node build/run.js", "test": "vitest", + "test:coverage": "vitest --run --coverage", "test:e2e": "vitest --project worker-e2e", "tsx:build": "tsx --tsconfig tsconfig.build.json", "watch": "tsc -b ./tsconfig.build.json --watch", diff --git a/services/worker/vitest.config.e2e.ts b/services/worker/vitest.config.e2e.ts index a51d190534..7dc6c0c6e9 100644 --- a/services/worker/vitest.config.e2e.ts +++ b/services/worker/vitest.config.e2e.ts @@ -17,6 +17,8 @@ export default extendBaseConfig(import.meta.url, (toAlias) => ({ coverage: { include: ["src/**/*.ts"], exclude: ["test/**/*.e2e.ts", "test"], + // TODO: add coverage thresholds once the service can run tests without a + // live database. Worker tests are all e2e and require a running DB. }, }, poolOptions: {