@@ -2033,4 +2033,78 @@ public function testShortcutReferenceLink(): void
20332033
20342034 $ this ->assertStringContainsString ('href="https://example.com" ' , $ result );
20352035 }
2036+
2037+ public function testUnclosedLinkEmphasisBoundary (): void
2038+ {
2039+ // Emphasis should NOT cross [text]( boundary when link is unclosed
2040+ $ djot = '[x_y](x_ ' ;
2041+ $ result = $ this ->converter ->convert ($ djot );
2042+
2043+ // Should be literal text, not emphasis
2044+ $ this ->assertSame ("<p>[x_y](x_</p> \n" , $ result );
2045+ }
2046+
2047+ public function testUnclosedLinkWithValidEmphasis (): void
2048+ {
2049+ // Emphasis entirely within (url) part should still work
2050+ $ djot = "[unclosed](hello *a \nb* " ;
2051+ $ result = $ this ->converter ->convert ($ djot );
2052+
2053+ // The *a\nb* is entirely in the url part, so emphasis applies
2054+ $ this ->assertStringContainsString ('<strong>a ' , $ result );
2055+ $ this ->assertStringContainsString ('b</strong> ' , $ result );
2056+ }
2057+
2058+ public function testReferenceDefinitionAttributes (): void
2059+ {
2060+ // Attributes before reference definition apply to the link
2061+ $ djot = "{title=foo} \n[ref]: /url \n\n[ref][] " ;
2062+ $ result = $ this ->converter ->convert ($ djot );
2063+
2064+ $ this ->assertStringContainsString ('title="foo" ' , $ result );
2065+ $ this ->assertStringContainsString ('href="/url" ' , $ result );
2066+ // Attributes should be on the link, not the paragraph
2067+ $ this ->assertStringNotContainsString ('<p title= ' , $ result );
2068+ }
2069+
2070+ public function testReferenceDefinitionAttributeOverride (): void
2071+ {
2072+ // Inline attributes override definition attributes
2073+ $ djot = "{title=foo} \n[ref]: /url \n\n[ref][]{title=bar} " ;
2074+ $ result = $ this ->converter ->convert ($ djot );
2075+
2076+ $ this ->assertStringContainsString ('title="bar" ' , $ result );
2077+ $ this ->assertStringNotContainsString ('title="foo" ' , $ result );
2078+ }
2079+
2080+ public function testAttributeOrderIdClassFirst (): void
2081+ {
2082+ // Attributes should be ordered: id first, class second, then others
2083+ $ djot = 'hi{#myid .myclass key="value"} ' ;
2084+ $ result = $ this ->converter ->convert ($ djot );
2085+
2086+ // Check that id comes before class, and class comes before key
2087+ $ this ->assertMatchesRegularExpression ('/id="myid".*class="myclass".*key="value"/ ' , $ result );
2088+ }
2089+
2090+ public function testUnclosedBraceParagraphContinuation (): void
2091+ {
2092+ // Unclosed { means next line is continuation, not new block
2093+ $ djot = "text{a=x \n# not-a-heading " ;
2094+ $ result = $ this ->converter ->convert ($ djot );
2095+
2096+ // Should be single paragraph, not paragraph + heading
2097+ $ this ->assertSame ("<p>text{a=x \n# not-a-heading</p> \n" , $ result );
2098+ $ this ->assertStringNotContainsString ('<h1 ' , $ result );
2099+ }
2100+
2101+ public function testUnclosedBraceAtStartOfLine (): void
2102+ {
2103+ // Unclosed { at start of line also continues
2104+ $ djot = "{a=x \n# not-a-heading " ;
2105+ $ result = $ this ->converter ->convert ($ djot );
2106+
2107+ $ this ->assertSame ("<p>{a=x \n# not-a-heading</p> \n" , $ result );
2108+ $ this ->assertStringNotContainsString ('<h1 ' , $ result );
2109+ }
20362110}
0 commit comments