/*
* Copyright 2011 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.passes;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Iterables;
import com.google.template.soy.SoyFileSetParserBuilder;
import com.google.template.soy.error.FormattingErrorReporter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for CheckDelegatesVisitor.
*
*/
@RunWith(JUnit4.class)
public final class CheckDelegatesVisitorTest {
@Test
public void testRecognizeValidDelegatePackage() {
assertValidSoyFiles(
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " blah\n"
+ "{/template}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .foo private=\"true\"}\n"
+ " blah\n"
+ "{/template}\n");
}
@Test
public void testRecognizeValidDelegateTemplate() {
assertValidSoyFiles(
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " blah\n"
+ "{/template}\n"
+ "\n"
+ "/** @param foo */\n"
+ "{deltemplate MagicButton}\n"
+ " 000\n"
+ "{/deltemplate}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/** @param foo */\n"
+ "{deltemplate MagicButton}\n"
+ " 111 {$foo}\n"
+ "{/deltemplate}\n");
}
@Test
public void testRecognizeValidDelegateCall() {
assertValidSoyFiles(
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " {delcall MagicButton}{param foo : '' /}{/delcall}\n"
+ "{/template}\n"
+ "\n"
+ "/** @param foo */\n"
+ "{deltemplate MagicButton}\n"
+ " 000\n"
+ "{/deltemplate}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/** @param foo */\n"
+ "{deltemplate MagicButton}\n"
+ " 111 {$foo}\n"
+ "{/deltemplate}\n");
}
@Test
public void testErrorReusedTemplateName() {
assertInvalidSoyFiles(
"Found deltemplate ns1.boo with the same name as a basic template at no-path:4:1.",
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " blah\n"
+ "{/template}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{deltemplate ns1.boo}\n"
+ // reused name ns1.boo
" 111\n"
+ "{/deltemplate}\n");
}
@Test
public void testErrorParamsMismatch() {
assertInvalidSoyFiles(
"Found delegate template with same name 'MagicButton' "
+ "but different param declarations compared to the "
+ "definition at no-path:9:1.",
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " blah\n"
+ "{/template}\n"
+ "\n"
+ "/***/\n"
+ // no params
"{deltemplate MagicButton}\n"
+ " 000\n"
+ "{/deltemplate}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/** @param foo */\n"
+ // has param 'foo'
"{deltemplate MagicButton}\n"
+ " 111 {$foo}\n"
+ "{/deltemplate}\n");
assertInvalidSoyFiles(
"Found delegate template with same name 'MagicButton' but different param "
+ "declarations compared to the definition at no-path:9:1.",
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " blah\n"
+ "{/template}\n"
+ "\n"
+ "/** @param? foo */\n"
+ // param 'foo' is optional
"{deltemplate MagicButton}\n"
+ " 000\n"
+ "{/deltemplate}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/** @param foo */\n"
+ // param 'foo' is required
"{deltemplate MagicButton}\n"
+ " 111 {$foo}\n"
+ "{/deltemplate}\n");
}
@Test
public void testErrorParamsMismatchAcrossVariants() {
assertInvalidSoyFiles(
"Found delegate template with same name 'MagicButton' "
+ "but different param declarations compared to the definition at no-path:4:1.",
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ // no params
"{deltemplate MagicButton}\n"
+ " vanilla\n"
+ "{/deltemplate}\n"
+ "/** @param foo */\n"
+ // some params params
"{deltemplate MagicButton variant=\"'something'\"}\n"
+ " something\n"
+ "{/deltemplate}\n");
}
@Test
public void testOkNonRequiredParamsMismatchAcrossVariants() {
assertValidSoyFiles(
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ // no params
"{deltemplate MagicButton}\n"
+ " vanilla\n"
+ "{/deltemplate}\n"
+ "/** @param? foo */\n"
+ // an optional param
"{deltemplate MagicButton variant=\"'something'\"}\n"
+ " something\n"
+ "{/deltemplate}\n");
}
@Test
public void testAllowPublicBasicTemplateInDelegatePackage() {
assertValidSoyFiles(
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " blah\n"
+ "{/template}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .foo}\n"
+ // not marked private
" blah\n"
+ "{/template}\n");
}
@Test
public void testErrorBasicCallToDelegateTemplate() {
assertInvalidSoyFiles(
"'call' to delegate template 'MagicButton' (expected 'delcall').",
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " {call MagicButton /}\n"
+ // basic call (should be delegate call)
"{/template}\n"
+ "\n"
+ "/** @param foo */\n"
+ "{deltemplate MagicButton}\n"
+ " 000\n"
+ "{/deltemplate}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/** @param foo */\n"
+ "{deltemplate MagicButton}\n"
+ " 111 {$foo}\n"
+ "{/deltemplate}\n");
}
@Test
public void testErrorBasicDepFromNonDelpackageOnOtherDelegatePackage() {
assertInvalidSoyFiles(
"Found illegal call from 'ns1.boo' to 'ns2.foo', which is in a different delegate package.",
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " {call ns2.foo /}\n"
+ // call to ns2.foo, which is public
"{/template}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .foo}\n"
+ " blah\n"
+ "{/template}\n");
}
@Test
public void testErrorBasicDepOnOtherDelegatePackage() {
assertInvalidSoyFiles(
"Found illegal call from 'ns1.boo' to 'ns2.foo', which is in a different delegate package.",
""
+ "{delpackage NotQuiteSoSecretFeature}\n"
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " {call ns2.foo /}\n"
+ // call to ns2.foo, which is public
"{/template}\n",
""
+ "{delpackage SecretFeature}\n"
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .foo}\n"
+ " blah\n"
+ "{/template}\n");
}
@Test
public void testErrorDelegateCallToBasicTemplate() {
assertInvalidSoyFiles(
"'delcall' to basic template 'ns2.foo' (expected 'call').",
""
+ "{namespace ns1 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .boo}\n"
+ " {delcall ns2.foo /}\n"
+ // delegate call (should be basic call)
"{/template}\n",
""
+ "{namespace ns2 autoescape=\"deprecated-noncontextual\"}\n"
+ "\n"
+ "/***/\n"
+ "{template .foo private=\"true\"}\n"
+ " blah\n"
+ "{/template}\n");
}
@Test
public void testStrictModeContentKindMatches() {
// One is strict and the other is not.
assertInvalidSoyFiles(
"If one deltemplate has strict autoescaping, all its peers must also be strictly "
+ "autoescaped with the same content kind: null != HTML. "
+ "Conflicting definition at no-path-2:5:1.",
""
+ "{namespace ns}\n\n"
+ "/***/\n"
+ "{template .main autoescape=\"deprecated-contextual\"}\n"
+ "{delcall foo}\n"
+ "{param x: '' /}\n"
+ "{/delcall}\n"
+ "{/template}",
""
+ "{delpackage dp1}\n"
+ "{namespace ns}\n\n"
+ "/** @param x */\n"
+ "{deltemplate foo autoescape=\"deprecated-contextual\"}\n"
+ "<b>{$x}</b>\n"
+ "{/deltemplate}",
""
+ "{delpackage dp2}\n"
+ "{namespace ns}\n\n"
+ "/** @param x */\n"
+ "{deltemplate foo autoescape=\"strict\" kind=\"html\"}\n"
+ "<i>{$x}</i>\n"
+ "{/deltemplate}");
// Both are strict, but have non-matching kinds.
assertInvalidSoyFiles(
"If one deltemplate has strict autoescaping, all its peers must also be strictly "
+ "autoescaped with the same content kind: HTML != TEXT. "
+ "Conflicting definition at no-path-2:4:1.",
""
+ "{namespace ns}\n\n"
+ "/***/\n"
+ "{template .main autoescape=\"deprecated-contextual\"}\n"
+ "{delcall foo}\n"
+ "{param x: '' /}\n"
+ "{/delcall}\n"
+ "{/template}",
""
+ "{namespace ns.default}\n\n"
+ "/** @param x */\n"
+ "{deltemplate foo autoescape=\"strict\" kind=\"html\"}\n"
+ "<b>{$x}</b>\n"
+ "{/deltemplate}",
""
+ "{delpackage dp2}\n"
+ "{namespace ns}\n\n"
+ "/** @param x */\n"
+ "{deltemplate foo autoescape=\"strict\" kind=\"text\"}\n"
+ "<i>{$x}</i>\n"
+ "{/deltemplate}");
}
private void assertValidSoyFiles(String... soyFileContents) {
SoyFileSetParserBuilder.forFileContents(soyFileContents).parse();
}
private void assertInvalidSoyFiles(String expectedErrorMsgSubstr, String... soyFileContents) {
FormattingErrorReporter errorReporter = new FormattingErrorReporter();
SoyFileSetParserBuilder.forFileContents(soyFileContents).errorReporter(errorReporter).parse();
assertThat(errorReporter.getErrorMessages()).hasSize(1);
assertThat(Iterables.getOnlyElement(errorReporter.getErrorMessages()))
.isEqualTo(expectedErrorMsgSubstr);
}
}