/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.safehtml.rebind;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.util.UnitTestTreeLogger;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import junit.framework.TestCase;
/**
* Tests for {@link HtmlTemplateParser}.
*/
public final class HtmlTemplateParserTest extends TestCase {
private TreeLogger logger;
@Override
public void setUp() {
logger = new PrintWriterTreeLogger();
}
/*
* We use the string representation of ParsedHtmlTemplate to express expected
* results. The unit test for ParsedHtmlTemplate establishes that this string
* representation accurately represents the structure of the parsed template.
*/
private void assertParseTemplateResult(String expected, String template)
throws UnableToCompleteException {
HtmlTemplateParser parser = new HtmlTemplateParser(logger);
parser.parseTemplate(template);
assertEquals(expected, parser.getParsedTemplate().toString());
}
public void testParseTemplate_noMarkup() throws UnableToCompleteException {
assertParseTemplateResult("[]", "");
assertParseTemplateResult("[L(foo)]", "foo");
assertParseTemplateResult(
"[L(foo), P((TEXT,null,null),0), L(bar)]",
"foo{0}bar");
assertParseTemplateResult(
"[L(foo), P((TEXT,null,null),0), "
+ "P((TEXT,null,null),1), L(bar)]",
"foo{0}{1}bar");
assertParseTemplateResult(
"[L(foo), P((TEXT,null,null),0), L(b), "
+ "P((TEXT,null,null),1), L(bar)]",
"foo{0}b{1}bar");
assertParseTemplateResult(
"[L(foo), P((TEXT,null,null),0), L(baz), "
+ "P((TEXT,null,null),1), L(bar)]",
"foo{0}baz{1}bar");
assertParseTemplateResult(
"[P((TEXT,null,null),0), L(foo), P((TEXT,null,null),0), "
+ "P((TEXT,null,null),1), L(bar)]",
"{0}foo{0}{1}bar");
assertParseTemplateResult(
"[P((TEXT,null,null),0), L(foo), P((TEXT,null,null),0), "
+ "P((TEXT,null,null),1), L(b)]",
"{0}foo{0}{1}b");
assertParseTemplateResult(
"[L(foo), P((TEXT,null,null),0), P((TEXT,null,null),1), "
+ "L(bar), P((TEXT,null,null),2)]",
"foo{0}{1}bar{2}");
assertParseTemplateResult(
"[L(f), P((TEXT,null,null),2), P((TEXT,null,null),1), "
+ "L(bar), P((TEXT,null,null),2)]",
"f{2}{1}bar{2}");
// Test degenerate cases with curly braces that don't match a parameter
// pattern; these are treated as regular string literals.
assertParseTemplateResult("[L(foo{)]", "foo{");
assertParseTemplateResult("[L(}foo)]", "}foo");
assertParseTemplateResult("[L(foo{text})]", "foo{text}");
}
public void testParseTemplate_withMarkup() throws UnableToCompleteException {
// Basic cases.
assertParseTemplateResult("[L(<b>foo</b>)]", "<b>foo</b>");
assertParseTemplateResult(
"[L(<span>foo<b>), P((TEXT,null,null),0), L(</b></span>)]",
"<span>foo<b>{0}</b></span>");
assertParseTemplateResult(
("[L(<span>foo<b>), P((TEXT,null,null),1), L(</b>), "
+ "P((TEXT,null,null),0), L(</span>)]"),
"<span>foo<b>{1}</b>{0}</span>");
// Check that case is not modified.
assertParseTemplateResult(
"[L(<B Id=\"bAr\">fOo</B>)]", "<B Id=\"bAr\">fOo</B>");
// Verify correct handling/escaping of HTML metacharacters and
// CDATA sections.
assertParseTemplateResult(
("[L(<span>foo&bar<b>), P((TEXT,null,null),1), "
+ "L(</b><![CDATA[foo-cdata <baz>]]>), P((TEXT,null,null),0), "
+ "L(</span>)]"),
"<span>foo&bar<b>{1}</b><![CDATA[foo-cdata <baz>]]>{0}</span>");
// Check correct handling of ATTRIBUTE_VALUE vs. URL_ATTRIBUTE_START and
// URL_ATTRIBUTE_ENTIRE context.
assertParseTemplateResult(("[L(<a href=\"), P((URL_ATTRIBUTE_ENTIRE,a,href),0), "
+ "L(\">), P((TEXT,null,null),1), L(</a>)]"),
"<a href=\"{0}\">{1}</a>");
// Single quotes work too:
assertParseTemplateResult(("[L(<a href='), P((URL_ATTRIBUTE_ENTIRE,a,href),0), "
+ "L('>), P((TEXT,null,null),1), L(</a>)]"),
"<a href='{0}'>{1}</a>");
assertParseTemplateResult(
("[L(<a href=\"http://), P((ATTRIBUTE_VALUE,a,href),0), "
+ "L(\">), P((TEXT,null,null),1), L(</a>)]"),
"<a href=\"http://{0}\">{1}</a>");
assertParseTemplateResult(("[L(<a href=\"), P((URL_ATTRIBUTE_START,a,href),0), "
+ "L(/), P((ATTRIBUTE_VALUE,a,href),1), "
+ "L(\">), P((TEXT,null,null),2), L(</a>)]"),
"<a href=\"{0}/{1}\">{2}</a>");
// Verify correct escaping in attributes.
assertParseTemplateResult(
("[L(<a href=\"http://...&), " + "P((ATTRIBUTE_VALUE,a,href),0), "
+ "L(=), P((ATTRIBUTE_VALUE,a,href),1), "
+ "L(\">), P((TEXT,null,null),2), L(</a>)]"),
"<a href=\"http://...&{0}={1}\">{2}</a>");
// Test correct detection of CSS context.
assertParseTemplateResult(
"[L(<div class=\"), P((ATTRIBUTE_VALUE,div,class),0), L(\" style=\"), "
+ "P((CSS_ATTRIBUTE_START,div,style),2), L(\">Hello ), "
+ "P((TEXT,null,null),1)]",
"<div class=\"{0}\" style=\"{2}\">Hello {1}");
assertParseTemplateResult(
"[L(<div class=\"), P((ATTRIBUTE_VALUE,div,class),0), L(\" style=\"color:green; ), "
+ "P((CSS_ATTRIBUTE,div,style),2), L(\">Hello ), "
+ "P((TEXT,null,null),1)]",
"<div class=\"{0}\" style=\"color:green; {2}\">Hello {1}");
assertParseTemplateResult(
"[L(<div>), P((TEXT,null,null),0), L(<style>foo ), "
+ "P((CSS,null,null),1), L(</style>)]",
"<div>{0}<style>foo {1}</style>");
// Test that javascript contexts without variables are allowed
assertParseTemplateResult(
"[L(<div onClick=alert() class=\"), "
+ "P((ATTRIBUTE_VALUE,div,class),0), L(\">)]",
"<div onClick=alert() class=\"{0}\">");
}
private void assertParsingTemplateEndingInNonInnerHtmlContextFails(
String template) {
assertParseFails(
"Template does not end in inner-HTML context: ", template, template);
}
public void testParseTemplate_endingInNonInnerHtmlContextFails() {
assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=");
assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=\"");
assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=\"{0}");
assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=\"{0}\"");
assertParsingTemplateEndingInNonInnerHtmlContextFails(
"<div class=\"{0}\" foo");
assertParsingTemplateEndingInNonInnerHtmlContextFails(
"<div class=\"{0}\" foo=bar");
assertParsingTemplateEndingInNonInnerHtmlContextFails(
"<div class=\"{0}\" foo=bar>{1}<a");
assertParsingTemplateEndingInNonInnerHtmlContextFails(
"<div class=\"{0}\" foo=bar>{1}<a href=");
// Check that parseTemplate doesn't walk off the end of the string when
// extracting lookAhead: We should be getting an error that the template
// ends in non-inner-HTML context, and not an IndexOutOfBoundsException.
assertParsingTemplateEndingInNonInnerHtmlContextFails("<a href='{0}'");
assertParsingTemplateEndingInNonInnerHtmlContextFails("<a href='{0}");
}
private void assertTemplateVariableInUnquotedAttributeFails(
String template, String failAtPrefix) {
assertParseFails("Template variable in unquoted attribute value: ",
template, failAtPrefix);
}
public void testParseTemplate_templateVariableInUnquotedAttributeFails() {
assertTemplateVariableInUnquotedAttributeFails(
"<div class={0}>", "<div class={0}");
assertTemplateVariableInUnquotedAttributeFails(
"<div style=blah class={0}>", "<div style=blah class={0}");
assertTemplateVariableInUnquotedAttributeFails(
"<div style=blah class=blah{0}>", "<div style=blah class=blah{0}");
assertTemplateVariableInUnquotedAttributeFails(
"<div style=blah class=\"{0}\" foo={1}>bar</div><a href={3}>",
"<div style=blah class=\"{0}\" foo={1}");
assertTemplateVariableInUnquotedAttributeFails(
"<div style=blah class=\"{0}\" foo=\"{1}\">bar</div><a href={3}>",
"<div style=blah class=\"{0}\" foo=\"{1}\">bar</div><a href={3}");
assertTemplateVariableInUnquotedAttributeFails(
"<div style=blah class=\"{0}\"foo=\"{1}\">bar</div><a href=http://{3}>",
"<div style=blah class=\"{0}\"foo=\"{1}\">bar</div><a href=http://{3}");
}
private void assertTemplateVariableInJsContextFails(
String template, String failAtPrefix) {
assertParseFails(
"Template variables in javascript context are not supported: ",
template, failAtPrefix);
}
public void testParseTemplate_templateVariableInJsContextFails() {
assertTemplateVariableInJsContextFails(
"<div onClick=\"{0}\">", "<div onClick=\"{0}");
assertTemplateVariableInJsContextFails(
"<div onClick=\"alert({0})\">", "<div onClick=\"alert({0}");
assertTemplateVariableInJsContextFails(
"foo<script language=blah>{0};", "foo<script language=blah>{0}");
assertTemplateVariableInJsContextFails(
"foo<script language=blah>alert({0});",
"foo<script language=blah>alert({0}");
}
private void assertTemplateVariableInCommentContextFails(
String template, String failAtPrefix) {
assertParseFails(
"Template variables inside HTML comments are not supported: ",
template, failAtPrefix);
}
public void testParseTemplate_templateVariableInCommentFails() {
assertTemplateVariableInCommentContextFails(
"<!-- Hello {0}-->", "<!-- Hello {0}");
assertTemplateVariableInCommentContextFails(
"<!--<script language=blah>alert({0});",
"<!--<script language=blah>alert({0}");
}
private void assertTemplateVariableInAttributeNameFails(
String template, String failAtPrefix) {
assertParseFails(
"Template variables in tags or in attribute names are not supported: ",
template, failAtPrefix);
}
public void testParseTemplate_templateVariableInAttributeNameFails() {
assertTemplateVariableInAttributeNameFails(
"<div style=\"{0}\" {1}=\"{2}\">", "<div style=\"{0}\" {1}");
assertTemplateVariableInAttributeNameFails(
"<div style=\"{0}\" foo{1}=\"{2}\">", "<div style=\"{0}\" foo{1}");
}
public void testParseTemplate_templateVariableInMetaContentFails() {
assertParseFails("Template variables in content attribute of meta tag are not supported: ",
"<meta http-equiv=\"{0}\" content=\"{1}\">", "<meta http-equiv=\"{0}\" content=\"{1}");
}
private void assertParseFails(
String expectedError, final String template, final String failAtPrefix) {
UnitTestTreeLogger.Builder loggerBuilder = new UnitTestTreeLogger.Builder();
loggerBuilder.expectError(
expectedError
+ failAtPrefix, null);
UnitTestTreeLogger logger = loggerBuilder.createLogger();
HtmlTemplateParser parser = new HtmlTemplateParser(logger);
try {
parser.parseTemplate(template);
fail("Parsing invalid template did not fail."
+ " Parsed representation: " + parser.getParsedTemplate().toString());
} catch (UnableToCompleteException e) {
logger.assertCorrectLogEntries();
}
}
}