22
33import static org .assertj .core .api .Assertions .assertThat ;
44import static org .assertj .core .api .Assertions .assertThatThrownBy ;
5-
5+ import java .util .HashMap ;
6+ import org .junit .Before ;
7+ import org .junit .Test ;
68import com .google .common .collect .ImmutableMap ;
79import com .google .common .collect .Lists ;
810import com .hubspot .jinjava .BaseJinjavaTest ;
911import com .hubspot .jinjava .Jinjava ;
1012import com .hubspot .jinjava .JinjavaConfig ;
1113import com .hubspot .jinjava .lib .filter .JoinFilterTest .User ;
12- import java .util .HashMap ;
13- import org .junit .Before ;
14- import org .junit .Test ;
1514
1615public class StringTokenScannerSymbolsTest {
1716
@@ -238,7 +237,86 @@ public void defaultBuilderBehavesLikeDefaultSymbols() {
238237 .isEqualTo (defaultJinjava .render (template , ctx ));
239238 }
240239
241- // ── Builder validation ─────────────────────────────────────────────────────
240+ // ── trimBlocks and lstripBlocks ────────────────────────────────────────────
241+ //
242+ // trimBlocks is handled in TokenScanner.emitStringToken(): when a TagToken or
243+ // NoteToken is emitted and trimBlocks=true, the immediately following newline
244+ // is consumed. This is equally true in the string-based path.
245+ //
246+ // lstripBlocks is handled in TreeParser, which operates on the token stream
247+ // produced by TokenScanner. It strips leading horizontal whitespace from any
248+ // TextNode that immediately precedes a TagNode. Since TreeParser is path-agnostic,
249+ // lstripBlocks works identically for both char-based and string-based scanning.
250+
251+ @ Test
252+ public void itRespectsTrimBlocksWithAngleSymbols () {
253+ Jinjava j = new Jinjava (
254+ BaseJinjavaTest
255+ .newConfigBuilder ()
256+ .withTokenScannerSymbols (ANGLE_SYMBOLS )
257+ .withTrimBlocks (true )
258+ .build ()
259+ );
260+ // Without trimBlocks the newline after <% if show %> would appear in output.
261+ // With trimBlocks=true it is consumed by the scanner, so output is "hello".
262+ String result = j .render (
263+ "<% if show %>\n hello\n <% endif %>" ,
264+ ImmutableMap .of ("show" , true )
265+ );
266+ assertThat (result ).isEqualTo ("hello\n " );
267+ }
268+
269+ @ Test
270+ public void itRespectsTrimBlocksWithLatexSymbols () {
271+ Jinjava j = new Jinjava (
272+ BaseJinjavaTest
273+ .newConfigBuilder ()
274+ .withTokenScannerSymbols (LATEX_SYMBOLS )
275+ .withTrimBlocks (true )
276+ .build ()
277+ );
278+ String result = j .render (
279+ "\\ BLOCK{ if show }\n hello\n \\ BLOCK{ endif }" ,
280+ ImmutableMap .of ("show" , true )
281+ );
282+ assertThat (result ).isEqualTo ("hello\n " );
283+ }
284+
285+ @ Test
286+ public void itRespectsLstripBlocksWithAngleSymbols () {
287+ Jinjava j = new Jinjava (
288+ BaseJinjavaTest
289+ .newConfigBuilder ()
290+ .withTokenScannerSymbols (ANGLE_SYMBOLS )
291+ .withLstripBlocks (true )
292+ .withTrimBlocks (true )
293+ .build ()
294+ );
295+ // Leading spaces before the tag are stripped by lstripBlocks (TreeParser).
296+ // The newline after the tag is consumed by trimBlocks (TokenScanner).
297+ String result = j .render (
298+ " <% if show %>\n hello\n <% endif %>" ,
299+ ImmutableMap .of ("show" , true )
300+ );
301+ assertThat (result ).isEqualTo ("hello\n " );
302+ }
303+
304+ @ Test
305+ public void itRespectsLstripBlocksWithLatexSymbols () {
306+ Jinjava j = new Jinjava (
307+ BaseJinjavaTest
308+ .newConfigBuilder ()
309+ .withTokenScannerSymbols (LATEX_SYMBOLS )
310+ .withLstripBlocks (true )
311+ .withTrimBlocks (true )
312+ .build ()
313+ );
314+ String result = j .render (
315+ " \\ BLOCK{ if show }\n hello\n \\ BLOCK{ endif }" ,
316+ ImmutableMap .of ("show" , true )
317+ );
318+ assertThat (result ).isEqualTo ("hello\n " );
319+ }
242320
243321 @ Test
244322 public void builderRejectsEmptyDelimiter () {
@@ -269,6 +347,27 @@ public void itRendersLineStatementPrefix() {
269347 assertThat (j .render (template , ImmutableMap .of ("show" , false ))).isEqualTo ("" );
270348 }
271349
350+ @ Test
351+ public void itRendersLineStatementPrefixWithWhitespaceControl () {
352+ Jinjava j = new Jinjava (
353+ BaseJinjavaTest
354+ .newConfigBuilder ()
355+ .withTokenScannerSymbols (
356+ StringTokenScannerSymbols .builder ().withLineStatementPrefix ("%%" ).build ()
357+ )
358+ .withTrimBlocks (true )
359+ .withLstripBlocks (true )
360+ .build ()
361+ );
362+ // "%%- for" strips the newline before the line (leftTrim).
363+ // trimBlocks consumes the newline after each tag line.
364+ // Expected: the \n after {| is stripped, c| repeated col_num times, each
365+ // followed by \n (from the body line), with the \n after c| stripped by
366+ // the leftTrim on %%- endfor.
367+ String template = "before|\n %%- for _ in range(3)\n c|\n %%- endfor\n after" ;
368+ assertThat (j .render (template , ImmutableMap .of ())).isEqualTo ("before|c|c|c|after" );
369+ }
370+
272371 @ Test
273372 public void itRendersLineStatementPrefixWithLeadingWhitespace () {
274373 Jinjava j = jinjavaWith (
@@ -298,23 +397,66 @@ public void itRendersLineStatementMixedWithBlockDelimiters() {
298397 }
299398
300399 // ── Line comment prefix ────────────────────────────────────────────────────
400+ //
401+ // Semantics:
402+ // %# (plain): comment content stripped, trailing \n KEPT → blank line where comment was
403+ // %#- (trim): comment content AND trailing \n stripped → no blank line
404+ // Neither form affects the newline that ended the preceding line.
301405
302406 @ Test
303- public void itStripsLineCommentPrefix () {
407+ public void itStripsLineCommentPrefixLeavingBlankLine () {
304408 Jinjava j = jinjavaWith (
305409 StringTokenScannerSymbols .builder ().withLineCommentPrefix ("%#" ).build ()
306410 );
411+ // %# keeps its trailing \n → "before\n" + "\n" + "after" = "before\n\nafter"
307412 String template = "before\n %# this whole line is a comment\n after" ;
308- assertThat (j .render (template , new HashMap <>())).isEqualTo ("before\n after" );
413+ assertThat (j .render (template , new HashMap <>())).isEqualTo ("before\n \ n after" );
309414 }
310415
311416 @ Test
312417 public void itStripsLineCommentWithLeadingWhitespace () {
313418 Jinjava j = jinjavaWith (
314419 StringTokenScannerSymbols .builder ().withLineCommentPrefix ("%#" ).build ()
315420 );
421+ // Indentation before %# is stripped, trailing \n is kept → still a blank line
316422 String template = "before\n %# indented comment\n after" ;
317- assertThat (j .render (template , new HashMap <>())).isEqualTo ("before\n after" );
423+ assertThat (j .render (template , new HashMap <>())).isEqualTo ("before\n \n after" );
424+ }
425+
426+ @ Test
427+ public void itStripsLineCommentWithTrimModifier () {
428+ Jinjava j = jinjavaWith (
429+ StringTokenScannerSymbols .builder ().withLineCommentPrefix ("%#" ).build ()
430+ );
431+ // %# keeps trailing \n → blank line: "before\n\nafter"
432+ assertThat (j .render ("before\n %# comment\n after" , new HashMap <>()))
433+ .isEqualTo ("before\n \n after" );
434+ // %#- strips trailing \n → no blank line: "before\nafter"
435+ assertThat (j .render ("before\n %#- comment\n after" , new HashMap <>()))
436+ .isEqualTo ("before\n after" );
437+ }
438+
439+ @ Test
440+ public void itStripsLineCommentWithoutLeavingBlankLine () {
441+ // %#- strips both content and trailing \n → no blank line.
442+ // "\\begin{document}\n" (preceding \n kept) + "\\section*{...}" (directly)
443+ Jinjava j = new Jinjava (
444+ BaseJinjavaTest
445+ .newConfigBuilder ()
446+ .withTokenScannerSymbols (
447+ StringTokenScannerSymbols
448+ .builder ()
449+ .withVariableStartString ("\\ VAR{" )
450+ .withVariableEndString ("}" )
451+ .withLineCommentPrefix ("%#" )
452+ .build ()
453+ )
454+ .build ()
455+ );
456+ String template =
457+ "\\ begin{document}\n %#-\\ VAR{reportHeader}\n \\ section*{\\ VAR{title}}" ;
458+ String result = j .render (template , ImmutableMap .of ("title" , "My Report" ));
459+ assertThat (result ).isEqualTo ("\\ begin{document}\n \\ section*{My Report}" );
318460 }
319461
320462 @ Test
@@ -333,7 +475,9 @@ public void itHandlesBothLinePrefixesTogether() {
333475 .build ()
334476 );
335477 String template = "%# this is stripped\n %% set x = 7\n << x >>" ;
336- assertThat (j .render (template , new HashMap <>())).isEqualTo ("7" );
478+ // %# keeps its trailing \n → blank line, then %% set produces nothing,
479+ // then << x >> renders as 7. Result: "\n7"
480+ assertThat (j .render (template , new HashMap <>())).isEqualTo ("\n 7" );
337481 }
338482
339483 // ── Helper ────────────────────────────────────────────────────────────────
0 commit comments