1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ParserMode\GfmDeleted; 6 7/** 8 * Tests for the GFM strikethrough mode (`~~text~~`). 9 */ 10class GfmDeletedTest extends ParserTestBase 11{ 12 public function setUp(): void 13 { 14 parent::setUp(); 15 $this->setSyntax('md'); 16 } 17 18 public function testBasicStrikethrough() 19 { 20 $this->P->addMode('gfm_deleted', new GfmDeleted()); 21 $this->P->parse('Foo ~~Bar~~ Baz'); 22 23 $calls = [ 24 ['document_start', []], 25 ['p_open', []], 26 ['cdata', ["\nFoo "]], 27 ['deleted_open', []], 28 ['cdata', ['Bar']], 29 ['deleted_close', []], 30 ['cdata', [' Baz']], 31 ['p_close', []], 32 ['document_end', []], 33 ]; 34 $this->assertCalls($calls, $this->H->calls); 35 } 36 37 public function testSingleCharacterBody() 38 { 39 $this->P->addMode('gfm_deleted', new GfmDeleted()); 40 $this->P->parse('foo ~~b~~ bar'); 41 42 $calls = [ 43 ['document_start', []], 44 ['p_open', []], 45 ['cdata', ["\nfoo "]], 46 ['deleted_open', []], 47 ['cdata', ['b']], 48 ['deleted_close', []], 49 ['cdata', [' bar']], 50 ['p_close', []], 51 ['document_end', []], 52 ]; 53 $this->assertCalls($calls, $this->H->calls); 54 } 55 56 public function testMultipleWords() 57 { 58 $this->P->addMode('gfm_deleted', new GfmDeleted()); 59 $this->P->parse('~~three four five~~'); 60 61 $calls = [ 62 ['document_start', []], 63 ['p_open', []], 64 ['cdata', ["\n"]], 65 ['deleted_open', []], 66 ['cdata', ['three four five']], 67 ['deleted_close', []], 68 ['cdata', ['']], 69 ['p_close', []], 70 ['document_end', []], 71 ]; 72 $this->assertCalls($calls, $this->H->calls); 73 } 74 75 public function testTwoSeparateStrikethroughsOnOneLine() 76 { 77 $this->P->addMode('gfm_deleted', new GfmDeleted()); 78 $this->P->parse('~~one~~ and ~~two~~'); 79 80 $calls = [ 81 ['document_start', []], 82 ['p_open', []], 83 ['cdata', ["\n"]], 84 ['deleted_open', []], 85 ['cdata', ['one']], 86 ['deleted_close', []], 87 ['cdata', [' and ']], 88 ['deleted_open', []], 89 ['cdata', ['two']], 90 ['deleted_close', []], 91 ['cdata', ['']], 92 ['p_close', []], 93 ['document_end', []], 94 ]; 95 $this->assertCalls($calls, $this->H->calls); 96 } 97 98 public function testUnmatchedOpenerDoesNotStrike() 99 { 100 $this->P->addMode('gfm_deleted', new GfmDeleted()); 101 $this->P->parse('foo ~~bar with no closer'); 102 103 $calls = [ 104 ['document_start', []], 105 ['p_open', []], 106 ['cdata', ["\nfoo ~~bar with no closer"]], 107 ['p_close', []], 108 ['document_end', []], 109 ]; 110 $this->assertCalls($calls, $this->H->calls); 111 } 112 113 public function testOpenerFollowedBySpaceDoesNotStrike() 114 { 115 $this->P->addMode('gfm_deleted', new GfmDeleted()); 116 $this->P->parse('foo ~~ bar~~ baz'); 117 118 $calls = [ 119 ['document_start', []], 120 ['p_open', []], 121 ['cdata', ["\nfoo ~~ bar~~ baz"]], 122 ['p_close', []], 123 ['document_end', []], 124 ]; 125 $this->assertCalls($calls, $this->H->calls); 126 } 127 128 public function testEmptyDelimiterDoesNotStrike() 129 { 130 $this->P->addMode('gfm_deleted', new GfmDeleted()); 131 $this->P->parse('foo ~~~~ bar'); 132 133 $modes = array_column($this->H->calls, 0); 134 $this->assertNotContains('deleted_open', $modes, 135 'Empty `~~~~` must stay literal'); 136 } 137 138 public function testTripleTildeDoesNotStrike() 139 { 140 // `~~~` is the GFM fenced-code-block marker; strikethrough must not 141 // consume a run of three or more tildes. 142 $this->P->addMode('gfm_deleted', new GfmDeleted()); 143 $this->P->parse('foo ~~~bar~~~ baz'); 144 145 $modes = array_column($this->H->calls, 0); 146 $this->assertNotContains('deleted_open', $modes, 147 'Run of 3+ tildes must not trigger strikethrough'); 148 } 149 150 public function testMultilineStrikethrough() 151 { 152 $this->P->addMode('gfm_deleted', new GfmDeleted()); 153 $this->P->parse("~~line\nline\nline~~"); 154 155 $calls = [ 156 ['document_start', []], 157 ['p_open', []], 158 ['cdata', ["\n"]], 159 ['deleted_open', []], 160 ['cdata', ["line\nline\nline"]], 161 ['deleted_close', []], 162 ['cdata', ['']], 163 ['p_close', []], 164 ['document_end', []], 165 ]; 166 $this->assertCalls($calls, $this->H->calls); 167 } 168 169 public function testDoesNotSpanParagraphBoundary() 170 { 171 // An unclosed `~~` followed by a blank line must stay literal. 172 // Mirrors GFM spec example 492. 173 $this->P->addMode('gfm_deleted', new GfmDeleted()); 174 $this->P->parse("This ~~has a\n\nnew paragraph~~."); 175 176 $modes = array_column($this->H->calls, 0); 177 $this->assertNotContains('deleted_open', $modes, 178 'GfmDeleted must not open when the closing `~~` is past a blank line'); 179 } 180 181 public function testAllowsSingleNewline() 182 { 183 $this->P->addMode('gfm_deleted', new GfmDeleted()); 184 $this->P->parse("~~open\nclose~~"); 185 186 $modes = array_column($this->H->calls, 0); 187 $this->assertContains('deleted_open', $modes, 188 'GfmDeleted must still match across a single newline'); 189 } 190 191 public function testTrailingWhitespaceBeforeCloserDoesNotStrike() 192 { 193 $this->P->addMode('gfm_deleted', new GfmDeleted()); 194 $this->P->parse('~~foo bar ~~'); 195 196 $modes = array_column($this->H->calls, 0); 197 $this->assertNotContains('deleted_open', $modes, 198 'Closer preceded by whitespace must not match'); 199 } 200 201 public function testSortValue() 202 { 203 $mode = new GfmDeleted(); 204 $this->assertSame(130, $mode->getSort()); 205 } 206 207 public function testRejectedOpenerBeforeValidSpanStaysLiteral() 208 { 209 $this->P->addMode('gfm_deleted', new GfmDeleted()); 210 $this->P->parse('~~ foo ~~bar~~'); 211 $this->assertContains('deleted_open', array_column($this->H->calls, 0)); 212 $this->assertStringContainsString('~~ foo ', $this->H->calls[2][1][0]); 213 } 214 215 public function testVeryLongSpanIsRecognized() 216 { 217 $body = trim(str_repeat('some words ', 7000)); // ~77KB 218 $this->P->addMode('gfm_deleted', new GfmDeleted()); 219 $this->P->parse('~~' . $body . '~~'); 220 221 $calls = array_map(fn($call) => [$call[0], $call[1]], $this->H->calls); 222 $this->assertContains(['deleted_open', []], $calls); 223 $this->assertContains(['cdata', [$body]], $calls); 224 } 225} 226