1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ModeRegistry; 6use dokuwiki\Parsing\ParserMode\GfmEmphasis; 7 8/** 9 * Tests for the GFM asterisk emphasis mode (`*text*`). 10 * 11 * Mirrors the existing FormattingTest pattern: one mode loaded in isolation, 12 * assertions against handler instruction sequences. 13 * 14 * The setUp flips ModeRegistry to `markdown` syntax so that the Base mode 15 * (constructed by the Parser) recognizes `gfm_emphasis` as an allowed nested 16 * mode. Without this, Base's allowedModes would be the dokuwiki set and would 17 * silently drop our entry pattern. 18 */ 19class GfmEmphasisTest extends ParserTestBase 20{ 21 public function setUp(): void 22 { 23 parent::setUp(); 24 global $conf; 25 $conf['syntax'] = 'markdown'; 26 ModeRegistry::reset(); 27 } 28 29 public function tearDown(): void 30 { 31 ModeRegistry::reset(); 32 parent::tearDown(); 33 } 34 35 function testBasicAsterisk() 36 { 37 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 38 $this->P->parse('Foo *Bar* Baz'); 39 $calls = [ 40 ['document_start', []], 41 ['p_open', []], 42 ['cdata', ["\nFoo "]], 43 ['emphasis_open', []], 44 ['cdata', ['Bar']], 45 ['emphasis_close', []], 46 ['cdata', [' Baz']], 47 ['p_close', []], 48 ['document_end', []], 49 ]; 50 $this->assertCalls($calls, $this->H->calls); 51 } 52 53 function testSingleCharacter() 54 { 55 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 56 $this->P->parse('foo *b* bar'); 57 $calls = [ 58 ['document_start', []], 59 ['p_open', []], 60 ['cdata', ["\nfoo "]], 61 ['emphasis_open', []], 62 ['cdata', ['b']], 63 ['emphasis_close', []], 64 ['cdata', [' bar']], 65 ['p_close', []], 66 ['document_end', []], 67 ]; 68 $this->assertCalls($calls, $this->H->calls); 69 } 70 71 function testMultipleWords() 72 { 73 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 74 $this->P->parse('*three four five*'); 75 $calls = [ 76 ['document_start', []], 77 ['p_open', []], 78 ['cdata', ["\n"]], 79 ['emphasis_open', []], 80 ['cdata', ['three four five']], 81 ['emphasis_close', []], 82 ['cdata', ['']], 83 ['p_close', []], 84 ['document_end', []], 85 ]; 86 $this->assertCalls($calls, $this->H->calls); 87 } 88 89 function testTwoSeparateEmphasisOnOneLine() 90 { 91 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 92 $this->P->parse('*one* and *two*'); 93 $calls = [ 94 ['document_start', []], 95 ['p_open', []], 96 ['cdata', ["\n"]], 97 ['emphasis_open', []], 98 ['cdata', ['one']], 99 ['emphasis_close', []], 100 ['cdata', [' and ']], 101 ['emphasis_open', []], 102 ['cdata', ['two']], 103 ['emphasis_close', []], 104 ['cdata', ['']], 105 ['p_close', []], 106 ['document_end', []], 107 ]; 108 $this->assertCalls($calls, $this->H->calls); 109 } 110 111 function testUnmatchedOpenerDoesNotEmphasise() 112 { 113 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 114 $this->P->parse('foo *bar with no closer'); 115 $calls = [ 116 ['document_start', []], 117 ['p_open', []], 118 ['cdata', ["\nfoo *bar with no closer"]], 119 ['p_close', []], 120 ['document_end', []], 121 ]; 122 $this->assertCalls($calls, $this->H->calls); 123 } 124 125 function testOpenerFollowedBySpaceDoesNotEmphasise() 126 { 127 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 128 $this->P->parse('foo * bar* baz'); 129 $calls = [ 130 ['document_start', []], 131 ['p_open', []], 132 ['cdata', ["\nfoo * bar* baz"]], 133 ['p_close', []], 134 ['document_end', []], 135 ]; 136 $this->assertCalls($calls, $this->H->calls); 137 } 138 139 function testEmptyDelimiterDoesNotEmphasise() 140 { 141 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 142 $this->P->parse('foo ** bar'); 143 $calls = [ 144 ['document_start', []], 145 ['p_open', []], 146 ['cdata', ["\nfoo ** bar"]], 147 ['p_close', []], 148 ['document_end', []], 149 ]; 150 $this->assertCalls($calls, $this->H->calls); 151 } 152 153 function testUnderscoreIsNotEmphasised() 154 { 155 // GfmEmphasis handles `*` only — `_` is reserved to avoid the 156 // `__underline__` conflict. See SPEC.md. 157 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 158 $this->P->parse('foo _bar_ baz'); 159 $calls = [ 160 ['document_start', []], 161 ['p_open', []], 162 ['cdata', ["\nfoo _bar_ baz"]], 163 ['p_close', []], 164 ['document_end', []], 165 ]; 166 $this->assertCalls($calls, $this->H->calls); 167 } 168 169 function testMultilineEmphasis() 170 { 171 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 172 $this->P->parse("*line\nline\nline*"); 173 $calls = [ 174 ['document_start', []], 175 ['p_open', []], 176 ['cdata', ["\n"]], 177 ['emphasis_open', []], 178 ['cdata', ["line\nline\nline"]], 179 ['emphasis_close', []], 180 ['cdata', ['']], 181 ['p_close', []], 182 ['document_end', []], 183 ]; 184 $this->assertCalls($calls, $this->H->calls); 185 } 186 187 function testModeNameIsDistinctFromInstructionName() 188 { 189 // The lexer mode is registered as 'gfm_emphasis' (to avoid collision 190 // with DW Emphasis), but instructions are 'emphasis_open/close' 191 // so the existing XHTML renderer emits <em>. 192 $mode = new GfmEmphasis(); 193 $this->assertSame(80, $mode->getSort()); 194 } 195 196 function testDoesNotSpanParagraphBoundary() 197 { 198 // An unclosed `*` followed by a blank line must stay literal — the 199 // entry pattern's lookahead is paragraph-boundary-safe. 200 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 201 $this->P->parse("*open\n\nclose*"); 202 $modes = array_column($this->H->calls, 0); 203 $this->assertNotContains('emphasis_open', $modes, 204 'GfmEmphasis must not open when the closing `*` is past a blank line'); 205 } 206 207 function testAllowsSingleNewline() 208 { 209 // Single newlines are fine inside emphasis (multi-line emphasis). 210 $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 211 $this->P->parse("*open\nclose*"); 212 $modes = array_column($this->H->calls, 0); 213 $this->assertContains('emphasis_open', $modes, 214 'GfmEmphasis must still match across a single newline'); 215 } 216} 217