diff --git a/.github/scripts/ui-code-coverage.mjs b/.github/scripts/ui-code-coverage.mjs new file mode 100644 index 0000000000..5e4dd70795 --- /dev/null +++ b/.github/scripts/ui-code-coverage.mjs @@ -0,0 +1,133 @@ +import fs from "fs"; + +const readJson = (filePath) => { + try { + return JSON.parse(fs.readFileSync(filePath, "utf-8")); + } catch (e) { + throw new Error(`Failed to read or parse JSON at ${filePath}: ${e.message}`); + } + }; + + +const findTotalPercentage = (rawData) => { + let totalLineCovered = 0; + let totalLineTotal = 0; + let totalBranchCovered = 0; + let totalBranchTotal = 0; + + for (const [, data] of Object.entries(rawData)) { + const lineData = data.lines; + const branchData = data.branches; + + totalLineCovered += lineData.covered; + totalLineTotal += lineData.total; + + totalBranchCovered += branchData.covered; + totalBranchTotal += branchData.total; + } + + const totalLinePct = + totalLineTotal === 0 ? 100 : (totalLineCovered / totalLineTotal) * 100; + const totalBranchPct = + totalBranchTotal === 0 + ? 100 + : (totalBranchCovered / totalBranchTotal) * 100; + + + return [totalLinePct, totalBranchPct]; +}; + + +// @ts-check +/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ +export default async ({ github, context }) => { + const prCoveragePath = "/tmp/pr-coverage.json"; + const developCoveragePath = "/tmp/develop-coverage.json"; + + console.log("๐Ÿงช Loaded latest ui-code-coverage.mjs script version"); + + // const developCoveragePath = '../../ui/coverage/coverage-summary.json'; + // const prCoveragePath = '../../ui/coverage/coverage-summary.json'; + + // const prCoverage = JSON.parse(fs.readFileSync('/tmp/pr-coverage.json', 'utf8')); + // const devCoverage = JSON.parse(fs.readFileSync('/tmp/develop-coverage.json', 'utf8')); + + const developCoverage = readJson(developCoveragePath); + const prCoverage = readJson(prCoveragePath); + + const [prLinePct, prBranchPct] = findTotalPercentage(prCoverage); + const [developLinePct, developBranchPct] = findTotalPercentage(developCoverage); + + // core.info("๐Ÿ“Š Total Coverage Summary For PR"); + // core.info("--------------------------"); + // core.info(`โœ… Line Coverage: ${prLinePct.toFixed(2)}%`); + // core.info(`โœ… Branch Coverage: ${prBranchPct.toFixed(2)}%`); + + // core.info("๐Ÿ“Š Total Coverage Summary For Develop"); + // core.info("--------------------------"); + // core.info(`โœ… Line Coverage: ${developLinePct.toFixed(2)}%`); + // core.info(`โœ… Branch Coverage: ${developBranchPct.toFixed(2)}%`); + + const lineDiff = prLinePct - developLinePct; + const branchDiff = prBranchPct - developBranchPct; + + let status = "unchanged"; + if (lineDiff > 0 || branchDiff > 0) { + status = "increased"; + } else if (lineDiff < 0 || branchDiff < 0) { + status = "decreased"; + } + + const lineStatus = + lineDiff > 0 + ? "๐ŸŸข Increased" + : lineDiff < 0 + ? "๐Ÿ”ด Decreased" + : "โšช Unchanged"; + const branchStatus = + branchDiff > 0 + ? "๐ŸŸข Increased" + : branchDiff < 0 + ? "๐Ÿ”ด Decreased" + : "โšช Unchanged"; + + const message = ` + ## ๐Ÿงช Frontend Code Coverage Report ${status === 'increased' ? '๐ŸŽ‰' : status === 'decreased' ? 'โš ๏ธ' : '๐Ÿ”„'}\n\n + + | Metric | PR | Base (develop) | Change | Status | + |--------|----|----------------|--------|--------| + | Line Coverage | ${prLinePct.toFixed(2)}% | ${developLinePct.toFixed(2)}% | ${lineDiff.toFixed(2)}% | ${lineStatus} | + | Branch Coverage | ${prBranchPct.toFixed(2)}% | ${developBranchPct.toFixed(2)}% | ${branchDiff.toFixed(2)}% | ${branchStatus} | + `; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const oldComment = comments.find( + (c) => + c.user.login === "github-actions[bot]" && + c.body.startsWith("## ๐Ÿงช Frontend Code Coverage Report") + ); + + if (oldComment) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: oldComment.id, + }); + } + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message.trim(), + }); + + if (lineDiff < 0 || branchDiff < 0) { + // core.setFailed("Coverage has decreased. Please improve test coverage."); + } +}; diff --git a/.github/workflows/ui-cod-cov.yml b/.github/workflows/ui-cod-cov.yml new file mode 100644 index 0000000000..690d03b035 --- /dev/null +++ b/.github/workflows/ui-cod-cov.yml @@ -0,0 +1,56 @@ +name: UI Code Coverage + +on: + pull_request: + branches: + - develop + paths: + - 'ui/src/**' + - 'tests/ui/**' + types: [opened, synchronize, reopened] + +jobs: + compare-frontend-coverage: + runs-on: ubuntu-22.04 + permissions: + pull-requests: write + contents: read + + steps: + - name: Checkout PR + uses: actions/checkout@v4 + + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: install UI deps + uses: ./.github/actions/cached-ui-deps + + - name: Generate coverage for PR branch + run: | + cd ui + npx jest --coverage --coverageReporters=json-summary + cp coverage/coverage-summary.json /tmp/pr-coverage.json + + - name: Checkout develop branch + uses: actions/checkout@v4 + with: + ref: develop + + - name: install UI deps + uses: ./.github/actions/cached-ui-deps + + - name: Generate coverage for develop branch + run: | + cd ui + npx jest --coverage --coverageReporters=json-summary + cp coverage/coverage-summary.json /tmp/develop-coverage.json + + - name: Compare and comment coverage + uses: actions/github-script@v7 + with: + script: | + const { default: compareDiff } = await import('.github/scripts/ui-code-coverage.mjs') + await compareDiff({ github, context }) diff --git a/ui/jest.config.ts b/ui/jest.config.ts index fd5584f3c1..3e4e20e6cd 100644 --- a/ui/jest.config.ts +++ b/ui/jest.config.ts @@ -11,6 +11,7 @@ export default { restoreMocks: true, // Coverage collectCoverage: true, + coverageReporters: ['json', 'text', 'lcov', 'json-summary'], collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], coveragePathIgnorePatterns: [ '/node_modules/', diff --git a/ui/src/components/CheckboxTree/CheckboxTree.test.tsx b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx index be6439513c..0d14457d55 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.test.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx @@ -83,6 +83,11 @@ describe('CheckboxTree Component', () => { expect(screen.getByLabelText('Row without group')).toBeInTheDocument(); expect(screen.getByLabelText('Row under Group 1')).toBeInTheDocument(); expect(screen.getByLabelText('First row under Group 3')).toBeInTheDocument(); + expect(screen.getByLabelText('First row under Group 3')).toBeInTheDocument(); + expect(screen.getByLabelText('First row under Group 3')).toBeInTheDocument(); + expect(screen.getByLabelText('First row under Group 3')).toBeInTheDocument(); + expect(screen.getByLabelText('First row under Group 3')).toBeInTheDocument(); + expect(screen.getByLabelText('First row under Group 3')).toBeInTheDocument(); expect(screen.getByLabelText('Second row under Group 3')).toBeInTheDocument(); });