mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 22:28:46 +00:00
196 lines
7.2 KiB
YAML
196 lines
7.2 KiB
YAML
name: Web Tests
|
|
|
|
on:
|
|
workflow_call:
|
|
|
|
concurrency:
|
|
group: web-tests-${{ github.head_ref || github.run_id }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
test:
|
|
name: Web Tests
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
working-directory: ./web
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@v4
|
|
with:
|
|
package_json_file: web/package.json
|
|
run_install: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
cache-dependency-path: ./web/pnpm-lock.yaml
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Check i18n types synchronization
|
|
run: pnpm run check:i18n-types
|
|
|
|
- name: Run tests
|
|
run: |
|
|
pnpm exec jest \
|
|
--ci \
|
|
--runInBand \
|
|
--coverage \
|
|
--passWithNoTests
|
|
|
|
- name: Coverage Summary
|
|
if: always()
|
|
id: coverage-summary
|
|
run: |
|
|
set -eo pipefail
|
|
|
|
COVERAGE_FILE="coverage/coverage-final.json"
|
|
COVERAGE_SUMMARY_FILE="coverage/coverage-summary.json"
|
|
|
|
if [ ! -f "$COVERAGE_FILE" ] && [ ! -f "$COVERAGE_SUMMARY_FILE" ]; then
|
|
echo "has_coverage=false" >> "$GITHUB_OUTPUT"
|
|
echo "### 🚨 Test Coverage Report :test_tube:" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "Coverage data not found. Ensure Jest runs with coverage enabled." >> "$GITHUB_STEP_SUMMARY"
|
|
exit 0
|
|
fi
|
|
|
|
echo "has_coverage=true" >> "$GITHUB_OUTPUT"
|
|
|
|
node <<'NODE' >> "$GITHUB_STEP_SUMMARY"
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const summaryPath = path.join('coverage', 'coverage-summary.json');
|
|
const finalPath = path.join('coverage', 'coverage-final.json');
|
|
|
|
const hasSummary = fs.existsSync(summaryPath);
|
|
const hasFinal = fs.existsSync(finalPath);
|
|
|
|
if (!hasSummary && !hasFinal) {
|
|
console.log('### Test Coverage Summary :test_tube:');
|
|
console.log('');
|
|
console.log('No coverage data found.');
|
|
process.exit(0);
|
|
}
|
|
|
|
const totals = {
|
|
lines: { covered: 0, total: 0 },
|
|
statements: { covered: 0, total: 0 },
|
|
branches: { covered: 0, total: 0 },
|
|
functions: { covered: 0, total: 0 },
|
|
};
|
|
const fileSummaries = [];
|
|
|
|
if (hasSummary) {
|
|
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
|
|
const totalEntry = summary.total ?? {};
|
|
['lines', 'statements', 'branches', 'functions'].forEach((key) => {
|
|
if (totalEntry[key]) {
|
|
totals[key].covered = totalEntry[key].covered ?? 0;
|
|
totals[key].total = totalEntry[key].total ?? 0;
|
|
}
|
|
});
|
|
|
|
Object.entries(summary)
|
|
.filter(([file]) => file !== 'total')
|
|
.forEach(([file, data]) => {
|
|
fileSummaries.push({
|
|
file,
|
|
pct: data.lines?.pct ?? data.statements?.pct ?? 0,
|
|
lines: {
|
|
covered: data.lines?.covered ?? 0,
|
|
total: data.lines?.total ?? 0,
|
|
},
|
|
});
|
|
});
|
|
} else if (hasFinal) {
|
|
const coverage = JSON.parse(fs.readFileSync(finalPath, 'utf8'));
|
|
|
|
Object.entries(coverage).forEach(([file, entry]) => {
|
|
const lineHits = entry.l ?? {};
|
|
const statementHits = entry.s ?? {};
|
|
const branchHits = entry.b ?? {};
|
|
const functionHits = entry.f ?? {};
|
|
|
|
const lineTotal = Object.keys(lineHits).length;
|
|
const lineCovered = Object.values(lineHits).filter((n) => n > 0).length;
|
|
|
|
const statementTotal = Object.keys(statementHits).length;
|
|
const statementCovered = Object.values(statementHits).filter((n) => n > 0).length;
|
|
|
|
const branchTotal = Object.values(branchHits).reduce((acc, branches) => acc + branches.length, 0);
|
|
const branchCovered = Object.values(branchHits).reduce(
|
|
(acc, branches) => acc + branches.filter((n) => n > 0).length,
|
|
0,
|
|
);
|
|
|
|
const functionTotal = Object.keys(functionHits).length;
|
|
const functionCovered = Object.values(functionHits).filter((n) => n > 0).length;
|
|
|
|
totals.lines.total += lineTotal;
|
|
totals.lines.covered += lineCovered;
|
|
totals.statements.total += statementTotal;
|
|
totals.statements.covered += statementCovered;
|
|
totals.branches.total += branchTotal;
|
|
totals.branches.covered += branchCovered;
|
|
totals.functions.total += functionTotal;
|
|
totals.functions.covered += functionCovered;
|
|
|
|
const pct = (covered, tot) => (tot > 0 ? (covered / tot) * 100 : 0);
|
|
|
|
fileSummaries.push({
|
|
file,
|
|
pct: pct(lineCovered || statementCovered, lineTotal || statementTotal),
|
|
lines: {
|
|
covered: lineCovered || statementCovered,
|
|
total: lineTotal || statementTotal,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
const pct = (covered, tot) => (tot > 0 ? ((covered / tot) * 100).toFixed(2) : '0.00');
|
|
|
|
console.log('### Test Coverage Summary :test_tube:');
|
|
console.log('');
|
|
console.log('| Metric | Coverage | Covered / Total |');
|
|
console.log('|--------|----------|-----------------|');
|
|
console.log(`| Lines | ${pct(totals.lines.covered, totals.lines.total)}% | ${totals.lines.covered} / ${totals.lines.total} |`);
|
|
console.log(`| Statements | ${pct(totals.statements.covered, totals.statements.total)}% | ${totals.statements.covered} / ${totals.statements.total} |`);
|
|
console.log(`| Branches | ${pct(totals.branches.covered, totals.branches.total)}% | ${totals.branches.covered} / ${totals.branches.total} |`);
|
|
console.log(`| Functions | ${pct(totals.functions.covered, totals.functions.total)}% | ${totals.functions.covered} / ${totals.functions.total} |`);
|
|
|
|
console.log('');
|
|
console.log('<details><summary>File coverage (lowest lines first)</summary>');
|
|
console.log('');
|
|
console.log('```');
|
|
fileSummaries
|
|
.sort((a, b) => (a.pct - b.pct) || (b.lines.total - a.lines.total))
|
|
.slice(0, 25)
|
|
.forEach(({ file, pct, lines }) => {
|
|
console.log(`${pct.toFixed(2)}%\t${lines.covered}/${lines.total}\t${file}`);
|
|
});
|
|
console.log('```');
|
|
console.log('</details>');
|
|
NODE
|
|
|
|
- name: Upload Coverage Artifact
|
|
if: steps.coverage-summary.outputs.has_coverage == 'true'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: web-coverage-report
|
|
path: web/coverage
|
|
retention-days: 30
|
|
if-no-files-found: error
|