package handlebarsjs.spec;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import org.junit.Test;
import com.github.jknack.handlebars.AbstractTest;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.HelperRegistry;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.Template;
public class BlockHelperMissingTest extends AbstractTest {
@Test
public void ifContextIsNotFoundHelperMissingIsUsed() throws IOException {
String string = "{{hello}} {{link_to world}}";
String context = "{ hello: Hello, world: world }";
Hash helpers = $(HelperRegistry.HELPER_MISSING, new Helper<String>() {
@Override
public Object apply(final String context, final Options options) throws IOException {
return new Handlebars.SafeString("<a>" + context + "</a>");
}
});
shouldCompileTo(string, context, helpers, "Hello <a>world</a>");
}
@Test
public void eachWithHash() throws IOException {
String string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
Object hash = $("goodbyes", $("<b>#1</b>", $("text", "goodbye"), "2", $("text", "GOODBYE")),
"world", "world");
shouldCompileTo(string, hash, "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!");
}
@Test
@SuppressWarnings("unused")
public void eachWithJavaBean() throws IOException {
String string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
Object hash = new Object() {
public Object getGoodbyes() {
return new Object() {
public Object getB1() {
return new Object() {
public String getText() {
return "goodbye";
}
};
}
public Object get2() {
return new Object() {
public String getText() {
return "GOODBYE";
}
};
}
};
}
public String getWorld() {
return "world";
}
};
try {
shouldCompileTo(string, hash, "b1. goodbye! 2. GOODBYE! cruel world!");
} catch (Throwable ex) {
// on jdk7 property order differ from jdk6
shouldCompileTo(string, hash, "2. GOODBYE! b1. goodbye! cruel world!");
}
}
@Test
public void with() throws IOException {
String string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, "{person: {first: Alan, last: Johnson}}", "Alan Johnson");
}
@Test
public void ifHelper() throws IOException {
String string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
shouldCompileTo(string, "{goodbye: true, world: world}", "GOODBYE cruel world!",
"if with boolean argument shows the contents when true");
shouldCompileTo(string, "{goodbye: dummy, world: world}", "GOODBYE cruel world!",
"if with string argument shows the contents");
shouldCompileTo(string, "{goodbye: false, world: world}", "cruel world!",
"if with boolean argument does not show the contents when false");
shouldCompileTo(string, "{world: world}", "cruel world!",
"if with undefined does not show the contents");
shouldCompileTo(string, $("goodbye", new Object[]{"foo" }, "world", "world"),
"GOODBYE cruel world!",
"if with non-empty array shows the contents");
shouldCompileTo(string, $("goodbye", new Object[0], "world", "world"), "cruel world!",
"if with empty array does not show the contents");
}
@Test
public void dataCanBeLookupViaAnnotation() throws IOException {
Template template = compile("{{@hello}}");
String result = template.apply(Context.newContext($).data("hello", "hello"));
assertEquals("hello", result);
}
@Test
public void deepAnnotationTriggersAutomaticTopLevelData() throws IOException {
String string = "{{#let world=\"world\"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}";
Hash helpers = $("let", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
for (Entry<String, Object> entry : options.hash.entrySet()) {
options.data(entry.getKey(), entry.getValue());
}
return options.fn(context);
}
});
Template template = compile(string, helpers);
String result = template.apply($("foo", true));
assertEquals("Hello world", result);
}
@Test
public void parameterCanBeLookupViaAnnotation() throws IOException {
Hash helpers = $("hello", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
return "Hello " + options.hash("noun");
}
});
Template template = compile("{{hello noun=@world}}", helpers);
String result = template.apply(Context.newContext($).data("world", "world"));
assertEquals("Hello world", result);
}
@Test
public void dataIsInheritedDownstream() throws IOException {
String string = "{{#let foo=bar.baz}}{{@foo}}{{/let}}";
Hash helpers = $("let", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
for (Entry<String, Object> entry : options.hash.entrySet()) {
options.data(entry.getKey(), entry.getValue());
}
return options.fn(context);
}
});
Template template = compile(string, helpers);
String result = template.apply($("bar", $("baz", "hello world")));
assertEquals("data variables are inherited downstream", "hello world", result);
}
@Test
public void passingInDataWorksWithHelpersInPartials() throws IOException {
String string = "{{>my_partial}}";
Hash partials = $("my_partial", "{{hello}}");
Hash helpers = $("hello", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
return options.data("adjective") + " " + options.get("noun");
}
});
Template template = compile(string, helpers, partials);
String result = template.apply(Context.newContext($("noun", "cat")).data("adjective", "happy"));
assertEquals("Data output by helper inside partial", "happy cat", result);
}
@Test
public void passingInDataWorksWithBlockHelpers() throws IOException {
String string = "{{#hello}}{{world}}{{/hello}}";
Hash helpers = $("hello", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
return options.fn();
}
}, "world", new Helper<Object>() {
@Override
public Object apply(final Object thing, final Options options) throws IOException {
Boolean exclaim = options.get("exclaim");
return options.data("adjective") + " world" + (exclaim ? "!" : "");
}
});
Template template = compile(string, helpers);
String result = template.apply(Context.newContext($("exclaim", true))
.data("adjective", "happy"));
assertEquals("happy world!", result);
}
@Test
public void passingInDataWorksWithBlockHelpersThatUsePaths() throws IOException {
String string = "{{#hello}}{{world ../zomg}}{{/hello}}";
Hash helpers = $("hello", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
return options.fn($("exclaim", "?"));
}
}, "world", new Helper<Object>() {
@Override
public Object apply(final Object thing, final Options options) throws IOException {
return options.data("adjective") + " " + thing + options.get("exclaim", "");
}
});
Template template = compile(string, helpers);
String result = template.apply(Context.newContext($("exclaim", true, "zomg", "world"))
.data("adjective", "happy"));
assertEquals("happy world?", result);
}
@Test
public void passingInDataWorksWithBlockHelpersWhereChildrenUsePaths() throws IOException {
String string = "{{#hello}}{{world ../zomg}}{{/hello}}";
Hash helpers = $("hello", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
return options.data("accessData") + " " + options.fn($("exclaim", "?"));
}
}, "world", new Helper<Object>() {
@Override
public Object apply(final Object thing, final Options options) throws IOException {
return options.data("adjective") + " " + thing + options.get("exclaim", "");
}
});
Template template = compile(string, helpers);
String result = template.apply(Context.newContext($("exclaim", true, "zomg", "world"))
.data("adjective", "happy").data("accessData", "#win"));
assertEquals("#win happy world?", result);
}
@Test
public void overrideInheritedDataWhenInvokingHelper() throws IOException {
String string = "{{#hello}}{{world zomg}}{{/hello}}";
Hash helpers = $("hello", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
return options.fn(Context.newContext($("exclaim", "?", "zomg", "world"))
.data("adjective", "sad"));
}
}, "world", new Helper<Object>() {
@Override
public Object apply(final Object thing, final Options options) throws IOException {
return options.data("adjective") + " " + thing + options.get("exclaim", "");
}
});
Template template = compile(string, helpers);
String result = template.apply(Context.newContext($("exclaim", true, "zomg", "planet"))
.data("adjective", "happy").data("accessData", "#win"));
assertEquals("Overriden data output by helper", "sad world?", result);
}
@Test
public void overrideInheritedDataWhenInvokingHelperWithDepth() throws IOException {
String string = "{{#hello}}{{world zomg}}{{/hello}}";
Hash helpers = $("hello", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options) throws IOException {
return options.fn(Context.newContext($("exclaim", "?", "zomg", "world"))
.data("adjective", "sad"));
}
}, "world", new Helper<Object>() {
@Override
public Object apply(final Object thing, final Options options) throws IOException {
return options.data("adjective") + " " + thing + options.get("exclaim", "");
}
});
Template template = compile(string, helpers);
String result = template.apply(Context.newContext($("exclaim", true, "zomg", "planet"))
.data("adjective", "happy").data("accessData", "#win"));
assertEquals("Overriden data output by helper", "sad world?", result);
}
@Test
public void helpersTakePrecedenceOverSameNamedContextProperties() throws IOException {
Hash helpers = $("goodbye", new Helper<Map<String, Object>>() {
@Override
public Object apply(final Map<String, Object> context, final Options options)
throws IOException {
return context.get("goodbye").toString().toUpperCase();
}
}, "cruel", new Helper<String>() {
@Override
public Object apply(final String world, final Options options) throws IOException {
return "cruel " + world.toUpperCase();
}
});
shouldCompileTo("{{goodbye}} {{cruel world}}", "{goodbye: goodbye, world: world}", helpers,
"GOODBYE cruel WORLD");
}
@Test
public void blockHelpersTakePrecedenceOverSameNamedContextProperties() throws IOException {
Hash helpers = $("goodbye", new Helper<Map<String, Object>>() {
@Override
public Object apply(final Map<String, Object> context, final Options options)
throws IOException {
return context.get("goodbye").toString().toUpperCase() + options.fn(context);
}
}, "cruel", new Helper<String>() {
@Override
public Object apply(final String world, final Options options) throws IOException {
return "cruel " + world.toUpperCase();
}
});
shouldCompileTo("{{#goodbye}} {{cruel world}}{{/goodbye}}", "{goodbye: goodbye, world: world}",
helpers, "GOODBYE cruel WORLD");
}
@Test
public void scopedNamesTakePrecedenceOverHelpers() throws IOException {
Hash helpers = $("goodbye", new Helper<Map<String, Object>>() {
@Override
public Object apply(final Map<String, Object> context, final Options options)
throws IOException {
return context.get("goodbye").toString().toUpperCase();
}
}, "cruel", new Helper<String>() {
@Override
public Object apply(final String world, final Options options) throws IOException {
return "cruel " + world.toUpperCase();
}
});
shouldCompileTo("{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}",
"{goodbye: goodbye, world: world}",
helpers, "goodbye cruel WORLD cruel GOODBYE");
}
@Test
public void scopedNamesTakePrecedenceOverBlockHelpers() throws IOException {
Hash helpers = $("goodbye", new Helper<Map<String, Object>>() {
@Override
public Object apply(final Map<String, Object> context, final Options options)
throws IOException {
return context.get("goodbye").toString().toUpperCase() + options.fn(context);
}
}, "cruel", new Helper<String>() {
@Override
public Object apply(final String world, final Options options) throws IOException {
return "cruel " + world.toUpperCase();
}
});
shouldCompileTo("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}",
"{goodbye: goodbye, world: world}",
helpers, "GOODBYE cruel WORLD goodbye");
}
@Test
public void helperCanTakeOptionalHash() throws IOException {
Hash helpers = $("goodbye", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return "GOODBYE " + options.hash("cruel") + " " + options.hash("world") + " "
+ options.hash("times") + " TIMES";
}
});
shouldCompileTo("{{goodbye cruel=\"CRUEL\" world=\"WORLD\" times=12}}",
$, helpers, "GOODBYE CRUEL WORLD 12 TIMES");
}
@Test
public void helperCanTakeOptionalHashWithBooleans() throws IOException {
Hash helpers = $("goodbye", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
Boolean print = options.hash("print");
if (print) {
return "GOODBYE " + options.hash("cruel") + " " + options.hash("world");
} else {
return "NOT PRINTING";
}
}
});
shouldCompileTo("{{goodbye cruel=\"CRUEL\" world=\"WORLD\" print=true}}",
$, helpers, "GOODBYE CRUEL WORLD");
shouldCompileTo("{{goodbye cruel=\"CRUEL\" world=\"WORLD\" print=false}}",
$, helpers, "NOT PRINTING");
}
@Test
public void blockHelperCanTakeOptionalHash() throws IOException {
Hash helpers = $("goodbye", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return "GOODBYE " + options.hash("cruel") + " " + options.fn(context) + " "
+ options.hash("times") + " TIMES";
}
});
shouldCompileTo("{{#goodbye cruel=\"CRUEL\" times=12}}world{{/goodbye}}",
$, helpers, "GOODBYE CRUEL world 12 TIMES");
}
@Test
public void blockHelperCanTakeOptionalHashWithSingleQuotedStrings() throws IOException {
Hash helpers = $("goodbye", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return "GOODBYE " + options.hash("cruel") + " " + options.fn(context) + " "
+ options.hash("times") + " TIMES";
}
});
shouldCompileTo("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}",
$, helpers, "GOODBYE CRUEL world 12 TIMES");
}
@Test
public void blockHelperCanTakeOptionalHashWithBooleans() throws IOException {
Hash helpers = $("goodbye", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
Boolean print = options.hash("print");
if (print) {
return "GOODBYE " + options.hash("cruel") + " " + options.fn(context);
} else {
return "NOT PRINTING";
}
}
});
shouldCompileTo("{{#goodbye cruel=\"CRUEL\" print=true}}world{{/goodbye}}",
$, helpers, "GOODBYE CRUEL world");
shouldCompileTo("{{#goodbye cruel=\"CRUEL\" print=false}}world{{/goodbye}}",
$, helpers, "NOT PRINTING");
}
@Test
public void argumentsToHelpersCanBeRetrievedFromOptionsHashInStringForm() throws IOException {
Hash helpers = $("wycats", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return "HELP ME MY BOSS " + options.param(0) + ' ' + options.param(1);
}
});
assertEquals("HELP ME MY BOSS is.a slave.driver",
compile("{{wycats this is.a slave.driver}}", helpers, true).apply($));
}
@Test
public void whenUsingBlockFormArgumentsToHelpersCanBeRetrievedFromOptionsHashInStringForm()
throws IOException {
Hash helpers = $("wycats", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return "HELP ME MY BOSS " + options.param(0) + ' ' + options.param(1) + ": " + options.fn();
}
});
assertEquals("HELP ME MY BOSS is.a slave.driver: help :(",
compile("{{#wycats this is.a slave.driver}}help :({{/wycats}}", helpers, true).apply($));
}
@Test
public void whenInsideABlockInStringModePassesTheAppropriateContextInTheOptionsHash()
throws IOException {
Hash helpers = $("tomdale", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return "STOP ME FROM READING HACKER NEWS I " +
context + " " + options.param(0);
}
}, "with", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return options.fn(context);
}
});
assertEquals("STOP ME FROM READING HACKER NEWS I need-a dad.joke",
compile("{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}", helpers, true).apply(
$("dale", $, "need", "need-a")));
}
@Test
public void whenInsideABlockInStringModePassesTheAppropriateContextInTheOptionsHashToABlockHelper()
throws IOException {
Hash helpers = $("tomdale", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return "STOP ME FROM READING HACKER NEWS I " +
context + " " + options.param(0) + " " + options.fn(context);
}
}, "with", new Helper<Object>() {
@Override
public Object apply(final Object context, final Options options)
throws IOException {
return options.fn(context);
}
});
assertEquals(
"STOP ME FROM READING HACKER NEWS I need-a dad.joke wot",
compile("{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}", helpers,
true).apply(
$("dale", $, "need", "need-a")));
}
}