/*
* 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.pysrc.internal;
import static com.google.template.soy.pysrc.internal.SoyCodeForPySubject.assertThatSoyCode;
import static com.google.template.soy.pysrc.internal.SoyCodeForPySubject.assertThatSoyFile;
import com.google.common.collect.ImmutableMap;
import com.google.template.soy.base.SoySyntaxException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for GenPyCodeVisitor.
*
* <p>TODO(dcphillips): Add non-inlined 'if' test after adding LetNode support.
*
*/
@RunWith(JUnit4.class)
public final class GenPyCodeVisitorTest {
private static final String SOY_NAMESPACE = "{namespace boo.foo autoescape=\"strict\"}\n";
private static final String DUMMY_SOY_FILE = SOY_NAMESPACE + "{template .dummy}{/template}\n";
private static final String EXPECTED_PYFILE_START =
"# coding=utf-8\n"
+ "\"\"\" This file was automatically generated from no-path.\n"
+ "Please don't edit this file by hand.\n"
+ "\n"
+ "SOY_NAMESPACE: 'boo.foo'.\n"
+ "\n"
+ "Templates in namespace boo.foo.\n"
+ "\"\"\"\n"
+ "\n"
+ "from __future__ import unicode_literals\n"
+ "import collections\n"
+ "import math\n"
+ "import random\n"
+ "import sys\n"
+ "from example.runtime import bidi\n"
+ "from example.runtime import directives\n"
+ "from example.runtime import runtime\n"
+ "from example.runtime import sanitize\n"
+ "\n"
+ "NAMESPACE_MANIFEST = {\n"
+ "}\n"
+ "\n"
+ "try:\n"
+ " str = unicode\n"
+ "except NameError:\n"
+ " pass\n"
+ "\n";
private static final String SANITIZATION_APPROVAL =
"approval=sanitize.IActuallyUnderstandSoyTypeSafetyAndHaveSecurityApproval("
+ "'Internally created Sanitization.')";
@Test
public void testSoyFile() {
assertThatSoyFile(DUMMY_SOY_FILE).compilesToSourceContaining(EXPECTED_PYFILE_START);
// TODO(dcphillips): Add external template dependency import test once templates are supported.
}
@Test
public void testNamespacedImport() {
String soyFile =
SOY_NAMESPACE
+ "{template .helloWorld}\n"
+ " {call foo.bar.baz.quz /}\n"
+ "{/template}\n";
String expectedImport = "namespaced_import('baz', namespace='foo.bar')";
assertThatSoyFile(soyFile).compilesToSourceContaining(expectedImport);
}
@Test
public void testAbsoluteImport() {
String soyFile =
SOY_NAMESPACE
+ "{template .helloWorld}\n"
+ " {call foo.bar.baz.quz /}\n"
+ "{/template}\n";
String expectedImport = "import google.foo.bar.baz as baz";
ImmutableMap.Builder<String, String> namespaceManifest = new ImmutableMap.Builder<>();
namespaceManifest.put("foo.bar.baz", "google.foo.bar.baz");
assertThatSoyFile(soyFile)
.withNamespaceManifest(namespaceManifest.build())
.compilesToSourceContaining(expectedImport);
}
@Test
public void testNamespaceManifest() {
String soyFile =
SOY_NAMESPACE
+ "{template .helloWorld}\n"
+ " {call foo.bar.baz.quz /}\n"
+ "{/template}\n";
String expectedManifest =
"NAMESPACE_MANIFEST = {\n" + " 'foo.bar.baz': 'google.foo.bar.baz',\n" + "}\n";
ImmutableMap.Builder<String, String> namespaceManifest = new ImmutableMap.Builder<>();
namespaceManifest.put("foo.bar.baz", "google.foo.bar.baz");
assertThatSoyFile(soyFile)
.withNamespaceManifest(namespaceManifest.build())
.compilesToSourceContaining(expectedManifest);
}
@Test
public void testEnvironmentConfiguration() {
String soyFile =
SOY_NAMESPACE
+ "{template .helloWorld}\n"
+ " {call foo.bar.baz.quz /}\n"
+ "{/template}\n";
String expectedEnviromentConfirmation =
"namespaced_import('baz', namespace='foo.bar', "
+ "environment_path='runtime.custom_environment')";
assertThatSoyFile(soyFile)
.withEnvironmentModule("runtime.custom_environment")
.compilesToSourceContaining(expectedEnviromentConfirmation);
}
@Test
public void testBidiConfiguration() {
String exptectedBidiConfig = "from example import bidi as external_bidi\n";
assertThatSoyFile(DUMMY_SOY_FILE)
.withBidi("example.bidi.fn")
.compilesToSourceContaining(exptectedBidiConfig);
}
@Test
public void testTranslationConfiguration() {
String exptectedTranslationConfig =
"from example.translate import SimpleTranslator\n"
+ "translator_impl = SimpleTranslator()\n";
assertThatSoyFile(DUMMY_SOY_FILE)
.withTranslationClass("example.translate.SimpleTranslator")
.compilesToSourceContaining(exptectedTranslationConfig);
}
@Test
public void testBlankTemplate() {
String soyFile = SOY_NAMESPACE + "{template .helloWorld}\n" + "{/template}\n";
String expectedPyFile =
EXPECTED_PYFILE_START
+ "\n\n"
+ "def helloWorld(data={}, ijData={}):\n"
+ " output = []\n"
+ " return sanitize.SanitizedHtml(''.join(output), "
+ SANITIZATION_APPROVAL
+ ")\n";
assertThatSoyFile(soyFile).compilesTo(expectedPyFile);
}
@Test
public void testSimpleTemplate() {
String soyFile =
SOY_NAMESPACE + "{template .helloWorld}\n" + " Hello World!\n" + "{/template}\n";
String expectedPyFile =
EXPECTED_PYFILE_START
+ "\n\n"
+ "def helloWorld(data={}, ijData={}):\n"
+ " output = []\n"
+ " output.append('Hello World!')\n"
+ " return sanitize.SanitizedHtml(''.join(output), "
+ SANITIZATION_APPROVAL
+ ")\n";
assertThatSoyFile(soyFile).compilesTo(expectedPyFile);
}
@Test
public void testOutputScope() {
String soyFile =
SOY_NAMESPACE
+ "{template .helloWorld}\n"
+ " {@param foo : ?}\n"
+ " {@param boo : ?}\n"
+ " {if $foo}\n"
+ " {for $i in range(5)}\n"
+ " {$boo[$i]}\n"
+ " {/for}\n"
+ " {else}\n"
+ " Blah\n"
+ " {/if}\n"
+ "{/template}\n";
String expectedPyFile =
EXPECTED_PYFILE_START
+ "\n\n"
+ "def helloWorld(data={}, ijData={}):\n"
+ " output = []\n"
+ " if data.get('foo'):\n"
+ " for i### in xrange(0, 5, 1):\n"
+ " output.append(str(runtime.key_safe_data_access(data.get('boo'), i###)))\n"
+ " else:\n"
+ " output.append('Blah')\n"
+ " return sanitize.SanitizedHtml(''.join(output), "
+ SANITIZATION_APPROVAL
+ ")\n";
assertThatSoyFile(soyFile).compilesTo(expectedPyFile);
}
@Test
public void testSwitch() {
String soyCode =
"{@param boo : ?}\n"
+ "{switch $boo}\n"
+ " {case 0}\n"
+ " Hello\n"
+ " {case 1}\n"
+ " World\n"
+ " {default}\n"
+ " !\n"
+ "{/switch}\n";
String expectedPyCode =
"switchValue = data.get('boo')\n"
+ "if runtime.type_safe_eq(switchValue, 0):\n"
+ " output.append('Hello')\n"
+ "elif runtime.type_safe_eq(switchValue, 1):\n"
+ " output.append('World')\n"
+ "else:\n"
+ " output.append('!')\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testSwitch_defaultOnly() {
String soyCode =
"{@param boo : ?}\n"
+ "{switch $boo}\n"
+ " {default}\n"
+ " Hello World!\n"
+ "{/switch}\n";
String expectedPyCode = "switchValue = data.get('boo')\n" + "output.append('Hello World!')\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testFor() {
String soyCode =
"{@param boo : ?}\n" + "{for $i in range(5)}\n" + " {$boo[$i]}\n" + "{/for}\n";
String expectedPyCode =
"for i### in xrange(0, 5, 1):\n"
+ " output.append(str(runtime.key_safe_data_access(data.get('boo'), i###)))\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
soyCode = "{@param boo : ?}\n" + "{for $i in range(5, 10)}\n" + " {$boo[$i]}\n" + "{/for}\n";
expectedPyCode =
"for i### in xrange(5, 10, 1):\n"
+ " output.append(str(runtime.key_safe_data_access(data.get('boo'), i###)))\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
soyCode =
" {@param boo : ?}\n"
+ " {@param goo : ?}\n"
+ " {@param foo : ?}\n"
+ "{for $i in range($foo, $boo, $goo)}\n"
+ " {$boo[$i]}\n"
+ "{/for}\n";
expectedPyCode =
"for i### in xrange(data.get('foo'), data.get('boo'), data.get('goo')):\n"
+ " output.append(str(runtime.key_safe_data_access(data.get('boo'), i###)))\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testForeach() {
String soyCode =
"{@param operands : ?}\n"
+ "{foreach $operand in $operands}\n"
+ " {$operand}\n"
+ "{/foreach}\n";
// There's no simple way to account for all instances of the id in these variables, so for now
// we just hardcode '3'.
String expectedPyCode =
"operandList### = data.get('operands')\n"
+ "for operandIndex###, operandData### in enumerate(operandList###):\n"
+ " output.append(str(operandData###))\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
soyCode =
"{@param operands : ?}\n"
+ "{foreach $operand in $operands}\n"
+ " {isFirst($operand) ? 1 : 0}\n"
+ " {isLast($operand) ? 1 : 0}\n"
+ " {index($operand)}\n"
+ "{/foreach}\n";
expectedPyCode =
"operandList### = data.get('operands')\n"
+ "for operandIndex###, operandData### in enumerate(operandList###):\n"
+ " output.extend([str(1 if operandIndex### == 0 else 0),"
+ "str(1 if operandIndex### == len(operandList###) - 1 else 0),"
+ "str(operandIndex###)])\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testForeach_ifempty() {
String soyCode =
"{@param operands : ?}\n"
+ "{@param foo : ?}\n"
+ "{foreach $operand in $operands}\n"
+ " {$operand}\n"
+ "{ifempty}\n"
+ " {$foo}"
+ "{/foreach}\n";
String expectedPyCode =
"operandList### = data.get('operands')\n"
+ "if operandList###:\n"
+ " for operandIndex###, operandData### in enumerate(operandList###):\n"
+ " output.append(str(operandData###))\n"
+ "else:\n"
+ " output.append(str(data.get('foo')))\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testLetValue() {
assertThatSoyCode("{@param boo : ?}\n" + "{let $foo: $boo /}\n")
.compilesTo("foo__soy### = data.get('boo')\n");
}
@Test
public void testLetContent() {
String soyCode =
"{@param boo : ?}\n" + "{let $foo kind=\"html\"}\n" + " Hello {$boo}\n" + "{/let}\n";
String expectedPyCode =
"foo__soy### = ['Hello ',str(data.get('boo'))]\n"
+ "foo__soy### = sanitize.SanitizedHtml(''.join(foo__soy###), "
+ SANITIZATION_APPROVAL
+ ")\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testLetContent_notComputableAsExpr() {
String soyCode =
"{@param boo : ?}\n"
+ "{let $foo kind=\"html\"}\n"
+ " {for $num in range(5)}\n"
+ " {$num}\n"
+ " {/for}\n"
+ " Hello {$boo}\n"
+ "{/let}\n";
String expectedPyCode =
"foo__soy### = []\n"
+ "for num### in xrange(0, 5, 1):\n"
+ " foo__soy###.append(str(num###))\n"
+ "foo__soy###.extend(['Hello ',str(data.get('boo'))])\n"
+ "foo__soy### = sanitize.SanitizedHtml(''.join(foo__soy###), "
+ SANITIZATION_APPROVAL
+ ")\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testLog() {
String soyCode = "{@param boo : ?}\n" + "{log}\n" + " {$boo}\n" + "{/log}\n";
String expectedPyCode =
"logger_5 = []\n" + "logger_5.append(str(data.get('boo')))\n" + "print logger_5\n" + "";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testDebugger() {
String soyCode = "{debugger}";
String expectedPyCode = "pdb.set_trace()\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
@Test
public void testLetContent_noContentKind() {
String soyCode = "{@param boo : ?}\n" + "{let $foo}\n" + " Hello {$boo}\n" + "{/let}\n";
assertThatSoyCode(soyCode).compilesWithException(SoySyntaxException.class);
}
@Test
public void testCallReturnsString() {
String soyCode = "{call .foo data=\"all\" /}";
String expectedPyCode = "output.append(str(ns.foo(data, ijData)))\n";
assertThatSoyCode(soyCode).compilesTo(expectedPyCode);
}
}