diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilter.java index defef913e..b2e6c25a6 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilter.java @@ -3,6 +3,7 @@ import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; +import com.hubspot.jinjava.interpret.DeferredValueException; import com.hubspot.jinjava.interpret.InvalidArgumentException; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.interpret.TemplateError; @@ -141,7 +142,21 @@ public Object filter(Object var, JinjavaInterpreter interpreter, String... args) killwords = BooleanUtils.toBoolean(args[2]); } - Document dom = Jsoup.parseBodyFragment((String) var); + int numDeferredTokensStart = interpreter.getContext().getDeferredTokens().size(); + + String val; + try ( + JinjavaInterpreter.InterpreterScopeClosable ignored = interpreter.enterScope() + ) { + val = interpreter.renderFlat((String) var); + if ( + interpreter.getContext().getDeferredTokens().size() > numDeferredTokensStart + ) { + throw new DeferredValueException("Deferred in TruncateHtmlFilter"); + } + } + + Document dom = Jsoup.parseBodyFragment(val); ContentTruncatingNodeVisitor visitor = new ContentTruncatingNodeVisitor( length, ends, @@ -171,8 +186,7 @@ private static class ContentTruncatingNodeVisitor implements NodeVisitor { @Override public void head(Node node, int depth) { - if (node instanceof TextNode) { - TextNode text = (TextNode) node; + if (node instanceof TextNode text) { String textContent = text.text(); if (textLen >= maxTextLen) { @@ -193,8 +207,7 @@ public void head(Node node, int depth) { @Override public void tail(Node node, int depth) { - if (node instanceof Element) { - Element el = (Element) node; + if (node instanceof Element el) { if (StringUtils.isBlank(el.text())) { el.addClass("__deleteme"); } diff --git a/src/test/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilterTest.java b/src/test/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilterTest.java index 963bc2e85..ff97e4562 100644 --- a/src/test/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilterTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/filter/TruncateHtmlFilterTest.java @@ -1,12 +1,22 @@ package com.hubspot.jinjava.lib.filter; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import com.hubspot.jinjava.BaseInterpretingTest; +import com.hubspot.jinjava.interpret.Context; +import com.hubspot.jinjava.interpret.DeferredValueException; +import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.lib.tag.eager.DeferredToken; +import com.hubspot.jinjava.tree.parse.ExpressionToken; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; @@ -54,6 +64,59 @@ public void itDoesntChopWordsWhenSpecified() { ); } + @Test + public void itExecutesJinjavaInsideTag() { + assertThat( + filter.filter("{% for i in [1, 2, 3] %}
{{i}}
{% endfor %}", interpreter) + ) + .isEqualTo("
\n 1\n
\n
\n 2\n
\n
\n 3\n
"); + } + + @Test + public void itExecutesJinjavaInsideTagAndTruncates() { + assertThat( + filter.filter( + "{% for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] %}
{{i}}
{% endfor %}", + interpreter, + "10" + ) + ) + .isEqualTo( + "
\n 1\n
\n
\n 2\n
\n
\n 3\n
\n
\n 4\n
\n
\n 5\n
\n" + + "
\n 6\n
\n
\n 7\n
\n
\n 8\n
\n
\n 9\n
\n
\n ...\n
" + ); + } + + @Test + public void itIsolatesJinjavaScopeWhenExecutingCodeInsideTag() { + filter.filter("{% set test = 'hello' %}", interpreter); + assertThat(interpreter.getContext().get("test")).isNull(); + } + + @Test + public void itThrowsDeferredValueExceptionWhenDeferredTokensAreLeft() { + AtomicInteger counter = new AtomicInteger(); + JinjavaInterpreter mockedInterpreter = mock(JinjavaInterpreter.class); + Context mockedContext = mock(Context.class); + when(mockedInterpreter.getContext()).thenReturn(mockedContext); + when(mockedContext.getDeferredTokens()) + .thenAnswer(i -> + counter.getAndIncrement() == 0 + ? Collections.emptySet() + : Collections.singleton( + DeferredToken + .builderFromImage( + "{{ deferred && other }}", + ExpressionToken.class, + interpreter + ) + .build() + ) + ); + assertThatThrownBy(() -> filter.filter("{{ deferred && other }}", mockedInterpreter)) + .isInstanceOf(DeferredValueException.class); + } + @Test public void itTakesKwargs() { String result = (String) filter.filter(