| /* | |
| * 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(); | |
| } | |
| } | |
| } |