package com.google.sitebricks.compiler; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.sitebricks.Bricks; import com.google.sitebricks.Renderable; import com.google.sitebricks.Respond; import com.google.sitebricks.RespondersForTesting; import com.google.sitebricks.Template; import com.google.sitebricks.TestRequestCreator; import com.google.sitebricks.conversion.MvelTypeConverter; import com.google.sitebricks.conversion.TypeConverter; import com.google.sitebricks.headless.Request; import com.google.sitebricks.http.Delete; import com.google.sitebricks.http.Get; import com.google.sitebricks.http.Patch; import com.google.sitebricks.http.Post; import com.google.sitebricks.http.Put; import com.google.sitebricks.rendering.EmbedAs; import com.google.sitebricks.rendering.control.Chains; import com.google.sitebricks.rendering.control.WidgetRegistry; import com.google.sitebricks.routing.PageBook; import com.google.sitebricks.routing.SystemMetrics; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.lang.annotation.Annotation; import java.util.Map; import javax.servlet.http.HttpServletRequest; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; /** * @author Dhanji R. Prasanna (dhanji@gmail.com) */ public class HtmlTemplateCompilerTest { private static final String ANNOTATION_EXPRESSIONS = "Annotation expressions"; private Injector injector; private WidgetRegistry registry; private PageBook pageBook; private SystemMetrics metrics; private final Map<String, Class<? extends Annotation>> methods = Maps.newHashMap(); private HtmlTemplateCompiler compiler() { registry = injector.getInstance(WidgetRegistry.class); registry.addEmbed("myfave"); pageBook = injector.getInstance(PageBook.class); pageBook.at("/somewhere", MyEmbeddedPage.class).apply(Chains.terminal()); return new HtmlTemplateCompiler(registry, pageBook, metrics); } @BeforeMethod public void pre() { methods.put("get", Get.class); methods.put("post", Post.class); methods.put("put", Put.class); methods.put("patch", Patch.class); methods.put("delete", Delete.class); injector = Guice.createInjector(new AbstractModule() { protected void configure() { bind(new TypeLiteral<Request>(){}).toProvider(mockRequestProviderForContext()); bind(new TypeLiteral<Map<String, Class<? extends Annotation>>>() { }) .annotatedWith(Bricks.class) .toInstance(methods); } }); pageBook = createNiceMock(PageBook.class); metrics = createNiceMock(SystemMetrics.class); } @Test public final void annotationKeyExtraction() { assert "link".equals(Dom.extractKeyAndContent("@Link")[0]) : "Extraction wrong: "; assert "thing".equals(Dom.extractKeyAndContent("@Thing()")[0]) : "Extraction wrong: "; assert "thing".equals(Dom.extractKeyAndContent("@Thing(asodkoas)")[0]) : "Extraction wrong: "; assert "thing".equals(Dom.extractKeyAndContent("@Thing(asodkoas) ")[0]) : "Extraction wrong: "; assert "thing".equals(Dom.extractKeyAndContent("@Thing(asodkoas) kko")[0]) : "Extraction wrong: "; assert "".equals(Dom.extractKeyAndContent("@Link")[1]) : "Extraction wrong: "; final String val = Dom.extractKeyAndContent("@Thing()")[1]; assert null == (val) : "Extraction wrong: " + val; assert "asodkoas".equals(Dom.extractKeyAndContent("@Thing(asodkoas)")[1]) : "Extraction wrong: "; assert "asodkoas".equals(Dom.extractKeyAndContent("@Thing(asodkoas) ")[1]) : "Extraction wrong: "; assert "asodkoas".equals(Dom.extractKeyAndContent("@Thing(asodkoas) kko")[1]) : "Extraction wrong: "; } @Test public final void readShowIfWidgetTrue() { Renderable widget = compiler() .compile(Object.class, new Template("<html>@ShowIf(true)<p>hello</p></html>")); // .compile("<!doctype html>\n" + // "<html><head><meta charset=\"UTF-8\"><title>small test</title></head><body>\n" + // "@ShowIf(true)<p>hello</p>" + // "\n</body></html>"); assert null != widget : " null "; final Respond mockRespond = RespondersForTesting.newRespond(); // final Respond mockRespond = new StringBuilderRespond() { // @Override // public void write(String text) { // builder.append(text); // } // // @Override // public void write(char text) { // builder.append(text); // } // // @Override // public void chew() { // builder.deleteCharAt(builder.length() - 1); // } // }; widget.render(new Object(), mockRespond); final String value = mockRespond.toString(); System.out.println(value); assert "<html><p>hello</p></html>".equals(value) : "Did not write expected output, instead: " + value; // assert "<!doctype html><html><head><meta charset=\"UTF-8\"><title>small test</title></head><body><p>hello</p></body></html>".equals(value) : "Did not write expected output, instead: " + value; } @DataProvider(name = ANNOTATION_EXPRESSIONS) public Object[][] get() { return new Object[][]{ {"true"}, {"java.lang.Boolean.TRUE"}, {"java.lang.Boolean.valueOf('true')"}, // {"true ? true : true"}, @TODO (BD): Disabled until I actually investigate if this is a valid test. {"'x' == 'x'"}, {"\"x\" == \"x\""}, {"'hello' instanceof java.io.Serializable"}, {"true; return true"}, {" 5 >= 2 "}, }; } @Test(dataProvider = ANNOTATION_EXPRESSIONS) public final void readAWidgetWithVariousExpressions(String expression) { Renderable widget = compiler() .compile(Object.class, new Template(String.format("<html>@ShowIf(%s)<p>hello</p></html>", expression))); assert null != widget : " null "; final Respond mockRespond = RespondersForTesting.newRespond(); widget.render(new Object(), mockRespond); final String value = mockRespond.toString(); assert "<html><p>hello</p></html>".equals(value) : "Did not write expected output, instead: " + value; } @Test public final void readShowIfWidgetFalse() { Renderable widget = compiler() .compile(Object.class, new Template("<html>@ShowIf(false)<p>hello</p></html>")); assert null != widget : " null "; final Respond mockRespond = RespondersForTesting.newRespond(); widget.render(new Object(), mockRespond); final String value = mockRespond.toString(); assert "<html></html>".equals(value) : "Did not write expected output, instead: " + value; } @Test public final void readTextWidgetValues() { // make a basic type converter without creating TypeConverter converter = new MvelTypeConverter(); Parsing.setTypeConverter(converter); Renderable widget = compiler() .compile(TestBackingType.class, new Template("<html><div class='${clazz}'>hello <a href='/people/${id}'>${name}</a></div></html>")); assert null != widget : " null "; final Respond mockRespond = RespondersForTesting.newRespond(); widget.render(new TestBackingType("Dhanji", "content", 12), mockRespond); final String value = mockRespond.toString(); assert "<html><div class='content'>hello <a href='/people/12'>Dhanji</a></div></html>" .replaceAll("'", "\"") .equals(value) : "Did not write expected output, instead: " + value; } public static class TestBackingType { private String name; private String clazz; private Integer id; public TestBackingType(String name, String clazz, Integer id) { this.name = name; this.clazz = clazz; this.id = id; } public String getName() { return name; } public String getClazz() { return clazz; } public Integer getId() { return id; } } @Test public final void readAndRenderRequireWidget() { // make a basic type converter without creating TypeConverter converter = new MvelTypeConverter(); Parsing.setTypeConverter(converter); Renderable widget = compiler() //new HtmlTemplateCompiler(registry, pageBook, metrics) .compile(TestBackingType.class, new Template("<html> <head>" + " @Require <script type='text/javascript' src='my.js'> </script>" + " @Require <script type='text/javascript' src='my.js'> </script>" + "</head><body>" + "<div class='${clazz}'>hello <a href='/people/${id}'>${name}</a></div>" + "</body></html>")); assert null != widget : " null "; final Respond respond = RespondersForTesting.newRespond(); widget.render(new TestBackingType("Dhanji", "content", 12), respond); final String value = respond.toString(); String expected = "<html> <head>" + " <script type='text/javascript' src='my.js'> </script>" + "</head><body>" + "<div class='content'>hello <a href='/people/12'>Dhanji</a></div></body></html>"; expected = expected.replaceAll("'", "\""); assertEquals(value, expected); } @Test public final void readHtmlWidgetWithError() { try{ Renderable widget = compiler() .compile(TestBackingType.class, new Template("<html>\n<div class='${clazz}'>hello</div>\n</html>${qwe}")); fail(); } catch (Exception ex){ assertEquals(ex.getClass(), TemplateCompileException.class); TemplateCompileException te = (TemplateCompileException) ex; assertEquals(te.getErrors().size(), 1); CompileError error = te.getErrors().get(0); assertEquals(error.getLine(), 2); } } @Test public final void readHtmlWidgetWithErrorAndWidget() { try{ Renderable widget = compiler() .compile(TestBackingType.class, new Template("<html>\n<div class='${clazz}'>hello</div>\n\n</html>@ShowIf(true)\n${qwe}")); fail(); } catch (Exception ex){ assertEquals(ex.getClass(), TemplateCompileException.class); TemplateCompileException te = (TemplateCompileException) ex; assertEquals(te.getErrors().size(), 1); CompileError error = te.getErrors().get(0); assertEquals(error.getLine(), 4); } } @Test public final void readHtmlWidget() { Renderable widget = compiler() .compile(TestBackingType.class, new Template("<html><div class='${clazz}'>hello</div></html>")); assert null != widget : " null "; final Respond mockRespond = RespondersForTesting.newRespond(); widget.render(new TestBackingType("Dhanji", "content", 12), mockRespond); final String s = mockRespond.toString(); assert "<html><div class=\"content\">hello</div></html>" .equals(s) : "Did not write expected output, instead: " + s; } @Test public final void readHtmlWidgetWithChildren() { Renderable widget = compiler() .compile(TestBackingType.class, new Template("<!doctype html><html><body><div class='${clazz}'>hello @ShowIf(false)<a href='/hi/${id}'>hideme</a></div></body></html>")); assert null != widget : " null "; final Respond mockRespond = RespondersForTesting.newRespond(); widget.render(new TestBackingType("Dhanji", "content", 12), mockRespond); final String s = mockRespond.toString(); assertEquals(s, "<!doctype html><html><body><div class=\"content\">hello </div></body></html>"); } @EmbedAs(MyEmbeddedPage.MY_FAVE_ANNOTATION) public static class MyEmbeddedPage { protected static final String MY_FAVE_ANNOTATION = "MyFave"; private boolean should = true; public boolean isShould() { return should; } public void setShould(boolean should) { this.should = should; } } @Test public final void readEmbedWidgetAndStoreAsPage() { Renderable widget = compiler() .compile(TestBackingType.class, new Template("<xml><div class='content'>hello @MyFave(should=false)<a href='/hi/${id}'>hideme</a></div></xml>")); assert null != widget : " null "; //tell pagebook to track this as an embedded widget pageBook.embedAs(MyEmbeddedPage.class, MyEmbeddedPage.MY_FAVE_ANNOTATION) .apply(Chains.terminal()); final Respond mockRespond = RespondersForTesting.newRespond(); widget.render(new TestBackingType("Dhanji", "content", 12), mockRespond); final String s = mockRespond.toString(); assert "<xml><div class=\"content\">hello </div></xml>" .equals(s) : "Did not write expected output, instead: " + s; } @Test public final void readEmbedWidgetOnly() { Renderable widget = compiler() .compile(TestBackingType.class, new Template("<html><div class='content'>hello @MyFave(should=false)<a href='/hi/${id}'>hideme</a></div></html>")); assert null != widget : " null "; //tell pagebook to track this as an embedded widget pageBook.embedAs(MyEmbeddedPage.class, MyEmbeddedPage.MY_FAVE_ANNOTATION) .apply(Chains.terminal()); final Respond mockRespond = RespondersForTesting.newRespond(); widget.render(new TestBackingType("Dhanji", "content", 12), mockRespond); final String s = mockRespond.toString(); assert "<html><div class=\"content\">hello </div></html>" .equals(s) : "Did not write expected output, instead: " + s; } //TODO Fix this test! // @Test // public final void readEmbedWidgetWithArgs() throws ExpressionCompileException { // // final Evaluator evaluator = new MvelEvaluator(); // final Injector injector = Guice.createInjector(new AbstractModule() { // protected void configure() { // bind(HttpServletRequest.class).toProvider(mockRequestProviderForContext()); // } // }); // final PageBook book = injector.getInstance(PageBook.class); //hacky, where are you super-packages! // // final WidgetRegistry registry = injector.getInstance(WidgetRegistry.class); // // final MvelEvaluatorCompiler compiler = new MvelEvaluatorCompiler(TestBackingType.class); // Renderable widget = // new HtmlTemplateCompiler(Object.class, compiler, registry, book, metrics) // .compile("<xml><div class='content'>hello @MyFave(should=true)<a href='/hi/${id}'> @With(\"me\")<p>showme</p></a></div></xml>"); // // assert null != widget : " null "; // // // HtmlWidget bodyWrapper = new XmlWidget(Chains.proceeding().addWidget(new IncludeWidget(new TerminalWidgetChain(), "'me'", evaluator)), // "body", compiler, Collections.<String, String>emptyMap()); // // bodyWrapper.setRequestProvider(mockRequestProviderForContext()); // // //should include the @With("me") annotated widget from the template above (discarding the <p> tag). // book.embedAs(MyEmbeddedPage.class).apply(bodyWrapper); // // final Respond mockRespond = new StringBuilderRespond(); // // widget.render(new TestBackingType("Dhanji", "content", 12), mockRespond); // // final String s = mockRespond.toString(); // assert "<xml><div class=\"content\">hello showme</div></xml>" // .equals(s) : "Did not write expected output, instead: " + s; // } public static Provider<Request> mockRequestProviderForContext() { return new Provider<Request>() { public Request get() { final HttpServletRequest request = createNiceMock(HttpServletRequest.class); expect(request.getContextPath()) .andReturn("") .anyTimes(); expect(request.getMethod()) .andReturn("POST") .anyTimes(); expect(request.getParameterMap()) .andReturn(ImmutableMap.of()) .anyTimes(); replay(request); return TestRequestCreator.from(request, null); } }; } }