name: web Tests # Runs `npm test` (Vitest) + format check for the web React/TypeScript # frontend on every non-draft PR that touches web/** and on push to main. # Draft PRs are skipped; `ready_for_review` refires when the draft is converted. on: pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - "web/**" push: branches: - main paths: - "web/**" permissions: contents: read concurrency: # PRs key by number (old runs cancel); push keys by SHA (each merge runs). group: web-tests-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: # Security precondition gate: npm ci/test runs the PR's own install hooks and # test code, so untrusted PRs are held until the scan passes (security-gate.yml). # Trusted authors and non-PR events pass through. gate: uses: ./.github/workflows/security-gate.yml npm-test: name: npm test needs: gate if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Check out repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Node.js uses: ./.github/actions/setup-node - name: Install dependencies working-directory: web # Pin the npm registry to the npmjs default. env: NPM_CONFIG_REGISTRY: https://registry.npmjs.org/ run: npm ci --legacy-peer-deps - name: Check formatting working-directory: web run: npm run format:check - name: Run tests with coverage working-directory: web run: npm run test:coverage # Distill the v8 json-summary into a single total.txt, mirroring the # backend's coverage-report job. ui-code-coverage.yml (privileged # workflow_run) consumes this artifact and posts the report-only status. - name: Summarize coverage if: always() working-directory: web run: | mkdir -p ui-coverage-summary if [[ ! -f coverage/coverage-summary.json ]]; then echo "::warning::No coverage-summary.json; skipping UI coverage report." exit 0 fi node -e "process.stdout.write(String(require('./coverage/coverage-summary.json').total.lines.pct))" \ > ui-coverage-summary/total.txt echo "Total UI coverage: $(cat ui-coverage-summary/total.txt)%" # Render a markdown table; tee it to both the job log (visible inline) # and the run's Summary tab (parity with the backend coverage-report # job's GITHUB_STEP_SUMMARY table). node -e ' const t = require("./coverage/coverage-summary.json").total; const row = (k) => `| ${k[0].toUpperCase()}${k.slice(1)} | ${t[k].pct}% | ${t[k].covered}/${t[k].total} |`; process.stdout.write( "## UI Coverage\n\n" + "| Metric | % | Covered/Total |\n|---|---|---|\n" + ["lines","statements","functions","branches"].map(row).join("\n") + "\n"); ' | tee -a "$GITHUB_STEP_SUMMARY" - name: Upload UI coverage summary if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ui-coverage-summary-${{ github.run_id }} path: web/ui-coverage-summary/ retention-days: 14