/*
* Copyright 2015 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.template.soy.html.passes;
import static com.google.common.truth.Truth.assertThat;
import static com.google.template.soy.shared.SharedTestUtils.getNode;
import com.google.template.soy.SoyFileSetParserBuilder;
import com.google.template.soy.data.SanitizedContent.ContentKind;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.ExplodingErrorReporter;
import com.google.template.soy.error.FormattingErrorReporter;
import com.google.template.soy.html.IncrementalHtmlAttributeNode;
import com.google.template.soy.html.IncrementalHtmlCloseTagNode;
import com.google.template.soy.html.IncrementalHtmlOpenTagNode;
import com.google.template.soy.shared.AutoEscapingType;
import com.google.template.soy.soytree.CssNode;
import com.google.template.soy.soytree.HtmlContext;
import com.google.template.soy.soytree.IfCondNode;
import com.google.template.soy.soytree.LetContentNode;
import com.google.template.soy.soytree.MsgFallbackGroupNode;
import com.google.template.soy.soytree.PrintNode;
import com.google.template.soy.soytree.RawTextNode;
import com.google.template.soy.soytree.SoyFileSetNode;
import com.google.template.soy.soytree.TemplateNode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link HtmlTransformVisitor}. */
@RunWith(JUnit4.class)
public final class HtmlTransformVisitorTest {
private static final ErrorReporter FAIL = ExplodingErrorReporter.get();
private static SoyFileSetNode performVisitor(String templateBody, ErrorReporter er) {
SoyFileSetNode sfsn =
SoyFileSetParserBuilder.forTemplateContents(AutoEscapingType.STRICT, templateBody)
.errorReporter(er)
.parse()
.fileSet();
new HtmlTransformVisitor(er).exec(sfsn);
return sfsn;
}
@Test
public void testMultipleAttributes() {
String templateBody = "<div id=\"foo\" data-bam=\"baz\"></div>\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0)).getTagName()).isEqualTo("div");
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0)).getName()).isEqualTo("id");
assertThat(((RawTextNode) getNode(n, 0, 0, 0)).getRawText()).isEqualTo("foo");
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 1)).getName()).isEqualTo("data-bam");
assertThat(getNode(n, 1)).isInstanceOf(IncrementalHtmlCloseTagNode.class);
}
@Test
public void testAttributeWithoutValue() {
String templateBody = "<div id disabled></div>\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0)).getTagName()).isEqualTo("div");
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0)).getName()).isEqualTo("id");
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0)).getChildren()).isEmpty();
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 1)).getName()).isEqualTo("disabled");
assertThat(getNode(n, 1)).isInstanceOf(IncrementalHtmlCloseTagNode.class);
}
@Test
public void testAttributeWithPrintNodes() {
String templateBody =
"{@param foo : ?}\n"
+ "{@param bar : ?}\n"
+ "<div class=\"Class1 {$foo} Class2 {$bar}\">\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0)).getTagName()).isEqualTo("div");
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0)).getName()).isEqualTo("class");
assertThat(((RawTextNode) getNode(n, 0, 0, 0)).getRawText()).isEqualTo("Class1 ");
assertThat(((PrintNode) getNode(n, 0, 0, 1)).getExprText()).isEqualTo("$foo");
assertThat(((RawTextNode) getNode(n, 0, 0, 2)).getRawText()).isEqualTo(" Class2 ");
assertThat(((PrintNode) getNode(n, 0, 0, 3)).getExprText()).isEqualTo("$bar");
assertThat(((TemplateNode) getNode(n)).getChildren()).hasSize(1);
}
@Test
public void testCssNodeInAttributeValue() {
String templateBody = "<div class=\"{css Foobar} Baz\"></div>\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0)).getTagName()).isEqualTo("div");
assertThat(((CssNode) getNode(n, 0, 0, 0)).getCommandText()).isEqualTo("Foobar");
assertThat(((RawTextNode) getNode(n, 0, 0, 1)).getRawText()).isEqualTo(" Baz");
}
@Test
public void testPrintNodeInAttributeValue() {
String templateBody = "{@param foo : ?}\n<div id=\"foo {$foo} bar\"></div>\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((RawTextNode) getNode(n, 0, 0, 0)).getRawText()).isEqualTo("foo ");
assertThat(((PrintNode) getNode(n, 0, 0, 1)).getExprText()).isEqualTo("$foo");
assertThat(((PrintNode) getNode(n, 0, 0, 1)).getHtmlContext())
.isEqualTo(HtmlContext.HTML_NORMAL_ATTR_VALUE);
assertThat(((RawTextNode) getNode(n, 0, 0, 2)).getRawText()).isEqualTo(" bar");
}
@Test
public void testConditionalInAttributes() {
String templateBody = "{@param foo : ?}\n<div{if $foo} id=\"foo {$foo}\"{/if}></div>\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IfCondNode) getNode(n, 0, 0, 0)).getCommandText()).isEqualTo("$foo");
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0, 0, 0)).getName()).isEqualTo("id");
assertThat(((PrintNode) getNode(n, 0, 0, 0, 0, 1)).getHtmlContext())
.isEqualTo(HtmlContext.HTML_NORMAL_ATTR_VALUE);
}
@Test
public void testParamInAttributeValue() {
String templateBody =
""
+ "{@param x: ?}\n"
+ "<div id=\"{call .blah}{param a kind=\"text\"}{$x}{/param}{/call}\"></div>\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0)).getName()).isEqualTo("id");
assertThat(((PrintNode) getNode(n, 0, 0, 0, 0, 0)).getHtmlContext())
.isEqualTo(HtmlContext.TEXT);
}
@Test
public void testPrintInMsgHtml() {
String templateBody = "" + "{@param x: ?}\n" + "{msg desc=\"\"}<a href=\"{$x}\"></a>{/msg}\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0, 0, 0, 0, 0)).getName())
.isEqualTo("href");
assertThat(((PrintNode) getNode(n, 0, 0, 0, 0, 0, 0, 0)).getHtmlContext())
.isEqualTo(HtmlContext.HTML_NORMAL_ATTR_VALUE);
}
@Test
public void testConditionalInAttributeValue() {
String templateBody = "{@param foo : ?}\n<div id=\"foo {if $foo}{$foo}{/if}\"></div>\n";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0)).getName()).isEqualTo("id");
assertThat(((IfCondNode) getNode(n, 0, 0, 1, 0)).getCommandText()).isEqualTo("$foo");
assertThat(((PrintNode) getNode(n, 0, 0, 1, 0, 0)).getHtmlContext())
.isEqualTo(HtmlContext.HTML_NORMAL_ATTR_VALUE);
}
@Test
public void testLetKindHtml() {
String templateBody =
"" + "{let $content kind=\"html\"}\n" + " <div id=\"foo\"></div>\n" + "{/let}";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((LetContentNode) getNode(n, 0)).getContentKind()).isEqualTo(ContentKind.HTML);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0, 0)).getTagName()).isEqualTo("div");
assertThat(getNode(n, 0, 1)).isInstanceOf(IncrementalHtmlCloseTagNode.class);
}
@Test
public void testLetKindAttributes() {
String templateBody =
""
+ "{@param disabled : ?}\n"
+ "{let $content kind=\"attributes\"}\n"
+ " foo=\"bar\"\n"
+ " {if $disabled}\n"
+ " disabled=\"true {$disabled}\"\n"
+ " {/if}\n"
+ "{/let}";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 0)).getName()).isEqualTo("foo");
assertThat(((IfCondNode) getNode(n, 0, 1, 0)).getCommandText()).isEqualTo("$disabled");
assertThat(((IncrementalHtmlAttributeNode) getNode(n, 0, 1, 0, 0)).getName())
.isEqualTo("disabled");
assertThat(((RawTextNode) getNode(n, 0, 1, 0, 0, 0)).getRawText()).isEqualTo("true ");
assertThat(((PrintNode) getNode(n, 0, 1, 0, 0, 1)).getHtmlContext())
.isEqualTo(HtmlContext.HTML_NORMAL_ATTR_VALUE);
}
@Test
public void testLetKindText() {
String templateBody =
""
+ "{@param x: string}\n"
+ "{let $content kind=\"text\"}\n"
+ " <div id=\"foo\"></div>\n"
+ " {$x}\n"
+ "{/let}";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((LetContentNode) getNode(n, 0)).getContentKind()).isEqualTo(ContentKind.TEXT);
assertThat(((RawTextNode) getNode(n, 0, 0)).getRawText()).isEqualTo("<div id=\"foo\"></div>");
assertThat(((PrintNode) getNode(n, 0, 1)).getHtmlContext()).isEqualTo(HtmlContext.TEXT);
}
@Test
public void testTemplateKindText() {
String fileBody =
""
+ "{namespace test}"
+ "/** */"
+ "{template .foo kind=\"text\"}"
+ " {@param x: string}\n"
+ " <div id=\"foo\"></div>\n"
+ " {$x}\n"
+ " {msg desc=\"a\"}b{/msg}\n"
+ "{/template}";
SoyFileSetNode n = SoyFileSetParserBuilder.forFileContents(fileBody).parse().fileSet();
new HtmlTransformVisitor(FAIL).exec(n);
assertThat(((RawTextNode) getNode(n, 0)).getRawText()).isEqualTo("<div id=\"foo\"></div>");
assertThat(((PrintNode) getNode(n, 1)).getHtmlContext()).isEqualTo(HtmlContext.TEXT);
assertThat(((MsgFallbackGroupNode) getNode(n, 2)).getHtmlContext()).isEqualTo(HtmlContext.TEXT);
}
@Test
public void testConsecutiveIf() {
String templateBody =
""
+ "{@param foo : ?}\n"
+ "{let $content kind=\"html\"}\n"
+ " <div>\n"
+ " {if $foo}Hello world{/if}\n"
+ " {if $foo}\n"
+ " <div>Hello world</div>\n"
+ " {/if}\n"
+ " </div>\n"
+ "{/let}";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IfCondNode) getNode(n, 0, 1, 0)).getCommandText()).isEqualTo("$foo");
assertThat(((RawTextNode) getNode(n, 0, 1, 0, 0)).getRawText()).isEqualTo("Hello world");
assertThat(((RawTextNode) getNode(n, 0, 1, 0, 0)).getHtmlContext())
.isEqualTo(HtmlContext.HTML_PCDATA);
assertThat(((IfCondNode) getNode(n, 0, 2, 0)).getCommandText()).isEqualTo("$foo");
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0, 2, 0, 0)).getTagName()).isEqualTo("div");
}
@Test
public void testIfBeforeQuotedValue() {
String templateBody = "{@param foo : ?}\n<div id={if $foo}\"foo\"{/if}></div>";
FormattingErrorReporter fer = new FormattingErrorReporter();
performVisitor(templateBody, fer);
assertThat(fer.getErrorMessages())
.contains(
"Soy statements are not allowed before an "
+ "attribute value. They should be moved inside a quotation mark.");
}
@Test
public void testMissingTagName() {
String templateBody = "<></div>\n";
FormattingErrorReporter fer = new FormattingErrorReporter();
performVisitor(templateBody, fer);
assertThat(fer.getErrorMessages()).contains("Found a tag with an empty tag name.");
}
@Test
public void testUnclosedTag() {
String templateBody = "<div></div\n";
FormattingErrorReporter fer = new FormattingErrorReporter();
performVisitor(templateBody, fer);
assertThat(fer.getErrorMessages())
.contains(
"Ending context of the content within a Soy tag "
+ "must match the starting context. Transition was from HTML_PCDATA to HTML_TAG");
}
@Test
public void testSelfClosingInput() {
String templateBody = "<input />";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0)).getTagName()).isEqualTo("input");
// also without the space, a previous bug made this ambiguous and parsed the tag name as
// "input/"
templateBody = "<input/>";
n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 0)).getTagName()).isEqualTo("input");
}
@Test
public void testSelfClosingSvgContent() {
String templateBody = "<svg><g /></svg>";
SoyFileSetNode n = performVisitor(templateBody, FAIL);
assertThat(((IncrementalHtmlOpenTagNode) getNode(n, 1)).getTagName()).isEqualTo("g");
}
@Test
public void testSelfClosingDiv() {
String templateBody = "<div />";
FormattingErrorReporter fer = new FormattingErrorReporter();
performVisitor(templateBody, fer);
assertThat(fer.getErrorMessages())
.contains(
"Invalid self-closing tag for \"div\". Self-closing tags are only valid for void tags "
+ "and SVG content (partially supported). For a list of void elements, see "
+ "https://www.w3.org/TR/html5/syntax.html#void-elements.");
}
@Test
public void testSelfClosingPathInSeparateTemplate() {
String content =
""
+ "{namespace foo}\n"
+ "/** Outer */\n"
+ "{template .outer}\n"
+ " <svg>{call .inner /}</svg>\n"
+ "{/template}\n"
+ "/** Inner */\n"
+ "{template .inner}\n"
+ " <path />\n"
+ "{/template}";
FormattingErrorReporter fer = new FormattingErrorReporter();
SoyFileSetNode sfsn = SoyFileSetParserBuilder.forFileContents(content).parse().fileSet();
new HtmlTransformVisitor(fer).exec(sfsn);
assertThat(fer.getErrorMessages())
.contains(
"Invalid self-closing tag for \"path\". Self-closing tags are only valid for void tags "
+ "and SVG content (partially supported). For a list of void elements, see "
+ "https://www.w3.org/TR/html5/syntax.html#void-elements.");
}
@Test
public void testNonQuotedAttributeValue() {
String templateBody = "<div></div><div foo=bar></div>";
FormattingErrorReporter fer = new FormattingErrorReporter();
performVisitor(templateBody, fer);
assertThat(fer.getErrorMessages())
.contains("Expected to find a quoted attribute value, but " + "found \"b\".");
}
@Test
public void testCssNodeInAttribute() {
String templateBody = "<div class=\"{css Foo}\"></div>";
performVisitor(templateBody, FAIL);
}
@Test
public void testCssNodeInText() {
String templateBody =
"" + "{let $class kind=\"text\"}{css Foo}{/let}" + "<div class=\"{$class}\"></div>";
performVisitor(templateBody, FAIL);
}
@Test
public void testCssNodeInHtml() {
String templateBody = "<div>{css Foo}</div>";
FormattingErrorReporter fer = new FormattingErrorReporter();
performVisitor(templateBody, fer);
assertThat(fer.getErrorMessages())
.contains(
"The incremental HTML Soy backend does not allow "
+ "{css} nodes to appear in HTML outside of attribute values.");
}
@Test
public void testXidNodeInText() {
String templateBody =
"" + "{let $foo kind=\"text\"}{xid Foo}{/let}" + "<div data-foo=\"{$foo}\"></div>";
performVisitor(templateBody, FAIL);
}
@Test
public void testXidNodeInAttribute() {
String templateBody = "<div data-foo=\"{xid Foo}\"></div>";
performVisitor(templateBody, FAIL);
}
@Test
public void testXidNodeInHtml() {
String templateBody = "<div>{xid Foo}</div>";
FormattingErrorReporter fer = new FormattingErrorReporter();
performVisitor(templateBody, fer);
assertThat(fer.getErrorMessages())
.contains(
"The incremental HTML Soy backend does not allow "
+ "{xid} nodes to appear in HTML outside of attribute values.");
}
}