test(qa): e2e 装配修正 + 集成/门禁测试与工具链重构

- e2e:修死选择器与蜜罐误命中、补断言、GA4/security-headers 用例诚实标注
  skip 边界;新增 assert-helpers/fixtures/hydrated/primary-nav/touch-targets
  等复用桩。
- 集成层:config/test/jest.integration.config.js + tests-integration/ 真库
  一次性 SQLite 用例,teardown 守卫开发库指纹。
- 门禁工具链:jest.setup 归位到根、scripts/accessibility 全站 axe 节点计数、
  check-brand-text-token/check-motion-constraints 机械守卫、stryker/lighthouse
  配置收敛。
This commit is contained in:
2026-09-28 10:48:08 +08:00
parent a366bd1400
commit 6bb7c557ee
142 changed files with 6597 additions and 2653 deletions
+58
View File
@@ -0,0 +1,58 @@
// @ts-nocheck
// 集成测试 Jest project —— 唯一跑真实数据层的测试配置。
//
// 与 config/test/jest.config.js(单元测试)的关系:完全独立,不复用 jest.setup.js,
// 因为 jest.setup.js:12-50 全局 mock 了 `@/generated/prisma/client` 与
// `@/lib/cms/data-server`(验收报告 ACCEPTANCE_REVIEW_2026-09-21.md A-8)。
// 本配置不加载任何全局 mock,因此 src/lib/db.ts 导出的是真实的 PrismaClient。
//
// 文件发现:testMatch 只匹配 tests-integration 下的 .itest.ts。
// 单测配置的 roots 是 <rootDir>/src,testMatch 是 *.test.ts(x);
// `.itest.ts` 既不以 `.test.ts` 结尾、也不在 src/ 下,故 `npm run test:unit`、
// `test:coverage`、stryker(同样读 config/test/jest.config.js)都不会发现这些用例 —— 行为零变化。
//
// 运行:npx jest --config config/test/jest.integration.config.js
const path = require('path');
module.exports = {
rootDir: path.resolve(__dirname, '../..'),
roots: ['<rootDir>/tests-integration'],
testMatch: ['<rootDir>/tests-integration/**/*.itest.ts'],
// testEnvironment 与单测配置(config/test/jest.config.js:6)的 jsdom **有意不同**,原因是硬约束:
// jest-environment-jsdom 30 的沙箱里没有 Request/Response/Headers(Node 主 realm 才有),
// 于是 `import { NextRequest } from 'next/server'` 直接 `ReferenceError: Request is not defined`
// (node_modules/next/src/server/web/spec-extension/request.ts:14)。
// 这正是既有「route.test.ts」宁愿手写 global.Request 假对象(jest.setup.js:185-232)并整体 mock
// 掉 next/server(jest.setup.js:90-109)的原因 —— 而那类测试测不到真实的 HTTP 契约。
// 被测对象是跑在 Node runtime 的 route handler + 真实 SQLite,所以集成层用 node 环境,
// 从而拿到真实的 NextRequest/NextResponse(含 cookies / headers / clone 语义)。
testEnvironment: 'node',
globalSetup: '<rootDir>/config/test/itest/global-setup.js',
globalTeardown: '<rootDir>/config/test/itest/global-teardown.js',
// 每个 worker 在 import 被测模块之前把 DATABASE_URL 钉到 /tmp 并做守卫断言
setupFiles: ['<rootDir>/config/test/itest/setup-files.js'],
setupFilesAfterEnv: [],
collectCoverage: false,
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
// 与 Next 运行时对齐的真实 React:src/lib/cms/data-server.ts:6 用 `import { cache } from 'react'`,
// 而仓库依赖的 node_modules/react@18.3.1 **不导出 cache**(实测 typeof require('react').cache
// === 'undefined';其 react-server 入口 react.shared-subset.js 直接抛
// "This entry point is not yet supported outside of experimental channels")。
// 应用能跑是因为 Next 把 react 解析到自己内置的 19.3.0-canary。这里做同样的解析,
// 集成层才能 import 真实 data-server —— 而不是像 jest.setup.js:29-50 那样把它整体 mock 掉。
'^react$': require.resolve('next/dist/compiled/react'),
},
transform: {
// Prisma 生成产物是 ESM 且使用 import.meta.url(src/generated/prisma/client.ts:16),
// 在 CJS 的 Jest 运行时里必须由专用 transformer 处理;该目录是机器生成的代码,
// 只做转译不做类型检查(文件自身带 @ts-nocheck)。
'src/generated/prisma/.+\\.ts$': '<rootDir>/config/test/itest/prisma-generated-transformer.js',
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: { jsx: 'react-jsx' } }],
},
// SQLite 单写者 + 固定文件名:串行执行,避免多个 worker 同时写同一个临时库
maxWorkers: 1,
testTimeout: 30000,
verbose: true,
};