fix(#2366): scope parseCoverageMatrix to recognized coverage tables (#2551)

* test(#2366): regression tests for parseCoverageMatrix scoping bugs

Bug 1: summary table outside matrix not parsed as data
Bug 2: multi-section matrix with repeated headers parses correctly
Bug 3: markdown emphasis on decision cell is stripped

* fix(#2366): scope parseCoverageMatrix to recognized coverage tables

Replace latching sawHeader with contextual inMatrix tracking that
resets on non-pipe lines, preventing summary tables from being parsed
as data (bug 1). Allow multiple headers for multi-section matrices
(bug 2). Strip markdown emphasis from decision cells before validation
(bug 3).

Closes #2366

* fix(#2366): update representative-corpus test to expect correct behavior

The test previously documented the known-buggy parseCoverageMatrix behavior.
Now that the fix is in place, test against the expected correct output
(expectedBlock, expectedCounts, expectedErrorCount) instead of the
currentBuggyOutput snapshot.

* docs(#2366): backfill changeset PR number (2551)
This commit is contained in:
Tom Boucher
2026-07-23 07:38:00 -04:00
committed by GitHub
parent 0ad3c5dd14
commit 1482dc5ce0
4 changed files with 83 additions and 23 deletions

View File

@@ -562,6 +562,58 @@ describe('coverage matrix — parse / validate (#1562 acceptance #2)', () => {
assert.strictEqual(p.rows.length, 0);
});
test('#2366 bug 1: summary table outside the matrix is not parsed as data', () => {
const md = [
'# API Coverage',
'',
'| capability | decision | reason |',
'|---|---|---|',
'| search | INTEGRATE | |',
'',
'## Coverage summary',
'',
'| tier | INTEGRATE | OPT-OUT |',
'|---|---|---|',
'| phase 8 | 12 | 6 |',
].join('\n');
const p = parseCoverageMatrix(md);
assert.strictEqual(p.rows.length, 1, 'only the matrix row, not the summary table');
assert.strictEqual(p.rows[0].capability, 'search');
assert.strictEqual(p.errors.length, 0);
});
test('#2366 bug 2: multi-section matrix with repeated headers is parsed', () => {
const md = [
'| capability | decision | reason |',
'|---|---|---|',
'| search | INTEGRATE | |',
'',
'## Transferred',
'',
'| capability | decision | reason |',
'|---|---|---|',
'| widget | OPT-OUT | deferred to 9 |',
].join('\n');
const p = parseCoverageMatrix(md);
assert.strictEqual(p.rows.length, 2, 'both sections should parse');
assert.strictEqual(p.rows[0].capability, 'search');
assert.strictEqual(p.rows[1].capability, 'widget');
assert.strictEqual(p.errors.length, 0);
});
test('#2366 bug 3: markdown emphasis on decision is stripped', () => {
const md = [
'| capability | decision | reason |',
'|---|---|---|',
'| search | INTEGRATE | |',
'| skip | **OPT-OUT** | not needed yet |',
].join('\n');
const p = parseCoverageMatrix(md);
assert.strictEqual(p.rows.length, 2);
assert.strictEqual(p.rows[1].decision, 'OPT-OUT', '**OPT-OUT** should parse as OPT-OUT');
assert.strictEqual(p.errors.length, 0);
});
// ── validate: boundaries 0 / 1 / 2 rows (limit-1, limit, limit+1) ────────
test('validate — empty matrix is invalid (acceptance #1: surface must be enumerated)', () => {
const v = validateCoverageMatrix('| capability | decision | reason |\n|---|---|---|');

View File

@@ -153,9 +153,7 @@ describe('representative corpus — api-coverage matrix (#2366)', () => {
const manifest = readManifest('api-coverage-matrix');
for (const fx of manifest.fixtures) {
const label = fx.currentBuggyOutput
? `${fx.file} → currently silently-corrupted + spurious errors (#2366)`
: `${fx.file} → exactly the canonical rows, 0 errors`;
const label = `${fx.file} → exactly the canonical rows, 0 errors`;
test(label, () => {
tmpDir = makeProject();
const phaseDir = makePhaseDir(tmpDir, '01-repcorpus');
@@ -166,18 +164,10 @@ describe('representative corpus — api-coverage matrix (#2366)', () => {
assert.ok(r.success, `gate should succeed (JSON). stderr: ${r.error}`);
const j = JSON.parse(r.output);
if (fx.currentBuggyOutput) {
assert.strictEqual(j.block, fx.currentBuggyOutput.block,
`${fx.file}: expected today's known-buggy block:${fx.currentBuggyOutput.block}, got ${JSON.stringify(j)}. ` +
`If this now differs, #2366 may be fixed — check against expectedBlock:${fx.expectedBlock} instead.`);
assert.strictEqual(j.error_count, fx.currentBuggyOutput.error_count, `${fx.file}: error_count`);
assert.deepStrictEqual(j.errors, fx.currentBuggyOutput.errors, `${fx.file}: errors`);
} else {
assert.strictEqual(j.block, fx.expectedBlock, `${fx.file}: block. Got ${JSON.stringify(j)}`);
assert.deepStrictEqual(j.counts, fx.expectedCounts, `${fx.file}: counts. Got ${JSON.stringify(j)}`);
assert.strictEqual((j.errors || []).length, fx.expectedErrorCount,
`${fx.file}: errors. Got ${JSON.stringify(j.errors)}`);
}
assert.strictEqual(j.block, fx.expectedBlock, `${fx.file}: block. Got ${JSON.stringify(j)}`);
assert.deepStrictEqual(j.counts, fx.expectedCounts, `${fx.file}: counts. Got ${JSON.stringify(j)}`);
assert.strictEqual((j.errors || []).length, fx.expectedErrorCount,
`${fx.file}: errors. Got ${JSON.stringify(j.errors)}`);
});
}
});