blob: 9ee9563e4fd75e75215b3f9c8d495cf0317e292d [file] [log] [blame]
// Copyright 2006, 2007 The Apache Software Foundation
//
// 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 org.apache.tapestry5.internal.services;
import org.apache.tapestry5.internal.parser.*;
import org.apache.tapestry5.internal.test.InternalBaseTestCase;
import org.apache.tapestry5.ioc.Locatable;
import org.apache.tapestry5.ioc.Location;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newSet;
import org.apache.tapestry5.ioc.internal.util.TapestryException;
import org.apache.tapestry5.test.TapestryTestConstants;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static java.lang.String.format;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* This is used to test the template parser ... and in some cases, the underlying behavior of the SAX APIs.
*/
public class TemplateParserImplTest extends InternalBaseTestCase
{
private TemplateParser getParser()
{
return this.getService(TemplateParser.class);
}
private synchronized ComponentTemplate parse(String file)
{
Resource resource = getResource(file);
return getParser().parseTemplate(resource);
}
private synchronized List<TemplateToken> tokens(String file)
{
Resource resource = getResource(file);
return getParser().parseTemplate(resource).getTokens();
}
private Resource getResource(String file)
{
String packageName = getClass().getPackage().getName();
String path = packageName.replace('.', '/') + "/" + file;
ClassLoader loader = getClass().getClassLoader();
return new ClasspathResource(loader, path);
}
@SuppressWarnings("unchecked")
private <T extends TemplateToken> T get(List l, int index)
{
Object raw = l.get(index);
return (T) raw;
}
private void checkLine(Locatable l, int expectedLineNumber)
{
assertEquals(l.getLocation().getLine(), expectedLineNumber);
}
@Test
public void just_HTML()
{
Resource resource = getResource("justHTML.tml");
ComponentTemplate template = getParser().parseTemplate(resource);
assertSame(template.getResource(), resource);
List<TemplateToken> tokens = template.getTokens();
// They add up quick ...
assertEquals(tokens.size(), 20);
StartElementToken t0 = get(tokens, 0);
// Spot check a few things ...
assertEquals(t0.getName(), "html");
assertEquals(t0.getNamespaceURI(), "");
checkLine(t0, 1);
TextToken t1 = get(tokens, 1);
// Concerned this may not work cross platform.
assertEquals(t1.getText(), "\n ");
StartElementToken t2 = get(tokens, 2);
assertEquals(t2.getName(), "head");
checkLine(t2, 2);
TextToken t5 = get(tokens, 5);
assertEquals(t5.getText(), "title");
checkLine(t5, 3);
get(tokens, 6);
StartElementToken t12 = get(tokens, 12);
assertEquals(t12.getName(), "p");
AttributeToken t13 = get(tokens, 13);
assertEquals(t13.getName(), "class");
assertEquals(t13.getValue(), "important");
assertEquals(t13.getNamespaceURI(), "");
TextToken t14 = get(tokens, 14);
// Simplify the text, converting consecutive whitespace to just a single space.
assertEquals(t14.getText().replaceAll("\\s+", " ").trim(), "Tapestry rocks! Line 2");
// Line number is the *start* line of the whole text block.
checkLine(t14, 6);
}
@Test
public void namespaced_element()
{
Resource resource = getResource("namespaced_element.tml");
ComponentTemplate template = getParser().parseTemplate(resource);
assertSame(template.getResource(), resource);
List<TemplateToken> tokens = template.getTokens();
// They add up quick ...
assertEquals(tokens.size(), 8);
StartElementToken t0 = get(tokens, 0);
String expectedURI = "http://foo.com";
assertEquals(t0.getNamespaceURI(), expectedURI);
assertEquals(t0.getName(), "bar");
DefineNamespacePrefixToken t1 = get(tokens, 1);
assertEquals(t1.getNamespacePrefix(), "foo");
assertEquals(t1.getNamespaceURI(), expectedURI);
AttributeToken t2 = get(tokens, 2);
assertEquals(t2.getName(), "biff");
assertEquals(t2.getValue(), "baz");
assertEquals(t2.getNamespaceURI(), expectedURI);
StartElementToken t4 = get(tokens, 4);
assertEquals(t4.getNamespaceURI(), "");
assertEquals(t4.getName(), "gnip");
// The rest are close tokens
}
@Test
public void container_element()
{
List<TemplateToken> tokens = tokens("container_element.tml");
assertEquals(tokens.size(), 4);
TextToken t0 = get(tokens, 0);
assertEquals(t0.getText().trim(), "A bit of text.");
StartElementToken t1 = get(tokens, 1);
assertEquals(t1.getName(), "foo");
EndElementToken t2 = get(tokens, 2);
assertNotNull(t2); // Keep compiler happy
TextToken t3 = get(tokens, 3);
assertEquals(t3.getText().trim(), "Some more text.");
}
@Test
public void xml_entity()
{
List<TemplateToken> tokens = tokens("xmlEntity.tml");
assertEquals(tokens.size(), 3);
TextToken t = get(tokens, 1);
// This is OK because the org.apache.tapestry5.dom.Text will convert the characters back into
// XML entities.
assertEquals(t.getText().trim(), "lt:< gt:> amp:&");
}
@Test
public void html_entity()
{
String expectedURI = "http://www.w3.org/1999/xhtml";
List<TemplateToken> tokens = tokens("html_entity.tml");
assertEquals(tokens.size(), 5);
DTDToken t0 = get(tokens, 0);
assertEquals(t0.getName(), "html");
assertEquals(t0.getPublicId(), "-//W3C//DTD XHTML 1.0 Transitional//EN");
assertEquals(t0.getSystemId(), "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd");
StartElementToken t1 = get(tokens, 1);
assertEquals(t1.getNamespaceURI(), expectedURI);
assertEquals(t1.getName(), "html");
DefineNamespacePrefixToken t2 = get(tokens, 2);
assertEquals(t2.getNamespaceURI(), expectedURI);
assertEquals(t2.getNamespacePrefix(), "");
TextToken t = get(tokens, 3);
// HTML entities are parsed into values that will ultimately
// be output as numeric entities. This is less than ideal; would like
// to find a way to keep the entities in their original form (possibly
// involving a new type of token), but SAX seems to be fighting me on this.
// You have to have a DOCTYPE just to parse a template that uses
// an HTML entity.
assertEquals(t.getText().trim(), "nbsp:[\u00a0]");
}
@Test
public void cdata()
{
List<TemplateToken> tokens = tokens("cdata.tml");
// Whitespace text tokens around the CDATA
assertEquals(tokens.size(), 5);
CDATAToken t = get(tokens, 2);
assertEquals(t.getContent(), "CDATA: &lt;foo&gt; &amp; &lt;bar&gt; and <baz>");
checkLine(t, 2);
}
@Test
public void comment()
{
List<TemplateToken> tokens = tokens("comment.tml");
// Again, whitespace before and after the comment adds some tokens
assertEquals(tokens.size(), 3);
CommentToken token1 = get(tokens, 1);
// Comments are now trimmed of leading and trailing whitespace. This may mean
// that the output isn't precisely what's in the template, but a) its a comment
// and b) that's pretty much true of everything in the templates.
assertEquals(token1.getComment(), "Single line comment");
}
@Test
public void multiline_comment()
{
List<TemplateToken> tokens = tokens("multilineComment.tml");
// Again, whitespace before and after the comment adds some tokens
assertEquals(tokens.size(), 5);
CommentToken t = get(tokens, 2);
String comment = t.getComment().trim().replaceAll("\\s+", " ");
assertEquals(comment, "Line one Line two Line three");
}
@Test
public void component()
{
List<TemplateToken> tokens = tokens("component.tml");
assertEquals(tokens.size(), 6);
StartComponentToken t = get(tokens, 2);
assertEquals(t.getId(), "fred");
assertEquals(t.getComponentType(), "somecomponent");
assertNull(t.getMixins());
checkLine(t, 2);
get(tokens, 3);
}
@Test
public void component_with_body()
{
List<TemplateToken> tokens = tokens("componentWithBody.tml");
assertEquals(tokens.size(), 7);
get(tokens, 2);
TextToken t = get(tokens, 3);
assertEquals(t.getText().trim(), "fred's body");
get(tokens, 4);
}
@Test
public void root_element_is_component()
{
List<TemplateToken> tokens = tokens("root_element_is_component.tml");
assertEquals(tokens.size(), 3);
StartComponentToken start = get(tokens, 0);
assertEquals(start.getId(), "fred");
assertEquals(start.getComponentType(), "Fred");
assertNull(start.getElementName());
AttributeToken attr = get(tokens, 1);
assertEquals(attr.getName(), "param");
assertEquals(attr.getValue(), "value");
assertTrue(EndElementToken.class.isInstance(tokens.get(2)));
}
@Test
public void instrumented_element()
{
ComponentTemplate template = parse("instrumented_element.tml");
List<TemplateToken> tokens = template.getTokens();
assertEquals(tokens.size(), 3);
StartComponentToken start = get(tokens, 0);
assertEquals(start.getId(), "fred");
assertEquals(start.getComponentType(), "Fred");
assertEquals(start.getElementName(), "html");
AttributeToken attr = get(tokens, 1);
assertEquals(attr.getName(), "param");
assertEquals(attr.getValue(), "value");
assertTrue(EndElementToken.class.isInstance(tokens.get(2)));
assertEquals(template.getComponentIds(), Arrays.asList("fred"));
}
@Test
public void body_element()
{
List<TemplateToken> tokens = tokens("body_element.tml");
// start(html), text, body, text, end(html)
assertEquals(tokens.size(), 5);
// javac bug requires use of isInstance() instead of instanceof
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=113218
assertTrue(BodyToken.class.isInstance(get(tokens, 2)));
}
@Test
public void content_within_body_element()
{
List<TemplateToken> tokens = parse("content_within_body_element.tml").getTokens();
assertEquals(tokens.size(), 5);
// javac bug is requires use of isInstance() instead of instanceof
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=113218
assertTrue(BodyToken.class.isInstance(get(tokens, 2)));
assertTrue(TextToken.class.isInstance(get(tokens, 3)));
assertTrue(EndElementToken.class.isInstance(get(tokens, 4)));
}
@Test
public void component_with_parameters()
{
List<TemplateToken> tokens = tokens("componentWithParameters.tml");
assertEquals(tokens.size(), 9);
TemplateToken templateToken = get(tokens, 2);
Location l = templateToken.getLocation();
AttributeToken t1 = get(tokens, 3);
// TODO: Not sure what order the attributes appear in. Order in the XML? Sorted
// alphabetically? Random 'cause they're hashed?
assertEquals(t1.getName(), "cherry");
assertEquals(t1.getValue(), "bomb");
assertSame(t1.getLocation(), l);
AttributeToken t2 = get(tokens, 4);
assertEquals(t2.getName(), "align");
assertEquals(t2.getValue(), "right");
assertSame(t2.getLocation(), l);
TextToken t3 = get(tokens, 5);
assertEquals(t3.getText().trim(), "fred's body");
get(tokens, 6);
}
@Test
public void component_with_mixins()
{
List<TemplateToken> tokens = tokens("component_with_mixins.tml");
assertEquals(tokens.size(), 4);
StartComponentToken token1 = get(tokens, 1);
assertEquals(token1.getId(), "fred");
assertEquals(token1.getComponentType(), "comp");
assertEquals(token1.getMixins(), "Barney");
}
@Test
public void empty_string_mixins_is_null()
{
List<TemplateToken> tokens = tokens("empty_string_mixins_is_null.tml");
assertEquals(tokens.size(), 6);
StartComponentToken t = get(tokens, 2);
assertEquals(t.getId(), "fred");
// We also check that empty string type is null ..
assertNull(t.getComponentType());
assertNull(t.getMixins());
}
@Test
public void component_ids()
{
ComponentTemplate template = parse("component_ids.tml");
Set<String> ids = template.getComponentIds();
assertEquals(ids, newSet(Arrays.asList("bomb", "border", "zebra")));
}
@Test
public void expansions_in_normal_text()
{
List<TemplateToken> tokens = tokens("expansions_in_normal_text.tml");
assertEquals(tokens.size(), 7);
TextToken t1 = get(tokens, 1);
assertEquals(t1.getText().trim(), "Expansion #1[");
ExpansionToken t2 = get(tokens, 2);
assertEquals(t2.getExpression(), "expansion1");
TextToken t3 = get(tokens, 3);
assertEquals(t3.getText().replaceAll("\\s+", " "), "] Expansion #2[");
ExpansionToken t4 = get(tokens, 4);
assertEquals(t4.getExpression(), "expansion2");
TextToken t5 = get(tokens, 5);
assertEquals(t5.getText().trim(), "]");
}
@Test
public void expansions_must_be_on_one_line()
{
List<TemplateToken> tokens = tokens("expansions_must_be_on_one_line.tml");
assertEquals(tokens.size(), 3);
TextToken t1 = get(tokens, 1);
assertEquals(t1.getText().replaceAll("\\s+", " "), " ${expansions must be on a single line} ");
}
@Test
public void multiple_expansions_on_one_line()
{
List<TemplateToken> tokens = tokens("multiple_expansions_on_one_line.tml");
assertEquals(tokens.size(), 10);
ExpansionToken token3 = get(tokens, 3);
assertEquals(token3.getExpression(), "classLoader");
TextToken token4 = get(tokens, 4);
assertEquals(token4.getText(), " [");
ExpansionToken token5 = get(tokens, 5);
assertEquals(token5.getExpression(), "classLoader.class.name");
TextToken token6 = get(tokens, 6);
assertEquals(token6.getText(), "]");
}
@Test
public void expansions_not_allowed_in_cdata()
{
List<TemplateToken> tokens = tokens("expansions_not_allowed_in_cdata.tml");
assertEquals(tokens.size(), 5);
CDATAToken t2 = get(tokens, 2);
assertEquals(t2.getContent(), "${not-an-expansion}");
}
@Test
public void expansions_not_allowed_in_attributes()
{
List<TemplateToken> tokens = tokens("expansions_not_allowed_in_attributes.tml");
assertEquals(tokens.size(), 3);
AttributeToken token1 = get(tokens, 1);
assertEquals(token1.getName(), "exp");
assertEquals(token1.getValue(), "${not-an-expansion}");
}
@Test
public void parameter_element()
{
List<TemplateToken> tokens = tokens("parameter_element.tml");
ParameterToken token4 = get(tokens, 4);
assertEquals(token4.getName(), "fred");
CommentToken token6 = get(tokens, 6);
assertEquals(token6.getComment(), "fred content");
TemplateToken token8 = get(tokens, 8);
assertEquals(token8.getTokenType(), TokenType.END_ELEMENT);
}
@Test
public void complex_component_type()
{
List<TemplateToken> tokens = tokens("complex_component_type.tml");
assertEquals(tokens.size(), 4);
StartComponentToken token1 = get(tokens, 1);
assertEquals(token1.getComponentType(), "subfolder/nifty");
}
@Test
public void block_element()
{
List<TemplateToken> tokens = tokens("block_element.tml");
BlockToken token1 = get(tokens, 1);
assertEquals(token1.getId(), "block0");
CommentToken token2 = get(tokens, 2);
assertEquals(token2.getComment(), "block0 content");
BlockToken token4 = get(tokens, 4);
assertNull(token4.getId());
CommentToken token5 = get(tokens, 5);
assertEquals(token5.getComment(), "anon block content");
}
@DataProvider(name = "parse_failure_data")
public Object[][] parse_failure_data()
{
return new Object[][] {
{ "mixin_requires_id_or_type.tml",
"You may not specify mixins for element <span> because it does not represent a component (which requires either an id attribute or a type attribute).",
2 },
{ "illegal_nesting_within_body_element.tml", "Element 'xyz' is nested within a Tapestry body element",
2 },
{ "unexpected_attribute_in_parameter_element.tml",
"Element <parameter> does not support an attribute named 'grok'. The only allowed attribute name is 'name'.",
4 },
{ "name_attribute_of_parameter_element_omitted.tml",
"The name attribute of the <parameter> element must be specified.", 4 },
{ "name_attribute_of_parameter_element_blank.tml",
"The name attribute of the <parameter> element must be specified.", 4 },
{ "unexpected_attribute_in_block_element.tml",
"Element <block> does not support an attribute named 'name'. The only allowed attribute name is 'id'.",
3 },
};
}
@Test(dataProvider = "parse_failure_data")
public void parse_failure(String fileName, String errorMessageSubstring, int expectedLine)
{
try
{
parse(fileName);
unreachable();
}
catch (TapestryException ex)
{
if (!ex.getMessage().contains(errorMessageSubstring))
{
throw new AssertionError(format("Message [%s] does not contain substring [%s].", ex.getMessage(),
errorMessageSubstring));
}
assertEquals(ex.getLocation().getLine(), expectedLine);
}
}
@DataProvider(name = "doctype_parsed_correctly_data")
public Object[][] doctype_parsed_correctly_data()
{
return new Object[][] { { "xhtml1_strict_doctype.tml" }, { "xhtml1_transitional_doctype.tml" },
{ "xhtml1_frameset_doctype.tml" } };
}
@Test(dataProvider = "doctype_parsed_correctly_data")
public void doctype_parsed_correctly(String fileName) throws Exception
{
List<TemplateToken> tokens = tokens(fileName);
assertEquals(tokens.size(), 12);
TextToken t = get(tokens, 9);
assertEquals(t.getText().trim(), "<Test>");
}
@DataProvider(name = "doctype_token_added_correctly_data")
public Object[][] doctype_token_added_correctly_data()
{
return new Object[][] {
{ "xhtml1_strict_doctype.tml", "html", "-//W3C//DTD XHTML 1.0 Strict//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" },
{ "xhtml1_transitional_doctype.tml", "html", "-//W3C//DTD XHTML 1.0 Transitional//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" },
{ "xhtml1_frameset_doctype.tml", "html", "-//W3C//DTD XHTML 1.0 Frameset//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd" },
{ "html4_strict_doctype.tml", "HTML", "-//W3C//DTD HTML 4.01//EN",
"http://www.w3.org/TR/html4/strict.dtd" },
{ "html4_transitional_doctype.tml", "HTML", "-//W3C//DTD HTML 4.01 Transitional//EN",
"http://www.w3.org/TR/html4/loose.dtd" },
{ "html4_frameset_doctype.tml", "HTML", "-//W3C//DTD HTML 4.01 Frameset//EN",
"http://www.w3.org/TR/html4/frameset.dtd" },
{ "system_doctype.xml", "foo", null,
"src/test/resources/org/apache/tapestry5/internal/services/simple.dtd" } };
}
@Test(dataProvider = "doctype_token_added_correctly_data")
public void doctype_added_correctly(String fileName, String name, String publicId, String systemId) throws Exception
{
System.setProperty("user.dir", TapestryTestConstants.MODULE_BASE_DIR_PATH);
List<TemplateToken> tokens = tokens(fileName);
DTDToken t2 = get(tokens, 0);
assertEquals(t2.getName(), name);
assertEquals(t2.getPublicId(), publicId);
assertEquals(t2.getSystemId(), systemId);
}
@Test
public void invalid_component_id() throws Exception
{
try
{
parse("invalid_component_id.tml");
unreachable();
}
catch (RuntimeException ex)
{
assertMessageContains(ex, "Component id 'not-valid' is not valid");
}
}
@Test
public void invalid_block_id() throws Exception
{
try
{
parse("invalid_block_id.tml");
unreachable();
}
catch (RuntimeException ex)
{
assertMessageContains(ex, "Block id 'not-valid' is not valid");
}
}
/**
* Because of common code, this covers t:block and t:parameter.
*/
@Test
public void space_preserved_in_block() throws Exception
{
List<TemplateToken> tokens = tokens("space_preserved_in_block.tml");
TextToken token1 = get(tokens, 1);
assertEquals(token1.getText(), "\n" + " line in the middle\n" + " ");
}
/**
* t:container is a bit of a different code path than t:block/t:parameter
*/
@Test
public void space_preserved_in_container() throws Exception
{
List<TemplateToken> tokens = tokens("space_preserved_in_container.tml");
TextToken token0 = get(tokens, 0);
assertEquals(token0.getText(), "\n" + " ");
TextToken token2 = get(tokens, 2);
assertEquals(token2.getText(), "\n" + " some text\n" + " ");
}
@Test
public void minimal_whitespace_maintained_inside_tags() throws Exception
{
List<TemplateToken> tokens = tokens("minimal_whitespace_maintained_inside_tags.tml");
// A line feed or carriage return surrounded by other whitespace is reduced to
// just a line feed.
TextToken token1 = get(tokens, 1);
assertEquals(token1.getText(), "\nWhitespace\n");
TextToken token5 = get(tokens, 5);
assertEquals(token5.getText(), "\nis maintained.\n");
}
}