diff --git a/.gitea/workflows/project-ci.yml b/.gitea/workflows/project-ci.yml index 52394b9a1..95ca7310e 100644 --- a/.gitea/workflows/project-ci.yml +++ b/.gitea/workflows/project-ci.yml @@ -410,7 +410,7 @@ jobs: run: bash scripts/ci-npm-ci-with-retry.sh - name: Run frontend and script tests - run: npm run test + run: npm run test:ci:frontend - name: Run BgFilter worker smoke harness tests run: npm run bgfilter-worker:smoke-test diff --git a/docs/project-memory/shared-memory/development-workflow.md b/docs/project-memory/shared-memory/development-workflow.md index 7a3006746..fcd7615c4 100644 --- a/docs/project-memory/shared-memory/development-workflow.md +++ b/docs/project-memory/shared-memory/development-workflow.md @@ -4,7 +4,7 @@ ## 标准流程 -前端测试稳定性验证使用根目录 `npm test`(与 Frontend tests job 相同),保留 Vitest 的 8 worker 上限。涉及异步资源展示时,组件测试必须 mock 所有会触发的网络请求,每次调用创建独立 `Response`,并等待最终 DOM 状态而非仅等待 fetch 被调用。换签 Hook 的测试通过 `vitest.config.ts` 的 include 纳入全量运行;新增测试文件后需确认实际执行名单,命令参数指定文件不会绕过 include 白名单。排查顺序依赖可使用 `npm test -- --sequence.shuffle --sequence.seed=9467`,但不能以重试成功替代失败原因分析。 +前端测试稳定性验证使用根目录 `npm test` 执行全量集合,保留 Vitest 的 8 worker 上限。CI 的 `Frontend tests` 使用 `npm run test:ci:frontend`,继承根配置并排除 `apps/ai-game-creator-shell/tests/**`;该目录由 `AI game creator shell web tests` 执行,两个 job 的 Vitest 文件集合互斥且并集等于本地全量。原生壳定向检查与 Repository checks 的 AppSurface 检查仍保留。涉及异步资源展示时,组件测试必须 mock 所有会触发的网络请求,每次调用创建独立 `Response`,并等待最终 DOM 状态而非仅等待 fetch 被调用。换签 Hook 的测试通过 `vitest.config.ts` 的 include 纳入全量运行;新增测试文件后需确认实际执行名单,命令参数指定文件不会绕过 include 白名单。排查顺序依赖可使用 `npm test -- --sequence.shuffle --sequence.seed=9467`,但不能以重试成功替代失败原因分析。 用例隔离必须包括浏览器状态与 mock 实现:修改 `window.history` 后恢复基线路由;`spyOn(window, 'getSelection')` 等 spy 在用例结束后 restore;`clearAllMocks` 仅清调用记录,不能恢复被上一个用例替换的返回值。顺序打乱暴露的失败应修复泄漏来源,保留原有业务断言。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index 23d4a3cd5..65a42c942 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -293,7 +293,7 @@ npm run check 所有 CI job 和 Jenkins Web Build 在根 workspace 安装前都必须确认 `npm --version` 为 `10.9.7`。Gitea job 使用预构建镜像内的固定版本;Jenkins Web Build 在每个独立 `bash -lc` 中 source `scripts/jenkins-prepare-npm-env.sh`,首次为 Jenkins 运行用户的版本隔离目录引导同版 npm,后续复用并把该 `bin` 放到 `PATH` 首位。旧固定镜像缺少版本元数据时只能报告 `npm_version=partial` 并由当前 job 的根 `npm ci` 继续校验 lock,不能把过渡状态当作工具链已闭合。 - `Repository checks`:调用唯一入口 `npm run check:repository-ci`,执行 `npm run lint`、AI 游戏创作壳 AppSurface 定向测试、主站与后台生产构建和提交差异空白检查。本地 master `pre-push` 复用同一入口,禁止在 workflow 与 hook 中维护两份近似命令。 -- `Frontend tests`:按唯一根 workspace lockfile 执行一次干净的 `npm ci`,再独立执行根 `npm run test`、`npm run bgfilter-worker:smoke-test`、`npm run check:production-health-patrol`、`npm run check:production-api-release` 和 `npm run check:production-api-deploy`,让 Vitest、Node test smoke harness 及不依赖真实服务的生产巡检 / 发布 / 部署行为 fixture 在 Gitea job 中持续执行;其中 `.test.mjs` 使用 Node test runner,不依赖 Vitest 的 `scripts/**/*.test.ts` 收集规则。 +- `Frontend tests`:按唯一根 workspace lockfile 执行一次干净的 `npm ci`,再独立执行根 `npm run test:ci:frontend`、`npm run bgfilter-worker:smoke-test`、`npm run check:production-health-patrol`、`npm run check:production-api-release` 和 `npm run check:production-api-deploy`,让 Vitest、Node test smoke harness 及不依赖真实服务的生产巡检 / 发布 / 部署行为 fixture 在 Gitea job 中持续执行。`test:ci:frontend` 使用 `vitest.frontend-ci.config.ts` 继承根配置,仅排除 `apps/ai-game-creator-shell/tests/**`,由 `AI game creator shell web tests` 执行该目录,避免两个 job 重复运行 AGC 前端测试;本地 `npm test` 仍执行原有全量集合。CI 契约测试使用 Vitest 实际文件发现验证两组互斥、并集等于全量;原生壳定向检查与 Repository checks 的 AppSurface 检查仍保留。其中 `.test.mjs` 使用 Node test runner,不依赖 Vitest 的 `scripts/**/*.test.ts` 收集规则。 - `Backend tests`:先对 `server-rs/Cargo.lock` 执行带 5 次整命令级有界重试的 `cargo fetch --locked`,再执行 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`、`cargo test --locked -p spacetime-module --no-fail-fast`、`api-server --all-targets` 编译和 `cargo check --locked -p spacetime-module`;普通 workspace host 测试排除 `spacetime-module` 以避免其 `spacetime-types` feature 统一污染领域 crate,模块自身的纯单元测试通过独立 package test 纳入门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,不能把 host 链接支持当作运行时替身。依赖准备必须位于会触发 Cargo build 的 DDD / 产物边界门禁之前,避免锁新增依赖未命中镜像缓存时绕过既有下载重试。runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。依赖真实服务或密钥的测试必须显式 `ignored`,不能让普通 PR job访问现场环境。 - `Native shell tests`:按唯一根 workspace lockfile 安装全部 App 依赖后,用 `npm run check:native-shells:contract`、`npm run check:native-shells:shells` 和 `npm run check:native-shells:release` 分别执行静态契约、H5 / 微信 / Expo / Tauri 桌面壳运行时门禁,以及依赖发布产物的构建 smoke,最后确认桌面壳与 AI 游戏创作壳的 `Cargo.lock` 都没有被构建过程改写。 - `AI game creator shell web tests`:执行 `npm run check:native-shells:agc-web`(即 `npm run ai-game-creator-shell:check:web`:AGC 壳 typecheck 与壳内测试)。该分组不触碰 Cargo,因此不预热 Rust 依赖。 diff --git a/package.json b/package.json index 57405379c..929a974d3 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "format:staged": "lint-staged", "check:pre-push-master": "bash scripts/pre-push-master.sh", "test": "vitest run", + "test:ci:frontend": "vitest run --config vitest.frontend-ci.config.ts", "test:watch": "vitest", "container:init": "node scripts/container-compose.mjs init", "container:build": "node scripts/container-compose.mjs build", diff --git a/scripts/project-ci-workflow.test.ts b/scripts/project-ci-workflow.test.ts index f55375b98..4e3c9b356 100644 --- a/scripts/project-ci-workflow.test.ts +++ b/scripts/project-ci-workflow.test.ts @@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; +import { createVitest } from 'vitest/node'; const workflow = readFileSync( resolve(process.cwd(), '.gitea/workflows/project-ci.yml'), @@ -397,7 +398,10 @@ describe('project CI workflow', () => { it('keeps frontend, operations fixture, and native shell gates in dedicated jobs', () => { const frontendJob = jobSection('frontend-tests'); - expect(frontendJob).toContain('run: npm run test'); + expect(frontendJob).toMatch(/^ {8}run: npm run test:ci:frontend$/mu); + expect(rootPackageJson.scripts?.['test:ci:frontend']).toBe( + 'vitest run --config vitest.frontend-ci.config.ts', + ); expect(frontendJob).toContain('run: npm run bgfilter-worker:smoke-test'); expect(frontendJob).toContain( 'run: npm run check:production-health-patrol', @@ -419,6 +423,39 @@ describe('project CI workflow', () => { expect(nativeJob).toContain('cargo fetch --locked'); }); + it('partitions frontend and AGC test files without gaps or duplicates', async () => { + expect(rootPackageJson.scripts?.test).toBe('vitest run'); + const full = await createVitest('test', { + config: resolve('vitest.config.ts'), + watch: false, + }); + try { + const allFiles = (await full.globTestFiles()).map(([, file]) => file); + const agcFiles = ( + await full.globTestFiles(['apps/ai-game-creator-shell/tests']) + ).map(([, file]) => file); + const frontend = await createVitest('test', { + config: resolve('vitest.frontend-ci.config.ts'), + watch: false, + }); + try { + const frontendFiles = (await frontend.globTestFiles()).map( + ([, file]) => file, + ); + expect(agcFiles.length).toBeGreaterThan(0); + expect(frontendFiles.length).toBeGreaterThan(0); + expect(frontendFiles.filter((file) => agcFiles.includes(file))).toEqual( + [], + ); + expect([...frontendFiles, ...agcFiles].sort()).toEqual(allFiles.sort()); + } finally { + await frontend.close(); + } + } finally { + await full.close(); + } + }, 30_000); + it('runs every native shell gate group exactly once across the split jobs', () => { for (const [group, script] of Object.entries(nativeShellGateGroupScripts)) { expect(rootPackageJson.scripts?.[`check:native-shells:${group}`]).toBe( diff --git a/vitest.frontend-ci.config.ts b/vitest.frontend-ci.config.ts new file mode 100644 index 000000000..207404bf6 --- /dev/null +++ b/vitest.frontend-ci.config.ts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vitest/config'; + +import config from './vitest.config'; + +// AGC 前端测试由专属 CI job 执行;本地全量入口继续使用根配置。 +export default mergeConfig(config, { + test: { + exclude: ['apps/ai-game-creator-shell/tests/**'], + }, +});