Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
2ce87da
Post reports as a single PR comment
gohabereg May 31, 2026
d8edf3a
Merge branch 'main' into chore/pr-reporting
gohabereg May 31, 2026
88f1dd4
update all actions
gohabereg May 31, 2026
c175508
Remove core require
gohabereg May 31, 2026
8b117ee
build packages for base branch test run
gohabereg May 31, 2026
8904eb5
Replace require with import
gohabereg May 31, 2026
2f21c6c
Revert "Replace require with import"
gohabereg May 31, 2026
6642696
Replace require only inside js scripts
gohabereg May 31, 2026
1898afa
Fixes
gohabereg May 31, 2026
1b629a0
Remove unused code
gohabereg May 31, 2026
f6382d4
Pass core and github as parameters
gohabereg May 31, 2026
6834c3e
Remove require core and github
gohabereg May 31, 2026
1cca3ed
revert github require
gohabereg May 31, 2026
e962bcc
Pass context separately
gohabereg May 31, 2026
7910489
Remove turnstyle initial wait, update cache action
gohabereg May 31, 2026
d8bf2c1
Add reporting for unit tests to each package, update changed-files ac…
gohabereg May 31, 2026
9197c9b
Use coverage json-summary reporter
gohabereg May 31, 2026
ff2acc8
Upload mutation artifact even if no files changed
gohabereg May 31, 2026
7aa89ef
Send message about files not found if the files list is empty
gohabereg May 31, 2026
00051da
Add concurrency
gohabereg May 31, 2026
acb8efd
Test coverage drop and mutation tests report
gohabereg May 31, 2026
e41ee6e
Add at least one test
gohabereg May 31, 2026
b3be451
fixes
gohabereg May 31, 2026
3865c3c
update files list formatting
gohabereg Jun 1, 2026
389e2b5
Update artifact paths
gohabereg Jun 1, 2026
d5e785f
Test coverage drop
gohabereg Jun 1, 2026
7b9bb7e
Always run report, add dashboard url to mutation report
gohabereg Jun 1, 2026
952d1e8
fix base branch tests checkout
gohabereg Jun 1, 2026
eca9bfe
Test dashboard url
gohabereg Jun 1, 2026
72a3a1c
Add json report for dom-adapters
gohabereg Jun 1, 2026
9da66ec
Mutation score to percent conversion
gohabereg Jun 1, 2026
1685a8c
Switch warning and caution
gohabereg Jun 1, 2026
b30e1fd
resolve review comments
gohabereg Jun 1, 2026
488bf4c
Extract duplication to a common workflow. Run workflow only if packag…
gohabereg Jun 1, 2026
8f2da60
resolve base ref for shallow clone
gohabereg Jun 1, 2026
fbe7bd8
Update script
gohabereg Jun 1, 2026
9533fee
Update path to script
gohabereg Jun 1, 2026
0d24791
Remove some lint warnings
gohabereg Jun 1, 2026
962b1b0
Update format
gohabereg Jun 1, 2026
a08ce4a
Fix typo
gohabereg Jun 1, 2026
d0bb75a
Get info from computed data
gohabereg Jun 1, 2026
6461226
Add pipes at the end of the rows
gohabereg Jun 1, 2026
1515928
Remove empty lines between rows
gohabereg Jun 1, 2026
c13ff85
test updates and mutation report
gohabereg Jun 1, 2026
605d146
Revert test
gohabereg Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/actions/aggregate-report/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Aggregate report
description: 'Process downloaded artifacts (downloaded with actions/download-artifact) and produce a markdown report section'
inputs:
package-name:
description: 'Full package name (workspace)'
required: true
working-directory:
description: 'Path to the ./packages/name_of_your_package_folder'
required: true
outputs:
report:
description: 'Markdown report section generated by the action'
value: ${{ steps.section-report.outputs.report }}
runs:
using: 'composite'
steps:
- name: Download artifacts
uses: actions/download-artifact@v8

- name: Find current PR's number
uses: jwalton/gh-find-current-pr@v1
id: find_pr

- name: Process coverage reports
uses: actions/github-script@v9
id: process_test_report
env:
PACKAGE_NAME: ${{ inputs.package-name }}
with:
script: |
const processReports = require('./.github/scripts/compare-coverage.js').processReports;

core.setOutput('message', processReports(process.env.PACKAGE_NAME, 'test-report', 'test-report-base'));

Comment thread
gohabereg marked this conversation as resolved.
- name: Process mutation report
uses: actions/github-script@v9
id: process_mutation_report
env:
WORKING_DIR: ${{ inputs.working-directory }}
PR_NUMBER: ${{ steps.find_pr.outputs.number }}
PACKAGE_NAME: ${{ inputs.package-name }}
with:
script: |
const processMutationReport = require('./.github/scripts/process-mutation.js').processMutationReport;

core.setOutput('message', processMutationReport('mutation-report', `${process.env.WORKING_DIR}/reports/mutation/mutation.json`, 'changed-files.json', process.env.PR_NUMBER, process.env.PACKAGE_NAME));

- name: Acquire comment-update lock
id: acquire_lock
uses: softprops/turnstyle@v3
with:
queue-name: aggregated-comment-${{ steps.find_pr.outputs.number || github.ref_name || github.ref }}}}

- name: Create or update PR comment with report
uses: actions/github-script@v9
env:
REPORT: ${{ steps.section-report.outputs.report }}
PACKAGE: ${{ inputs.package-name }}
PR_NUMBER: ${{ steps.find_pr.outputs.number }}
MUTATION_MESSAGE: ${{ steps.process_mutation_report.outputs.message }}
UNIT_TEST_MESSAGE: ${{ steps.process_test_report.outputs.message }}
with:
script: |
const updater = require('./.github/scripts/update-aggregated-comment.js');

(async () => {
try {
const packageName = process.env.PACKAGE;
const mutationRow = process.env.MUTATION_MESSAGE;
const unitTestRow = process.env.UNIT_TEST_MESSAGE;
const pr = process.env.PR_NUMBER || undefined;
// Pass the injected core, github and context objects
await updater.updateAggregatedComment(unitTestRow, mutationRow, packageName, pr, core, github, context);
} catch (err) {
core.error(err && err.stack ? err.stack : String(err));
throw err;
}
})();
45 changes: 45 additions & 0 deletions .github/actions/base-coverage/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Base coverage
description: "Checkout base ref, run package coverage and upload artifact"
inputs:
package-name:
description: 'Full package name (workspace)'
required: true
working-directory:
description: 'Working directory relative to repo root (package folder)'
required: true

runs:
using: "composite"
steps:
- name: Checkout base ref
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.base.ref }}
fetch-depth: 0

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

- name: Setup environment
uses: ./.github/actions/setup

- name: Build the package
shell: bash
run: yarn workspace ${{ inputs.package-name }} run build
working-directory: ${{ inputs.working-directory }}

- name: Run coverage
shell: bash
run: yarn workspace ${{ inputs.package-name }} test:coverage
working-directory: ${{ inputs.working-directory }}

- name: Upload base coverage artifact
uses: actions/upload-artifact@v7
with:
name: test-report-base
path: |
${{ inputs.working-directory }}/coverage
${{ inputs.working-directory }}/jest-report.json
retention-days: 3
2 changes: 1 addition & 1 deletion .github/actions/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

Expand Down
2 changes: 1 addition & 1 deletion .github/actions/lint/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

Expand Down
8 changes: 5 additions & 3 deletions .github/actions/mutation-tests-all-files/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ inputs:
stryker_dashboard_api_key:
description: 'Stryker dashboard api key'
required: true

working-directory:
description: 'Path to the ./packages/name_of_your_package_folder'
required: true
runs:
using: "composite"
steps:
- uses: actions/setup-node@v3
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

Expand All @@ -24,7 +26,7 @@ runs:
package-name: ${{ inputs.package-name }}

- name: Get changed files
uses: tj-actions/changed-files@v39.0.0
uses: tj-actions/changed-files@v47
id: changed-files
with:
files_yaml: |
Expand Down
70 changes: 21 additions & 49 deletions .github/actions/mutation-tests-changed-files/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ inputs:
runs:
using: "composite"
steps:
- uses: actions/setup-node@v3
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

Expand All @@ -27,15 +27,15 @@ runs:
package-name: ${{ inputs.package-name }}

- name: Get changed files
uses: tj-actions/changed-files@v39.0.0
uses: tj-actions/changed-files@v47
id: changed-files
with:
files_yaml: |
src:
- 'src/**/*.ts'
- '!src/**/*.spec.ts'
- '!src/**/__mocks__/**'
separator: "','"
separator: ','
path: ${{ inputs.working-directory }}

- name: Run mutation tests
Expand All @@ -44,58 +44,30 @@ runs:
STRYKER_DASHBOARD_API_KEY: ${{ inputs.stryker_dashboard_api_key }}
shell: bash
id: run-mutation-tests
run: yarn workspace ${{ inputs.package-name }} test:mutations --mutate ${{format('''{0}''', steps.changed-files.outputs.src_all_changed_files)}} --dashboard.module ${{ inputs.package-name }}
run: yarn workspace ${{ inputs.package-name }} test:mutations --mutate "${{ steps.changed-files.outputs.src_all_changed_files }}" --dashboard.module ${{ inputs.package-name }}
continue-on-error: true

- name: Find current PR's number
uses: jwalton/gh-find-current-pr@v1
id: findPr

- name: Comment on successful mutation testing
uses: thollander/actions-comment-pull-request@v2
if: steps.changed-files.outputs.src_any_changed == 'true' && steps.run-mutation-tests.outcome == 'success' && ${{ steps.findPr.outputs.number != '' }}
with:
message: |
✅ Mutation testing passed for `${{ inputs.working-directory }}`

Report: https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-${{ steps.findPr.outputs.number }}?module=${{ inputs.package-name }}

<details>
<summary>Mutated files</summary>
<pre>
${{ join(fromJson(format('[{0}]', format('''{0}''', steps.changed-files.outputs.src_all_changed_files))), '<br />') }}
</pre>
</details>
comment_tag: mutation-tests for `${{ inputs.working-directory }}`
pr_number: ${{ steps.findPr.outputs.number != '' && steps.findPr.outputs.number || '1'}}

- name: Comment on failed mutation testing
uses: thollander/actions-comment-pull-request@v2
if: steps.changed-files.outputs.src_any_changed == 'true' && steps.run-mutation-tests.outcome == 'failure' && ${{ steps.findPr.outputs.number != '' }}
with:
message: |
❌ Mutation testing hasn't passed score threshold for `${{ inputs.working-directory }}`

Report: https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-${{ steps.findPr.outputs.number }}?module=${{ inputs.package-name }}
- name: Save mutated files list
if: steps.changed-files.outputs.src_any_changed == 'true'
shell: bash
run: |
echo "${{ steps.changed-files.outputs.src_all_changed_files }}" | sed 's/,/","/g' | sed 's/^/["/;s/$/"]/' > changed-files.json

<details>
<summary>Mutated files</summary>
<pre>
${{ join(fromJson(format('[{0}]', format('''{0}''', steps.changed-files.outputs.src_all_changed_files))), '<br />') }}
</pre>
</details>
comment_tag: mutation-tests for `${{ inputs.working-directory }}`
pr_number: ${{ steps.findPr.outputs.number != '' && steps.findPr.outputs.number || '1'}}
- name: Save empty list if no files changed
if: steps.changed-files.outputs.src_any_changed == 'false'
shell: bash
run: echo "[]" > changed-files.json

- name: Comment on empty changes
uses: thollander/actions-comment-pull-request@v2
if: steps.changed-files.outputs.src_any_changed == 'false' && ${{ steps.findPr.outputs.number != '' }}
- name: Upload mutation report artifact
uses: actions/upload-artifact@v7
with:
message: |
⏭️ No files to mutate for `${{ inputs.working-directory }}`
name: mutation-report
path: |
${{ inputs.working-directory }}/reports/mutation/mutation.json
changed-files.json
retention-days: 3

comment_tag: mutation-tests for `${{ inputs.working-directory }}`
pr_number: ${{ steps.findPr.outputs.number != '' && steps.findPr.outputs.number || '1'}}
# reporting will be triggered by workflow_run on workflow completion

- if: steps.changed-files.outputs.src_any_changed == 'true' && steps.run-mutation-tests.outcome == 'failure'
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ runs:
shell: bash

- name: Restore yarn cache folder
uses: actions/cache@v3
uses: actions/cache@v5
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
Expand Down
31 changes: 21 additions & 10 deletions .github/actions/unit-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ inputs:
runs:
using: "composite"
steps:
- uses: actions/setup-node@v3
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- uses: ./.github/actions/setup
Expand All @@ -19,15 +19,26 @@ runs:
with:
package-name: ${{ inputs.package-name }}

# Find current PR's number
- uses: jwalton/gh-find-current-pr@v1
id: findPr
- name: Run unit tests with coverage
if: ${{ github.ref != 'refs/heads/main' }} # Skip coverage on main branch to save time and resources
shell: bash
run: |
yarn workspace ${{ inputs.package-name }} test:coverage

- name: Run unit tests
uses: ArtiomTr/jest-coverage-report-action@v2
- name: Upload coverage artifact (PRs only)
if: ${{ github.event_name == 'pull_request' }}
uses: actions/upload-artifact@v7
with:
custom-title: Coverage report for `${{ inputs.working-directory }}`
name: test-report
path: |
${{ inputs.working-directory }}/coverage
${{ inputs.working-directory }}/jest-report.json
retention-days: 3

- name: Run base coverage action
if: ${{ github.event_name == 'pull_request' }}
uses: ./.github/actions/base-coverage
with:
package-name: ${{ inputs.package-name }}
working-directory: ${{ inputs.working-directory }}
Comment on lines +38 to 43
test-script: yarn workspace ${{ inputs.package-name }} test
package-manager: yarn
prnumber: ${{ steps.findPr.outputs.number }}

71 changes: 71 additions & 0 deletions .github/scripts/compare-coverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import fs from 'fs';
import path from 'path';
Comment thread
gohabereg marked this conversation as resolved.

function findCoverageJson(dir) {
if (!dir) return null;
const report = path.join(dir, 'coverage', 'coverage-summary.json');


if (fs.existsSync(report)) {
try {
return JSON.parse(fs.readFileSync(report, 'utf8'));
} catch (report) {
console.error(report);
}
}

Comment on lines +9 to +16
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice if eslint checked this scripts too, but for now i would suggest logging of the report or explicitly dismissing with void report

return null;
}


function pctFromCoverageSummary(obj, category) {
if (!obj) return null;

if (obj.total && obj.total[category] && typeof obj.total[category].pct === 'number') return obj.total[category].pct;

if (obj[category] && typeof obj[category].pct === 'number') return obj[category].pct;

return null;
}

function compute(headSummary, baseSummary) {
const categories = ['statements', 'branches', 'functions', 'lines'];

const out = {};

for (const cat of categories) {
const headPct = pctFromCoverageSummary(headSummary, cat);
const basePct = pctFromCoverageSummary(baseSummary, cat);

let delta = 'N/A';

if (headPct != null && basePct != null) {
delta = Number((headPct - basePct).toFixed(2));
}

out[cat] = { head: headPct, base: basePct, delta };
}
return out;
}

export function processReports(pkg, headDir, baseDir) {
const headCov = findCoverageJson(headDir);
const baseCov = findCoverageJson(baseDir);

const categories = compute(headCov, baseCov);

let delta = categories.branches.delta;

if (delta < 0) {
delta = `+${delta}% 🟢`;
} else if (delta > 0) {
delta = `-${delta}% 🔴`;
} else if (delta === 0) {
delta = `0% ⚪️`;
}

// | Package | Branches coverage | Delta |
return `| ${pkg} | ${categories.branches.head}% | ${delta} |`;
}


Loading
Loading