1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ParserMode\GfmBacktickDouble; 6 7/** 8 * Tests for the GFM double-backtick code-span mode. 9 * 10 * The whole point of this length is being able to embed a lone 11 * backtick in inline code — input ``foo`bar`` renders as 12 * <code>foo`bar</code>. Combined with the edge-space strip rule, 13 * the boundaries can hold backticks too: input `` `foo` `` 14 * renders as <code>`foo`</code>. 15 */ 16class GfmBacktickDoubleTest extends ParserTestBase 17{ 18 public function setUp(): void 19 { 20 parent::setUp(); 21 $this->setSyntax('md'); 22 } 23 24 function testBasicSpan() 25 { 26 $this->P->addMode('gfm_backtick_double', new GfmBacktickDouble()); 27 $this->P->parse('foo ``bar`` baz'); 28 $calls = [ 29 ['document_start', []], 30 ['p_open', []], 31 ['cdata', ["\nfoo "]], 32 ['monospace_open', []], 33 ['unformatted', ['bar']], 34 ['monospace_close', []], 35 ['cdata', [' baz']], 36 ['p_close', []], 37 ['document_end', []], 38 ]; 39 $this->assertCalls($calls, $this->H->calls); 40 } 41 42 function testAllowsInteriorSingleBacktick() 43 { 44 // GFM example 349. Input ``foo`bar`` — a lone backtick in the 45 // body cannot be a valid n=2 closer, so it stays as content. 46 $this->P->addMode('gfm_backtick_double', new GfmBacktickDouble()); 47 $this->P->parse('``foo`bar``'); 48 $calls = [ 49 ['document_start', []], 50 ['p_open', []], 51 ['cdata', ["\n"]], 52 ['monospace_open', []], 53 ['unformatted', ['foo`bar']], 54 ['monospace_close', []], 55 ['cdata', ['']], 56 ['p_close', []], 57 ['document_end', []], 58 ]; 59 $this->assertCalls($calls, $this->H->calls); 60 } 61 62 function testStripsEdgeSpaces() 63 { 64 // GFM example 339. Input `` foo ` bar `` — one leading and one 65 // trailing space stripped; the interior lone backtick stays. 66 $this->P->addMode('gfm_backtick_double', new GfmBacktickDouble()); 67 $this->P->parse('`` foo ` bar ``'); 68 $calls = [ 69 ['document_start', []], 70 ['p_open', []], 71 ['cdata', ["\n"]], 72 ['monospace_open', []], 73 ['unformatted', ['foo ` bar']], 74 ['monospace_close', []], 75 ['cdata', ['']], 76 ['p_close', []], 77 ['document_end', []], 78 ]; 79 $this->assertCalls($calls, $this->H->calls); 80 } 81 82 function testConvertsNewlinesToSpaces() 83 { 84 // GFM example 345: newlines inside a span become single spaces, 85 // then edge-space stripping applies to the normalized body. 86 $this->P->addMode('gfm_backtick_double', new GfmBacktickDouble()); 87 $this->P->parse("``\nfoo\nbar \nbaz\n``"); 88 $modes = array_column($this->H->calls, 0); 89 $this->assertContains('monospace_open', $modes); 90 91 $unformatted = array_values(array_filter( 92 $this->H->calls, 93 static fn($c) => $c[0] === 'unformatted' 94 )); 95 $this->assertCount(1, $unformatted); 96 $this->assertSame('foo bar baz', $unformatted[0][1][0]); 97 } 98 99 function testAllWhitespaceBodyIsPreserved() 100 { 101 $this->P->addMode('gfm_backtick_double', new GfmBacktickDouble()); 102 $this->P->parse('a `` `` b'); 103 $calls = [ 104 ['document_start', []], 105 ['p_open', []], 106 ['cdata', ["\na "]], 107 ['monospace_open', []], 108 ['unformatted', [' ']], 109 ['monospace_close', []], 110 ['cdata', [' b']], 111 ['p_close', []], 112 ['document_end', []], 113 ]; 114 $this->assertCalls($calls, $this->H->calls); 115 } 116 117 function testEmptyDelimiterDoesNotMatch() 118 { 119 // A run of four backticks — the length-boundary guards reject it 120 // as an n=2 opener followed immediately by an n=2 closer with 121 // empty body. 122 $this->P->addMode('gfm_backtick_double', new GfmBacktickDouble()); 123 $this->P->parse('foo ```` bar'); 124 $modes = array_column($this->H->calls, 0); 125 $this->assertNotContains('monospace_open', $modes, 126 'Run of 4 backticks must stay literal'); 127 } 128 129 function testSortValue() 130 { 131 // Shares the n=1 sort — the length-boundary guards on both modes 132 // mean they never compete for the same input anyway. 133 $mode = new GfmBacktickDouble(); 134 $this->assertSame(165, $mode->getSort()); 135 } 136} 137