@@ -1450,3 +1450,93 @@ func TestExtractPIFileParam_MultiLine(t *testing.T) {
14501450 require .NoError (t , err )
14511451 assert .Equal (t , "other.md" , result )
14521452}
1453+
1454+ // =====================================================================
1455+ // Plan 61 hardening: additional edge-case tests
1456+ // =====================================================================
1457+
1458+ // Wildcard heading (`# ?`) must still enforce the correct level.
1459+ // A document h2 where h1 is required produces a level-mismatch diagnostic.
1460+ func TestCheck_WildcardHeadingLevelMismatch (t * testing.T ) {
1461+ schemaPath := writeSchema (t , "# ?\n " )
1462+ r := & Rule {Schema : schemaPath }
1463+ f := newTestFile (t , "doc.md" , "## Title\n " )
1464+ diags := r .Check (f )
1465+ expectDiagMsg (t , diags , `heading level mismatch for "Title": expected h1, got h2` )
1466+ }
1467+
1468+ // Soft-wrapped body paragraph (multiple lines joined by space) must
1469+ // match the front matter field value when concatenated.
1470+ func TestCheck_BodySyncSoftWrapped (t * testing.T ) {
1471+ schemaPath := writeSchema (t , "# ?\n \n {description}\n " )
1472+ r := & Rule {Schema : schemaPath }
1473+ f := newTestFile (t , "doc.md" ,
1474+ "---\n description: Line exceeds maximum length.\n ---\n # My Rule\n \n " +
1475+ "Line exceeds\n maximum length.\n " )
1476+ diags := r .Check (f )
1477+ expectDiags (t , diags , 0 )
1478+ }
1479+
1480+ // The improved body sync diagnostic must include the expected value so
1481+ // authors know what text to write.
1482+ func TestCheck_BodySyncDiagnosticIncludesExpected (t * testing.T ) {
1483+ schemaPath := writeSchema (t , "# ?\n \n {description}\n " )
1484+ r := & Rule {Schema : schemaPath }
1485+ f := newTestFile (t , "doc.md" ,
1486+ "---\n description: Correct description.\n ---\n # My Rule\n \n Wrong text.\n " )
1487+ diags := r .Check (f )
1488+ expectDiagMsg (t , diags , `expected "Correct description."` )
1489+ }
1490+
1491+ // Integer front matter values are stringified for heading sync.
1492+ func TestCheck_SyncIntegerFrontMatterValue (t * testing.T ) {
1493+ schemaPath := writeSchema (t , "# {id}: {name}\n " )
1494+ r := & Rule {Schema : schemaPath }
1495+ f := newTestFile (t , "doc.md" ,
1496+ "---\n id: 42\n name: line-length\n ---\n # 42: line-length\n " )
1497+ diags := r .Check (f )
1498+ expectDiags (t , diags , 0 )
1499+ }
1500+
1501+ // When a synced heading is absent from the document, checkSync must
1502+ // not emit a spurious diagnostic; only checkStructure reports it.
1503+ func TestCheck_SyncNotFiredForMissingHeading (t * testing.T ) {
1504+ schemaPath := writeSchema (t , "# ?\n \n ## {title}\n " )
1505+ r := & Rule {Schema : schemaPath }
1506+ f := newTestFile (t , "doc.md" ,
1507+ "---\n title: My Section\n ---\n # Title\n " )
1508+ diags := r .Check (f )
1509+ // Exactly one diagnostic: missing required section, no sync error.
1510+ require .Len (t , diags , 1 )
1511+ expectDiagMsg (t , diags , "missing required section" )
1512+ for _ , d := range diags {
1513+ assert .NotContains (t , d .Message , "sync" )
1514+ }
1515+ }
1516+
1517+ // When several required sections are all absent, each gets its own
1518+ // "missing required section" diagnostic.
1519+ func TestCheck_MultipleMissingSections (t * testing.T ) {
1520+ schemaPath := writeSchema (t ,
1521+ "# ?\n \n ## Goal\n \n ## Tasks\n \n ## Acceptance Criteria\n " )
1522+ r := & Rule {Schema : schemaPath }
1523+ f := newTestFile (t , "doc.md" , "# Title\n " )
1524+ diags := r .Check (f )
1525+ expectDiagMsg (t , diags , `missing required section "## Goal"` )
1526+ expectDiagMsg (t , diags , `missing required section "## Tasks"` )
1527+ expectDiagMsg (t , diags , `missing required section "## Acceptance Criteria"` )
1528+ }
1529+
1530+ // A section that is both out of order AND at the wrong level must
1531+ // produce both the out-of-order and the level-mismatch diagnostic.
1532+ func TestCheck_OutOfOrderAlsoReportsLevelMismatch (t * testing.T ) {
1533+ schemaPath := writeSchema (t ,
1534+ "# ?\n \n ## Goal\n \n ## Tasks\n " )
1535+ r := & Rule {Schema : schemaPath }
1536+ // Tasks (h2) appears before Goal; Goal appears at h3 (wrong level).
1537+ f := newTestFile (t , "doc.md" ,
1538+ "# Title\n \n ## Tasks\n \n ### Goal\n " )
1539+ diags := r .Check (f )
1540+ expectDiagMsg (t , diags , `out of order` )
1541+ expectDiagMsg (t , diags , `heading level mismatch` )
1542+ }
0 commit comments