From e43d8e1e0e4ae6f6e0ddc6ac8c14d48a662c0b05 Mon Sep 17 00:00:00 2001 From: OmniZlatoon Date: Mon, 24 Aug 2026 01:12:42 +0100 Subject: [PATCH] chore: harden CI quality gates - remove --passWithNoTests, add typecheck and test:coverage scripts, enforce 75% coverage threshold, update README --- .gitignore | 1 + README.md | 32 +++++++++++++++++++++++++++++--- package-lock.json | 18 ------------------ package.json | 14 ++++++++++++-- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index a38b2af..75c3f0a 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ npm-debug.log* # Metro metro-cache/ +coverage/ diff --git a/README.md b/README.md index b4a7ef9..8901158 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,25 @@ npm run android # Android npm run ios # iOS (Mac only) ``` +### CI Quality Gates + +Before opening a PR, run the full local quality gate to ensure CI will pass: + +```bash +# 1. Type-check the entire codebase (must produce zero errors) +npm run typecheck + +# 2. Run all unit tests with coverage enforcement +# Overall line/function/branch/statement coverage must stay >= 75% +npm run test:coverage + +# 3. Lint +npm run lint +``` + +> **Note:** `npm test` (used in CI) runs without the `--coverage` flag for speed. +> Use `npm run test:coverage` locally to verify you have not dropped coverage below the 75% threshold. + ### Environment Variables ```env @@ -290,11 +309,14 @@ The `ecotask://lobstr/callback` path is handled automatically by ## 🧪 Testing ```bash -# Run unit tests (135 tests) +# Run unit tests (no --passWithNoTests — at least one test is required) npm test -# Run with coverage -npm test -- --coverage +# Run tests with coverage enforcement (>= 75% lines/functions/branches/statements) +npm run test:coverage + +# Type-check the entire codebase (zero TypeScript errors required) +npm run typecheck # Run integration tests (requires running backend) npm run test:integration @@ -302,6 +324,10 @@ npm run test:integration ### Test Coverage +The project enforces a **75% minimum coverage threshold** across lines, functions, +branches, and statements. Run `npm run test:coverage` before opening a PR to +verify you have not regressed below the threshold. + | Category | Tests | Files | | ------------------- | ----- | ----------------------------------------------------------------------------------------------------------- | | Store logic | 19 | walletStore, taskStore, userStore, activityStore | diff --git a/package-lock.json b/package-lock.json index 64b7270..21c3b89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,7 +84,6 @@ "node_modules/@babel/core": { "version": "7.29.7", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -114,7 +113,6 @@ "version": "7.29.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", @@ -1674,7 +1672,6 @@ "node_modules/@babel/preset-env": { "version": "7.29.7", "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", @@ -3299,7 +3296,6 @@ "node_modules/@react-navigation/native": { "version": "6.1.18", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/core": "^6.4.17", "escape-string-regexp": "^4.0.0", @@ -3783,7 +3779,6 @@ "version": "18.3.30", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -3876,7 +3871,6 @@ "version": "5.62.0", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -4082,7 +4076,6 @@ "node_modules/acorn": { "version": "8.16.0", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6304,7 +6297,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8705,7 +8697,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -10568,7 +10559,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -10938,7 +10928,6 @@ "node_modules/react": { "version": "18.2.0", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10989,7 +10978,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.6.tgz", "integrity": "sha512-oqmZe8D2/VolIzSPZw+oUd6j/bEmeRHwsLn1xLA5wllEYsZ5zNuMsDus235ONOnCRwexqof/J3aztyQswSmiaA==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-community/cli": "12.3.6", @@ -11089,7 +11077,6 @@ "node_modules/react-native-safe-area-context": { "version": "4.14.1", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -11100,7 +11087,6 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.37.0.tgz", "integrity": "sha512-vEi4qZqWYoGuVGuHTv1K2XA90rgSydksmR5+tb5uhL93whl6Bch6EEXzC+8eEfj4SimiCgXBPY7r/xTXJxvnUg==", "license": "MIT", - "peer": true, "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" @@ -12526,7 +12512,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -12756,7 +12741,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12951,7 +12935,6 @@ "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13328,7 +13311,6 @@ "node_modules/yaml": { "version": "2.9.0", "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index 9c01af4..93c2763 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", - "test": "jest --passWithNoTests --testPathIgnorePatterns='integration' --testPathIgnorePatterns='__mocks__'", + "test": "jest --testPathIgnorePatterns='integration' --testPathIgnorePatterns='__mocks__'", + "test:coverage": "jest --coverage --testPathIgnorePatterns='integration' --testPathIgnorePatterns='__mocks__'", + "typecheck": "tsc --noEmit", "test:integration": "jest --passWithNoTests --testMatch='**/__tests__/**/*.integration.*'", "lint": "eslint ." }, @@ -60,6 +62,14 @@ ], "transformIgnorePatterns": [ "node_modules/(?!((jest-)?react-native|@react-native(-community)?|@react-navigation|react-native-maps)/)" - ] + ], + "coverageThreshold": { + "global": { + "lines": 75, + "functions": 75, + "branches": 75, + "statements": 75 + } + } } }