1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ModeRegistry; 6use dokuwiki\Parsing\ParserMode\GfmDeleted; 7 8/** 9 * Tests for the GFM strikethrough mode (`~~text~~`). 10 */ 11class GfmDeletedTest extends ParserTestBase 12{ 13 public function setUp(): void 14 { 15 parent::setUp(); 16 global $conf; 17 $conf['syntax'] = 'markdown'; 18 ModeRegistry::reset(); 19 } 20 21 public function tearDown(): void 22 { 23 ModeRegistry::reset(); 24 parent::tearDown(); 25 } 26 27 function testBasicStrikethrough() 28 { 29 $this->P->addMode('gfm_deleted', new GfmDeleted()); 30 $this->P->parse('Foo ~~Bar~~ Baz'); 31 $calls = [ 32 ['document_start', []], 33 ['p_open', []], 34 ['cdata', ["\nFoo "]], 35 ['deleted_open', []], 36 ['cdata', ['Bar']], 37 ['deleted_close', []], 38 ['cdata', [' Baz']], 39 ['p_close', []], 40 ['document_end', []], 41 ]; 42 $this->assertCalls($calls, $this->H->calls); 43 } 44 45 function testSingleCharacterBody() 46 { 47 $this->P->addMode('gfm_deleted', new GfmDeleted()); 48 $this->P->parse('foo ~~b~~ bar'); 49 $calls = [ 50 ['document_start', []], 51 ['p_open', []], 52 ['cdata', ["\nfoo "]], 53 ['deleted_open', []], 54 ['cdata', ['b']], 55 ['deleted_close', []], 56 ['cdata', [' bar']], 57 ['p_close', []], 58 ['document_end', []], 59 ]; 60 $this->assertCalls($calls, $this->H->calls); 61 } 62 63 function testMultipleWords() 64 { 65 $this->P->addMode('gfm_deleted', new GfmDeleted()); 66 $this->P->parse('~~three four five~~'); 67 $calls = [ 68 ['document_start', []], 69 ['p_open', []], 70 ['cdata', ["\n"]], 71 ['deleted_open', []], 72 ['cdata', ['three four five']], 73 ['deleted_close', []], 74 ['cdata', ['']], 75 ['p_close', []], 76 ['document_end', []], 77 ]; 78 $this->assertCalls($calls, $this->H->calls); 79 } 80 81 function testTwoSeparateStrikethroughsOnOneLine() 82 { 83 $this->P->addMode('gfm_deleted', new GfmDeleted()); 84 $this->P->parse('~~one~~ and ~~two~~'); 85 $calls = [ 86 ['document_start', []], 87 ['p_open', []], 88 ['cdata', ["\n"]], 89 ['deleted_open', []], 90 ['cdata', ['one']], 91 ['deleted_close', []], 92 ['cdata', [' and ']], 93 ['deleted_open', []], 94 ['cdata', ['two']], 95 ['deleted_close', []], 96 ['cdata', ['']], 97 ['p_close', []], 98 ['document_end', []], 99 ]; 100 $this->assertCalls($calls, $this->H->calls); 101 } 102 103 function testUnmatchedOpenerDoesNotStrike() 104 { 105 $this->P->addMode('gfm_deleted', new GfmDeleted()); 106 $this->P->parse('foo ~~bar with no closer'); 107 $calls = [ 108 ['document_start', []], 109 ['p_open', []], 110 ['cdata', ["\nfoo ~~bar with no closer"]], 111 ['p_close', []], 112 ['document_end', []], 113 ]; 114 $this->assertCalls($calls, $this->H->calls); 115 } 116 117 function testOpenerFollowedBySpaceDoesNotStrike() 118 { 119 $this->P->addMode('gfm_deleted', new GfmDeleted()); 120 $this->P->parse('foo ~~ bar~~ baz'); 121 $calls = [ 122 ['document_start', []], 123 ['p_open', []], 124 ['cdata', ["\nfoo ~~ bar~~ baz"]], 125 ['p_close', []], 126 ['document_end', []], 127 ]; 128 $this->assertCalls($calls, $this->H->calls); 129 } 130 131 function testEmptyDelimiterDoesNotStrike() 132 { 133 $this->P->addMode('gfm_deleted', new GfmDeleted()); 134 $this->P->parse('foo ~~~~ bar'); 135 $modes = array_column($this->H->calls, 0); 136 $this->assertNotContains('deleted_open', $modes, 137 'Empty `~~~~` must stay literal'); 138 } 139 140 function testTripleTildeDoesNotStrike() 141 { 142 // `~~~` is the GFM fenced-code-block marker; strikethrough must not 143 // consume a run of three or more tildes. 144 $this->P->addMode('gfm_deleted', new GfmDeleted()); 145 $this->P->parse('foo ~~~bar~~~ baz'); 146 $modes = array_column($this->H->calls, 0); 147 $this->assertNotContains('deleted_open', $modes, 148 'Run of 3+ tildes must not trigger strikethrough'); 149 } 150 151 function testMultilineStrikethrough() 152 { 153 $this->P->addMode('gfm_deleted', new GfmDeleted()); 154 $this->P->parse("~~line\nline\nline~~"); 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 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 $modes = array_column($this->H->calls, 0); 176 $this->assertNotContains('deleted_open', $modes, 177 'GfmDeleted must not open when the closing `~~` is past a blank line'); 178 } 179 180 function testAllowsSingleNewline() 181 { 182 $this->P->addMode('gfm_deleted', new GfmDeleted()); 183 $this->P->parse("~~open\nclose~~"); 184 $modes = array_column($this->H->calls, 0); 185 $this->assertContains('deleted_open', $modes, 186 'GfmDeleted must still match across a single newline'); 187 } 188 189 function testTrailingWhitespaceBeforeCloserDoesNotStrike() 190 { 191 $this->P->addMode('gfm_deleted', new GfmDeleted()); 192 $this->P->parse('~~foo bar ~~'); 193 $modes = array_column($this->H->calls, 0); 194 $this->assertNotContains('deleted_open', $modes, 195 'Closer preceded by whitespace must not match'); 196 } 197 198 function testSortValue() 199 { 200 $mode = new GfmDeleted(); 201 $this->assertSame(130, $mode->getSort()); 202 } 203} 204