package com.hubspot.jinjava.lib.tag; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.io.Resources; import com.hubspot.jinjava.Jinjava; import com.hubspot.jinjava.JinjavaConfig; import com.hubspot.jinjava.interpret.Context; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.lib.fn.MacroFunction; import com.hubspot.jinjava.tree.Node; import com.hubspot.jinjava.tree.TagNode; import com.hubspot.jinjava.tree.TreeParser; public class MacroTagTest { Context context; JinjavaInterpreter interpreter; @Before public void setup() { interpreter = new Jinjava().newInterpreter(); JinjavaInterpreter.pushCurrent(interpreter); context = interpreter.getContext(); } @After public void cleanup() { JinjavaInterpreter.popCurrent(); } @Test public void testSimpleFn() { TagNode t = fixture("simple"); assertThat(t.render(interpreter).getValue()).isEmpty(); MacroFunction fn = (MacroFunction) interpreter.resolveObject("__macros__.getPath", -1); assertThat(fn.getName()).isEqualTo("getPath"); assertThat(fn.getArguments()).isEmpty(); assertThat(fn.isCaller()).isFalse(); assertThat(fn.isCatchKwargs()).isFalse(); assertThat(fn.isCatchVarargs()).isFalse(); context.put("myname", "jared"); assertThat(snippet("{{ getPath() }}").render(interpreter).getValue().trim()).isEqualTo("Hello jared"); } @Test public void testFnWithArgs() { TagNode t = fixture("with-args"); assertThat(t.render(interpreter).getValue()).isEmpty(); MacroFunction fn = (MacroFunction) interpreter.resolveObject("__macros__.section_link", -1); assertThat(fn.getName()).isEqualTo("section_link"); assertThat(fn.getArguments()).containsExactly("link", "text"); assertThat(snippet("{{section_link('mylink', 'mytext')}}").render(interpreter).getValue().trim()).isEqualTo("link: mylink, text: mytext"); } @Test public void testFnWithArgsWithDefVals() { TagNode t = fixture("def-vals"); assertThat(t.render(interpreter).getValue()).isEmpty(); MacroFunction fn = (MacroFunction) interpreter.resolveObject("__macros__.article", -1); assertThat(fn.getArguments()).containsExactly("title", "thumb", "link", "summary", "last"); assertThat(fn.getDefaults()).contains(entry("last", false)); assertThat(snippet("{{ article('mytitle','mythumb','mylink','mysummary') }}").render(interpreter).getValue().trim()) .isEqualTo("title: mytitle, thumb: mythumb, link: mylink, summary: mysummary, last: false"); assertThat(snippet("{{ article('mytitle','mythumb','mylink','mysummary', last=true) }}").render(interpreter).getValue().trim()) .isEqualTo("title: mytitle, thumb: mythumb, link: mylink, summary: mysummary, last: true"); } @Test public void testFnWithArrayDefVal() { TagNode t = fixture("array-def-val"); assertThat(t.render(interpreter).getValue()).isEmpty(); MacroFunction fn = (MacroFunction) interpreter.resolveObject("__macros__.prefix", -1); assertThat(fn.getArguments()).containsExactly("property", "value", "prefixes", "prefixval"); assertThat(fn.getDefaults()).contains(entry("prefixes", Lists.newArrayList("webkit", "moz"))); } @Test public void testMacroUsedInForLoop() throws Exception { Map<String, Object> bindings = new HashMap<>(); bindings.put("widget_data", ImmutableMap.of( "tools_body_1", ImmutableMap.of("html", "body1"), "tools_body_2", ImmutableMap.of("html", "body2"), "tools_body_3", ImmutableMap.of("html", "body3"), "tools_body_4", ImmutableMap.of("html", "body4"))); Document dom = Jsoup.parseBodyFragment(new Jinjava().render(fixtureText("macro-used-in-forloop"), bindings)); Element tabs = dom.select(".tabs").get(0); assertThat(tabs.select(".tools__description")).hasSize(4); assertThat(tabs.select(".tools__description").get(0).text()).isEqualTo("body1"); assertThat(tabs.select(".tools__description").get(1).text()).isEqualTo("body2"); assertThat(tabs.select(".tools__description").get(2).text()).isEqualTo("body3"); assertThat(tabs.select(".tools__description").get(3).text()).isEqualTo("body4"); } @Test public void itPreventsDirectMacroRecursion() throws IOException { String template = fixtureText("recursion"); interpreter.render(template); assertThat(interpreter.getErrors().get(0).getMessage()).contains("Cycle detected for macro 'hello'"); } @Test public void itPreventsIndirectMacroRecursion() throws IOException { String template = fixtureText("recursion_indirect"); interpreter.render(template); assertThat(interpreter.getErrors().get(0).getMessage()).contains("Cycle detected for macro 'goodbye'"); } @Test public void itAllowsMacrosCallingMacrosUsingCall() throws IOException { String template = fixtureText("macros-calling-macros"); String out = interpreter.render(template); assertThat(interpreter.getErrors()).isEmpty(); assertThat(out).contains("Hello World One"); assertThat(out).contains("Hello World Two"); } @Test public void itAllowsMacroRecursionWhenEnabledInConfiguration() throws IOException { // I need a different configuration here therefore interpreter = new Jinjava(JinjavaConfig.newBuilder().withEnableRecursiveMacroCalls(true).build()).newInterpreter(); JinjavaInterpreter.pushCurrent(interpreter); try { String template = fixtureText("ending-recursion"); String out = interpreter.render(template); assertThat(interpreter.getErrors()).isEmpty(); assertThat(out).contains("Hello Hello Hello Hello Hello"); } finally { // and I need to cleanup my mess... JinjavaInterpreter.popCurrent(); } } private Node snippet(String jinja) { return new TreeParser(interpreter, jinja).buildTree().getChildren().getFirst(); } private String fixtureText(String name) { try { return Resources.toString(Resources.getResource(String.format("tags/macrotag/%s.jinja", name)), StandardCharsets.UTF_8); } catch (IOException e) { throw Throwables.propagate(e); } } private TagNode fixture(String name) { return (TagNode) snippet(fixtureText(name)); } }