完善 CI 质量门禁并启用覆盖率阈值

This commit is contained in:
2026-03-03 13:02:31 +08:00
parent 4cd593bc30
commit 41ac21ea12
8 changed files with 213 additions and 19 deletions
Vendored
+14
View File
@@ -18,6 +18,20 @@ pipeline {
}
}
stage('Install Dependencies') {
steps {
sh 'npm ci'
}
}
stage('Quality Gate') {
steps {
sh 'npm run lint'
sh 'npx tsc --noEmit'
sh 'npm run test:coverage'
}
}
stage('Build Docker Image') {
steps {
sh "docker build --build-arg NEXT_PUBLIC_AMAP_API_KEY=${AMAP_KEY} -t ${APP_NAME}:${BUILD_NUMBER} -t ${APP_NAME}:latest ."
+9 -4
View File
@@ -182,11 +182,16 @@
- 为上述 hook 增加 `useEffect` cleanup 清理 `timersRef`
- confetti 动画增加销毁标志与取消逻辑。
### P2-6 构建/测试门禁链路不完整
### P2-6 构建/测试门禁链路不完整【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
- `Jenkinsfile` 新增 `Install Dependencies``Quality Gate` 阶段,执行 `npm ci``npm run lint``npx tsc --noEmit``npm run test:coverage`
- 补齐 `@vitest/coverage-v8` 依赖,恢复 `npm run test:coverage` 可执行;
-`vitest.config.ts` 增加覆盖率阈值(`statements:57 / branches:50 / functions:47 / lines:60`);
- 调整 `blindbox/page``profile/page` 测试异步断言,减少 `act(...)` 噪声。
- 证据:
- `Jenkinsfile` 当前仅构建和部署,无 `lint/test/tsc` 阶段。
- `npm run test:coverage` 缺少 `@vitest/coverage-v8`
- `npm test` 虽通过,但出现多处 `act(...)` 警告。
- `npm run test:coverage` 通过(`54 files / 337 tests`,覆盖率总览满足阈值);
- `npm run lint` 当前 `0 errors``npx tsc --noEmit` 通过
- 影响:
- 回归问题可能绕过 CI 直接进入部署。
- 建议:
+1
View File
@@ -11,6 +11,7 @@ const eslintConfig = defineConfig([
".next/**",
"out/**",
"build/**",
"coverage/**",
"next-env.d.ts",
]),
]);
+155 -6
View File
@@ -25,7 +25,8 @@
"qrcode.react": "^4.2.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"swr": "^2.4.0"
"swr": "^2.4.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -38,6 +39,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"jsdom": "^28.1.0",
@@ -280,7 +282,7 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -290,7 +292,7 @@
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -324,7 +326,7 @@
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
@@ -416,7 +418,7 @@
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -426,6 +428,16 @@
"node": ">=6.9.0"
}
},
"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/@bramus/specificity": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
@@ -3753,6 +3765,49 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/@vitest/coverage-v8": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz",
"integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.0.18",
"ast-v8-to-istanbul": "^0.3.10",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.2.0",
"magicast": "^0.5.1",
"obug": "^2.1.1",
"std-env": "^3.10.0",
"tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "4.0.18",
"vitest": "4.0.18"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
}
},
"node_modules/@vitest/coverage-v8/node_modules/magicast": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
"integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vitest/expect": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
@@ -4135,6 +4190,25 @@
"dev": true,
"license": "MIT"
},
"node_modules/ast-v8-to-istanbul": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
"integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
"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-function": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
@@ -6192,6 +6266,13 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/html-to-image": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
@@ -6741,6 +6822,45 @@
"dev": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -7282,6 +7402,35 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -9639,6 +9788,7 @@
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
@@ -10026,7 +10176,6 @@
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"devOptional": true,
"license": "MIT",
"peer": true,
"funding": {
+1
View File
@@ -43,6 +43,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"jsdom": "^28.1.0",
+10 -6
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import React from "react";
import { ToastContext, type ToastContextValue } from "@/hooks/useToast";
@@ -40,19 +40,23 @@ beforeEach(() => {
});
describe("BlindboxPage", () => {
it("renders page heading", () => {
it("renders page heading", async () => {
renderPage();
expect(screen.getByText("周末契约")).toBeInTheDocument();
expect(await screen.findByText("周末契约")).toBeInTheDocument();
await waitFor(() => {
expect(mockFetch).toHaveBeenCalled();
});
});
it("renders back button", () => {
it("renders back button", async () => {
renderPage();
await screen.findByText("周末契约");
const backBtns = screen.getAllByRole("button");
expect(backBtns.length).toBeGreaterThan(0);
});
it("renders subtitle text", () => {
it("renders subtitle text", async () => {
renderPage();
expect(screen.getByText("ADVENTURE ROULETTE")).toBeInTheDocument();
expect(await screen.findByText("ADVENTURE ROULETTE")).toBeInTheDocument();
});
});
+13 -3
View File
@@ -70,9 +70,14 @@ beforeEach(() => {
});
describe("ProfilePage", () => {
it("renders profile heading", () => {
it("renders profile heading", async () => {
renderPage();
expect(screen.getByText("个人中心")).toBeInTheDocument();
expect(await screen.findByText("个人中心")).toBeInTheDocument();
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledWith(
"/api/user?id=user-1",
);
});
});
it("fetches user profile data with correct URL", async () => {
@@ -91,8 +96,13 @@ describe("ProfilePage", () => {
});
});
it("renders navigation element", () => {
it("renders navigation element", async () => {
renderPage();
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledWith(
"/api/user?id=user-1",
);
});
expect(screen.getByRole("navigation")).toBeInTheDocument();
});
});
+10
View File
@@ -19,6 +19,16 @@ export default defineConfig({
},
},
test: {
coverage: {
provider: "v8",
reporter: ["text", "html"],
thresholds: {
statements: 57,
branches: 50,
functions: 47,
lines: 60,
},
},
projects: [
{
plugins: [react()],