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