package org.trimou.engine;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
import org.trimou.AbstractEngineTest;
import org.trimou.ArchiveType;
import org.trimou.Mustache;
import org.trimou.MustacheExceptionAssert;
import org.trimou.engine.config.EngineConfigurationKey;
import org.trimou.engine.locator.AbstractTemplateLocator;
import org.trimou.engine.locator.MapTemplateLocator;
import org.trimou.engine.locator.TemplateLocator;
import org.trimou.exception.MustacheProblem;
import org.trimou.lambda.Lambda;
import org.trimou.lambda.SpecCompliantLambda;
import org.trimou.util.ImmutableList;
import org.trimou.util.ImmutableMap;
import org.trimou.util.ImmutableSet;
/**
*
* @author Martin Kouba
*/
public class MustacheEngineTest extends AbstractEngineTest {
@Before
public void buildEngine() {
}
@Test
public void testGlobalData() {
Lambda bold = new SpecCompliantLambda() {
@Override
public String invoke(String text) {
return "<b>" + text + "</b>";
}
@Override
public boolean isReturnValueInterpolated() {
return false;
}
};
Lambda italic = new SpecCompliantLambda() {
@Override
public String invoke(String text) {
return "<i>" + text + "</i>";
}
@Override
public boolean isReturnValueInterpolated() {
return false;
}
};
String templateContents = "{{foo}}| {{#bold}}Hello{{/bold}} {{#italic}}world{{/italic}}!|{{#archiveType.values}}{{this.suffix}}{{#iterHasNext}}, {{/iterHasNext}}{{/archiveType.values}}|{{archiveType.JAR}}";
Mustache mustache = MustacheEngineBuilder.newBuilder()
.addGlobalData("foo", true)
.addGlobalData("archiveType", ArchiveType.class)
.addGlobalData("bold", bold).addGlobalData("italic", italic)
.build().compileMustache("global_data", templateContents);
assertEquals("true| <b>Hello</b> <i>world</i>!|jar, war, ear|JAR",
mustache.render(null));
}
@Test
public void testDelimitersConfiguration() {
assertEquals(
"bar",
MustacheEngineBuilder
.newBuilder()
.setProperty(EngineConfigurationKey.START_DELIMITER,
"<%")
.setProperty(EngineConfigurationKey.END_DELIMITER, "//")
.build()
.compileMustache("delimiters_configuration", "<%foo//")
.render(ImmutableMap.<String, Object> of("foo", "bar")));
}
@Test
public void testDebugModeDisablesTemplateCache() {
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.setProperty(EngineConfigurationKey.DEBUG_MODE, true)
.addTemplateLocator(
new MapTemplateLocator(ImmutableMap.of("foo", "Hey!")))
.build();
assertNotEquals(engine.getMustache("foo"), engine.getMustache("foo"));
}
@Test
public void testTemplateCacheExpirationTimeout()
throws InterruptedException {
Map<String, String> templates = new HashMap<>();
templates.put("foo", "0");
long timeout = 1;
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.setProperty(
EngineConfigurationKey.TEMPLATE_CACHE_EXPIRATION_TIMEOUT,
timeout)
.addTemplateLocator(new MapTemplateLocator(templates)).build();
assertEquals("0", engine.getMustache("foo").render(null));
templates.put("foo", "1");
assertEquals("0", engine.getMustache("foo").render(null));
Thread.sleep((2 * timeout) * 1000);
assertEquals("1", engine.getMustache("foo").render(null));
}
@Test
public void testTemplateCacheDisabled() {
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.setProperty(EngineConfigurationKey.TEMPLATE_CACHE_ENABLED,
false)
.addTemplateLocator(new AbstractTemplateLocator(10) {
@Override
public Reader locate(String templateId) {
return new StringReader(UUID.randomUUID().toString());
}
@Override
public Set<String> getAllIdentifiers() {
return null;
}
}).build();
int size = 10;
Set<String> values = new HashSet<>(size);
for (int i = 0; i < size; i++) {
values.add(engine.getMustache("foo").render(null));
}
assertEquals(size, values.size());
}
@Test
public void testPrecompileAllAvailableTemplates() {
final List<String> sequence = new ArrayList<>();
TemplateLocator locator01 = new AbstractTemplateLocator(10) {
@Override
public Reader locate(String templateId) {
sequence.add("fooLocate");
return templateId.equals("foo") ? new StringReader("{{foo}}")
: null;
}
@Override
public Set<String> getAllIdentifiers() {
sequence.add("fooGetAllIdentifiers");
return ImmutableSet.of("foo");
}
};
MustacheEngineBuilder
.newBuilder()
.setProperty(EngineConfigurationKey.PRECOMPILE_ALL_TEMPLATES,
true).addTemplateLocator(locator01).build();
assertEquals(2, sequence.size());
assertEquals("fooGetAllIdentifiers", sequence.get(0));
assertEquals("fooLocate", sequence.get(1));
}
@Test
public void testIterationMetadataAlias() {
assertEquals(
"1",
MustacheEngineBuilder
.newBuilder()
.setProperty(
EngineConfigurationKey.ITERATION_METADATA_ALIAS,
"foo")
.build()
.compileMustache("iter_meta_alias",
"{{#this}}{{foo.index}}{{/this}}")
.render(ImmutableList.of(BigDecimal.ZERO)));
}
@Test
public void testTemplateLocatorReaderIsAlwaysClosed() {
final String template = "FOO";
final String illegalTemplate = "{{foo";
final AtomicBoolean isCloseInvoked = new AtomicBoolean(false);
TemplateLocator locator = new AbstractTemplateLocator(1) {
@Override
public Reader locate(String templateId) {
return "foo".equals(templateId)
? new MyStringReader(template, isCloseInvoked)
: new MyStringReader(illegalTemplate, isCloseInvoked);
}
@Override
public Set<String> getAllIdentifiers() {
return null;
}
};
final MustacheEngine engine = MustacheEngineBuilder.newBuilder()
.addTemplateLocator(locator).build();
assertEquals(template, engine.getMustacheSource("foo"));
assertTrue(isCloseInvoked.get());
engine.invalidateTemplateCache();
isCloseInvoked.set(false);
assertFalse(isCloseInvoked.get());
assertEquals(template, engine.getMustache("foo").render(null));
assertTrue(isCloseInvoked.get());
isCloseInvoked.set(false);
assertFalse(isCloseInvoked.get());
MustacheExceptionAssert.expect(MustacheProblem.COMPILE_INVALID_TEMPLATE, "Unexpected non-text buffer")
.check(() -> engine.getMustache("whatever").render(null));
assertTrue(isCloseInvoked.get());
}
@Test
public void testHelloWorld() {
String data = "Hello world!";
assertEquals(data, MustacheEngineBuilder.newBuilder().build()
.compileMustache("myTemplateName", "{{this}}").render(data));
}
@Test
public void testGeneratedIdsAreUnique() {
MustacheEngine engine = MustacheEngineBuilder.newBuilder().build();
assertNotEquals(engine.compileMustache("foo", "{{foo}}").getGeneratedId(), engine.compileMustache("foo", "{{foo}}").getGeneratedId());
}
@Test
public void testInvalidateTemplateCache() {
final AtomicInteger locatorCalled = new AtomicInteger(0);
MustacheEngine engine = MustacheEngineBuilder.newBuilder().addTemplateLocator((name) -> {
locatorCalled.incrementAndGet();
return new StringReader("{{this}}");
}).build();
assertEquals("foo", engine.getMustache("any").render("foo"));
assertEquals("bar", engine.getMustache("one").render("bar"));
assertEquals(2, locatorCalled.get());
engine.invalidateTemplateCache((name) -> name.equals("any"));
assertEquals("foo", engine.getMustache("any").render("foo"));
assertEquals(3, locatorCalled.get());
}
@Test
public void testTemplateCacheUsedForSource() {
final AtomicInteger locatorCalled = new AtomicInteger(0);
MustacheEngine engine = MustacheEngineBuilder.newBuilder()
.addTemplateLocator((name) -> {
locatorCalled.incrementAndGet();
return new StringReader("{{this}}");
}).build();
assertEquals("{{this}}", engine.getMustacheSource("any"));
assertEquals("{{this}}", engine.getMustacheSource("any"));
assertEquals(2, locatorCalled.get());
locatorCalled.set(0);
engine = MustacheEngineBuilder.newBuilder()
.setProperty(
EngineConfigurationKey.TEMPLATE_CACHE_USED_FOR_SOURCE,
true)
.addTemplateLocator((name) -> {
locatorCalled.incrementAndGet();
return new StringReader("{{this}}");
}).build();
assertEquals("{{this}}", engine.getMustacheSource("any"));
assertEquals("{{this}}", engine.getMustacheSource("any"));
assertEquals(1, locatorCalled.get());
}
private static class MyStringReader extends StringReader {
final AtomicBoolean isCloseInvoked;
public MyStringReader(String s, AtomicBoolean isClosed) {
super(s);
this.isCloseInvoked = isClosed;
}
@Override
public void close() {
isCloseInvoked.set(true);
super.close();
}
}
}