1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ParserMode\Deleted; 6use dokuwiki\Parsing\ParserMode\Emphasis; 7use dokuwiki\Parsing\ParserMode\Monospace; 8use dokuwiki\Parsing\ParserMode\Strong; 9use dokuwiki\Parsing\ParserMode\Subscript; 10use dokuwiki\Parsing\ParserMode\Superscript; 11use dokuwiki\Parsing\ParserMode\Underline; 12 13/** 14 * Tests for the individual formatting modes (bold, italic, underline, etc.) 15 */ 16class FormattingTest extends ParserTestBase 17{ 18 function testStrong() 19 { 20 $this->P->addMode('strong', new Strong()); 21 $this->P->parse('Foo **Bar** Baz'); 22 $calls = [ 23 ['document_start', []], 24 ['p_open', []], 25 ['cdata', ["\nFoo "]], 26 ['strong_open', []], 27 ['cdata', ['Bar']], 28 ['strong_close', []], 29 ['cdata', [' Baz']], 30 ['p_close', []], 31 ['document_end', []], 32 ]; 33 $this->assertCalls($calls, $this->H->calls); 34 } 35 36 function testEmphasis() 37 { 38 $this->P->addMode('emphasis', new Emphasis()); 39 $this->P->parse('Foo //Bar// Baz'); 40 $calls = [ 41 ['document_start', []], 42 ['p_open', []], 43 ['cdata', ["\nFoo "]], 44 ['emphasis_open', []], 45 ['cdata', ['Bar']], 46 ['emphasis_close', []], 47 ['cdata', [' Baz']], 48 ['p_close', []], 49 ['document_end', []], 50 ]; 51 $this->assertCalls($calls, $this->H->calls); 52 } 53 54 function testUnderline() 55 { 56 $this->P->addMode('underline', new Underline()); 57 $this->P->parse('Foo __Bar__ Baz'); 58 $calls = [ 59 ['document_start', []], 60 ['p_open', []], 61 ['cdata', ["\nFoo "]], 62 ['underline_open', []], 63 ['cdata', ['Bar']], 64 ['underline_close', []], 65 ['cdata', [' Baz']], 66 ['p_close', []], 67 ['document_end', []], 68 ]; 69 $this->assertCalls($calls, $this->H->calls); 70 } 71 72 function testMonospace() 73 { 74 $this->P->addMode('monospace', new Monospace()); 75 $this->P->parse("Foo ''Bar'' Baz"); 76 $calls = [ 77 ['document_start', []], 78 ['p_open', []], 79 ['cdata', ["\nFoo "]], 80 ['monospace_open', []], 81 ['cdata', ['Bar']], 82 ['monospace_close', []], 83 ['cdata', [' Baz']], 84 ['p_close', []], 85 ['document_end', []], 86 ]; 87 $this->assertCalls($calls, $this->H->calls); 88 } 89 90 function testSubscript() 91 { 92 $this->P->addMode('subscript', new Subscript()); 93 $this->P->parse('Foo <sub>Bar</sub> Baz'); 94 $calls = [ 95 ['document_start', []], 96 ['p_open', []], 97 ['cdata', ["\nFoo "]], 98 ['subscript_open', []], 99 ['cdata', ['Bar']], 100 ['subscript_close', []], 101 ['cdata', [' Baz']], 102 ['p_close', []], 103 ['document_end', []], 104 ]; 105 $this->assertCalls($calls, $this->H->calls); 106 } 107 108 function testSuperscript() 109 { 110 $this->P->addMode('superscript', new Superscript()); 111 $this->P->parse('Foo <sup>Bar</sup> Baz'); 112 $calls = [ 113 ['document_start', []], 114 ['p_open', []], 115 ['cdata', ["\nFoo "]], 116 ['superscript_open', []], 117 ['cdata', ['Bar']], 118 ['superscript_close', []], 119 ['cdata', [' Baz']], 120 ['p_close', []], 121 ['document_end', []], 122 ]; 123 $this->assertCalls($calls, $this->H->calls); 124 } 125 126 function testDeleted() 127 { 128 $this->P->addMode('deleted', new Deleted()); 129 $this->P->parse('Foo <del>Bar</del> Baz'); 130 $calls = [ 131 ['document_start', []], 132 ['p_open', []], 133 ['cdata', ["\nFoo "]], 134 ['deleted_open', []], 135 ['cdata', ['Bar']], 136 ['deleted_close', []], 137 ['cdata', [' Baz']], 138 ['p_close', []], 139 ['document_end', []], 140 ]; 141 $this->assertCalls($calls, $this->H->calls); 142 } 143 144 function testNesting() 145 { 146 $this->P->addMode('strong', new Strong()); 147 $this->P->addMode('emphasis', new Emphasis()); 148 $this->P->parse('Foo **bold //and italic// text** Bar'); 149 $calls = [ 150 ['document_start', []], 151 ['p_open', []], 152 ['cdata', ["\nFoo "]], 153 ['strong_open', []], 154 ['cdata', ['bold ']], 155 ['emphasis_open', []], 156 ['cdata', ['and italic']], 157 ['emphasis_close', []], 158 ['cdata', [' text']], 159 ['strong_close', []], 160 ['cdata', [' Bar']], 161 ['p_close', []], 162 ['document_end', []], 163 ]; 164 $this->assertCalls($calls, $this->H->calls); 165 } 166 167 function testNoSelfNesting() 168 { 169 $this->P->addMode('strong', new Strong()); 170 $this->P->parse('Foo **bold **not nested** end** Bar'); 171 $calls = [ 172 ['document_start', []], 173 ['p_open', []], 174 ['cdata', ["\nFoo "]], 175 ['strong_open', []], 176 ['cdata', ['bold ']], 177 ['strong_close', []], 178 ['cdata', ['not nested']], 179 ['strong_open', []], 180 ['cdata', [' end']], 181 ['strong_close', []], 182 ['cdata', [' Bar']], 183 ['p_close', []], 184 ['document_end', []], 185 ]; 186 $this->assertCalls($calls, $this->H->calls); 187 } 188 189 /** 190 * @dataProvider provideParagraphBoundaryModes 191 * 192 * Formatting delimiters must not match across a blank line. An unclosed 193 * delimiter followed by a blank line and then an unrelated delimiter 194 * further down must stay literal — otherwise the lexer greedily swallows 195 * the paragraph break. 196 */ 197 function testDelimitersDoNotSpanParagraphBoundary( 198 string $modeName, 199 $mode, 200 string $input 201 ) { 202 $this->P->addMode($modeName, $mode); 203 $this->P->parse($input); 204 foreach ($this->H->calls as $call) { 205 $this->assertNotSame( 206 $modeName . '_open', 207 $call[0], 208 "Mode '$modeName' must not open across a blank line in: " . json_encode($input) 209 ); 210 } 211 } 212 213 public static function provideParagraphBoundaryModes(): array 214 { 215 return [ 216 'strong' => ['strong', new Strong(), "**open\n\nclose**"], 217 'emphasis' => ['emphasis', new Emphasis(), "//open\n\nclose//"], 218 'underline' => ['underline', new Underline(), "__open\n\nclose__"], 219 'monospace' => ['monospace', new Monospace(), "''open\n\nclose''"], 220 'subscript' => ['subscript', new Subscript(), "<sub>open\n\nclose</sub>"], 221 'superscript' => ['superscript', new Superscript(), "<sup>open\n\nclose</sup>"], 222 'deleted' => ['deleted', new Deleted(), "<del>open\n\nclose</del>"], 223 ]; 224 } 225 226 /** 227 * A single newline inside a delimiter pair is still valid (multi-line 228 * formatting), only blank lines end it. 229 */ 230 function testStrongAllowsSingleNewline() 231 { 232 $this->P->addMode('strong', new Strong()); 233 $this->P->parse("**open\nclose**"); 234 $this->assertContains( 235 'strong_open', 236 array_column($this->H->calls, 0), 237 'Strong must still match across a single newline' 238 ); 239 } 240} 241