1515import org .junit .jupiter .api .Test ;
1616
1717public class RenderablesTest {
18-
18+
19+ private static Renderable snippet (String content ) {
20+ return Renderables .inlineSnippet (content );
21+ }
22+
23+ private static String snippetMD (String content ) {
24+ return snippet (content ).toMarkdown ();
25+ }
26+
27+ @ Test
28+ void inlineSnippetRendersAsHtmlCodeElementWithEntityEscaping () {
29+ assertThat (snippet ("a<b>&c" ).toHtml ()).isEqualTo ("<code>a<b>&c</code>" );
30+ }
31+
32+ @ Test
33+ void inlineSnippetOfNullRendersAsHtmlLiteralNullTooForConsistencyWithMarkdown () {
34+ assertThat (snippet (null ).toHtml ()).isEqualTo ("<code>null</code>" );
35+ }
36+
1937 @ Test
2038 void escapeParenthesisForMardownLink () {
2139 Renderable r = Renderables .link ("my-link-with-parenthesis" , "https://foo.com/index(1).html" );
@@ -24,4 +42,149 @@ void escapeParenthesisForMardownLink() {
2442 assertThat (sb .toString ()).isEqualTo ("[my-link-with-parenthesis](https://foo.com/index%281%29.html)" );
2543 }
2644
45+ @ Test
46+ void escapeBracketsAndBackslashForMarkdownLinkText () {
47+ Renderable r = Renderables .link ("weird]name\\ with[brackets" , "https://foo.com" );
48+ StringBuilder sb = new StringBuilder ();
49+ r .renderAsMarkdown (sb );
50+ assertThat (sb .toString ()).isEqualTo ("[weird\\ ]name\\ \\ with\\ [brackets](https://foo.com)" );
51+ }
52+
53+ @ Test
54+ void escapeLoneBackslashNotAdjacentToABracketInLinkText () {
55+ // A single '\' between two plain characters: CommonMark only treats "\" followed by
56+ // ASCII punctuation as an escape, so a lone backslash must still be doubled or a
57+ // compliant parser would otherwise just print it literally anyway — doubling it here
58+ // keeps the escaping logic uniform regardless of what follows the backslash.
59+ Renderable r = Renderables .link ("a\\ b" , "https://foo.com" );
60+ StringBuilder sb = new StringBuilder ();
61+ r .renderAsMarkdown (sb );
62+ assertThat (sb .toString ()).isEqualTo ("[a\\ \\ b](https://foo.com)" );
63+ }
64+
65+ @ Test
66+ void escapeIsAppliedUniformlyEvenWhenInputAlreadyContainsABackslashBracketPair () {
67+ // Guards the escaping ORDER: backslash must be escaped before '[' / ']', otherwise an
68+ // attacker-supplied "\]" could ride through as what looks like an already-escaped
69+ // bracket and end up under-escaped. Build inputs/outputs via explicit char appends
70+ // instead of string literals so the backslash counts can't be miscounted by eye.
71+ String input = new StringBuilder ().append ('\\' ).append (']' ).toString (); // real chars: \ ]
72+ Renderable r = Renderables .link (input , "https://foo.com" );
73+ StringBuilder sb = new StringBuilder ();
74+ r .renderAsMarkdown (sb );
75+ String expectedLabel = new StringBuilder ().append ('\\' ).append ('\\' ).append ('\\' ).append (']' ).toString (); // real chars: \ \ \ ]
76+ assertThat (sb .toString ()).isEqualTo ("[" + expectedLabel + "](https://foo.com)" );
77+ }
78+
79+ @ Test
80+ void escapeAppliesToBalancedBracketsToo () {
81+ // CommonMark link text may legally contain balanced brackets unescaped, but escaping
82+ // them anyway is harmless (an escaped bracket renders as the literal character) and
83+ // avoids having to reason about balance for attacker-controlled content.
84+ Renderable r = Renderables .link ("[nested]" , "https://foo.com" );
85+ StringBuilder sb = new StringBuilder ();
86+ r .renderAsMarkdown (sb );
87+ assertThat (sb .toString ()).isEqualTo ("[\\ [nested\\ ]](https://foo.com)" );
88+ }
89+
90+ @ Test
91+ void inlineSnippetWithoutBacktick () {
92+ assertThat (snippetMD ("plain-text" )).isEqualTo ("`plain-text`" );
93+ }
94+
95+ @ Test
96+ void inlineSnippetWithEmbeddedSingleBacktick () {
97+ assertThat (snippetMD ("has`backtick" )).isEqualTo ("``has`backtick``" );
98+ }
99+
100+ @ Test
101+ void inlineSnippetWithLongerEmbeddedBacktickRun () {
102+ assertThat (snippetMD ("has``double" )).isEqualTo ("```has``double```" );
103+ }
104+
105+ @ Test
106+ void inlineSnippetFenceScalesToLongestEmbeddedBacktickRun () {
107+ // A run of 3 backticks in the content needs a 4-backtick fence, not just "one more
108+ // than the shortest run" — this pins the fence length to maxRun + 1 in general, not
109+ // just for the 1- and 2-backtick cases already covered above.
110+ assertThat (snippetMD ("a```b" )).isEqualTo ("````a```b````" );
111+ }
112+
113+ @ Test
114+ void inlineSnippetFenceSizedByLongestOfSeveralDistinctRuns () {
115+ // Runs of length 1, 2, then 3 appear in the content; the fence must be sized off the
116+ // longest (3), not the first or the last one encountered.
117+ assertThat (snippetMD ("a`b``c```d" )).isEqualTo ("````a`b``c```d````" );
118+ }
119+
120+ @ Test
121+ void inlineSnippetOfSingleBacktickCharacter () {
122+ // The canonical CommonMark example: content that is exactly one backtick needs a
123+ // 2-backtick fence plus padding, since the content both starts and ends with '`'.
124+ assertThat (snippetMD ("`" )).isEqualTo ("`` ` ``" );
125+ }
126+
127+ @ Test
128+ void inlineSnippetOfContentThatIsEntirelyBackticks () {
129+ assertThat (snippetMD ("``" )).isEqualTo ("``` `` ```" );
130+ }
131+
132+ @ Test
133+ void inlineSnippetPadsWhenContentStartsOrEndsWithBacktick () {
134+ assertThat (snippetMD ("`leading" )).isEqualTo ("`` `leading ``" );
135+ assertThat (snippetMD ("trailing`" )).isEqualTo ("`` trailing` ``" );
136+ }
137+
138+ @ Test
139+ void inlineSnippetPadsBothStartAndEndBacktickBoundary () {
140+ assertThat (snippetMD ("`both`" )).isEqualTo ("`` `both` ``" );
141+ }
142+
143+ @ Test
144+ void inlineSnippetOfEmptyString () {
145+ // An empty code span can't be represented in markdown; omit it entirely
146+ // rather than rendering literal spaces (as a naive "` `" wrapping would).
147+ assertThat (snippetMD ("" )).isEqualTo ("" );
148+ }
149+
150+ @ Test
151+ void inlineSnippetOfNull () {
152+ assertThat (snippetMD (null )).isEqualTo ("`null`" );
153+ }
154+
155+ @ Test
156+ void inlineSnippetPadsWhenContentStartsAndEndsWithSpaceButIsNotAllSpaces () {
157+ // Without the extra pad space, CommonMark's own space-stripping rule would
158+ // turn "` a `" into "a", losing the leading/trailing space from the original content.
159+ assertThat (snippetMD (" a " )).isEqualTo ("` a `" );
160+ }
161+
162+ @ Test
163+ void inlineSnippetPadIsIndependentOfHowManyBoundarySpacesAreAlreadyPresent () {
164+ // The parser only ever strips ONE space from each side, no matter how many are there,
165+ // so exactly one pad space is enough to protect a 2-space boundary too.
166+ assertThat (snippetMD (" a " )).isEqualTo ("`" + " " + "a" + " " + "`" );
167+ }
168+
169+ @ Test
170+ void inlineSnippetDoesNotPadWhenOnlyOneSideHasABoundarySpace () {
171+ // CommonMark's strip rule requires the content to BOTH start and end with a space;
172+ // a space on only one side is left completely alone by the parser, so padding it
173+ // would incorrectly add a space that was never there in the original content.
174+ assertThat (snippetMD (" abc" )).isEqualTo ("` abc`" );
175+ assertThat (snippetMD ("abc " )).isEqualTo ("`abc `" );
176+ }
177+
178+ @ Test
179+ void inlineSnippetDoesNotPadContentThatIsEntirelySpaces () {
180+ // CommonMark's space-stripping rule only applies when the content isn't all spaces,
181+ // so no extra padding is needed to preserve an all-space payload.
182+ assertThat (snippetMD (" " )).isEqualTo ("` `" );
183+ }
184+
185+ @ Test
186+ void inlineSnippetOfSingleSpaceIsTreatedAsAllSpaces () {
187+ assertThat (snippetMD (" " )).isEqualTo ("` `" );
188+ }
189+
27190}
0 commit comments