/*
* 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.SoyExprForPySubject.assertThatSoyExpr;
import com.google.template.soy.exprtree.Operator;
import com.google.template.soy.pysrc.restricted.PyExpr;
import com.google.template.soy.pysrc.restricted.PyExprUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for GenPyExprsVisitor.
*
*/
@RunWith(JUnit4.class)
public final class GenPyExprsVisitorTest {
@Test
public void testRawText() {
assertThatSoyExpr("I'm feeling lucky!")
.compilesTo(new PyExpr("'I\\'m feeling lucky!'", Integer.MAX_VALUE));
}
@Test
public void testCss() {
assertThatSoyExpr("{css primary}")
.compilesTo(new PyExpr("runtime.get_css_name('primary')", Integer.MAX_VALUE));
assertThatSoyExpr("{@param foo:?}\n{css $foo, bar}")
.compilesTo(new PyExpr("runtime.get_css_name(data.get('foo'), 'bar')", Integer.MAX_VALUE));
}
@Test
public void testXid() {
assertThatSoyExpr("{xid primary}")
.compilesTo(new PyExpr("runtime.get_xid_name('primary')", Integer.MAX_VALUE));
}
@Test
public void testIf() {
String soyNodeCode =
"{@param boo:?}\n"
+ "{@param goo:?}\n"
+ "{if $boo}\n"
+ " Blah\n"
+ "{elseif not $goo}\n"
+ " Bleh\n"
+ "{else}\n"
+ " Bluh\n"
+ "{/if}\n";
String expectedPyExprText =
"'Blah' if data.get('boo') else 'Bleh' if not data.get('goo') else 'Bluh'";
assertThatSoyExpr(soyNodeCode)
.compilesTo(
new PyExpr(
expectedPyExprText, PyExprUtils.pyPrecedenceForOperator(Operator.CONDITIONAL)));
}
@Test
public void testIf_nested() {
String soyNodeCode =
"{@param boo:?}\n"
+ "{@param goo:?}\n"
+ "{if $boo}\n"
+ " {if $goo}\n"
+ " Blah\n"
+ " {/if}\n"
+ "{else}\n"
+ " Bleh\n"
+ "{/if}\n";
String expectedPyExprText =
"('Blah' if data.get('goo') else '') if data.get('boo') else 'Bleh'";
assertThatSoyExpr(soyNodeCode)
.compilesTo(
new PyExpr(
expectedPyExprText, PyExprUtils.pyPrecedenceForOperator(Operator.CONDITIONAL)));
}
@Test
public void testSimpleMsgFallbackGroupNodeWithOneNode() {
String soyCode =
"{msg meaning=\"verb\" desc=\"Used as a verb.\"}\n" + " Archive\n" + "{/msg}\n";
String expectedPyCode =
"translator_impl.render_literal("
+ "translator_impl.prepare_literal("
+ "###, "
+ "'Archive'))";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgFallbackGroupNodeWithTwoNodes() {
String soyCode =
"{msg meaning=\"verb\" desc=\"Used as a verb.\"}\n"
+ " archive\n"
+ "{fallbackmsg desc=\"\"}\n"
+ " ARCHIVE\n"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render_literal("
+ "translator_impl.prepare_literal("
+ "###, "
+ "'archive')) "
+ "if translator_impl.is_msg_available(###) or "
+ "not translator_impl.is_msg_available(###) "
+ "else translator_impl.render_literal("
+ "translator_impl.prepare_literal(###, 'ARCHIVE'))";
assertThatSoyExpr(soyCode)
.compilesTo(
new PyExpr(expectedPyCode, PyExprUtils.pyPrecedenceForOperator(Operator.CONDITIONAL)));
}
@Test
public void testMsgOnlyLiteral() {
String soyCode =
"{msg meaning=\"verb\" desc=\"The word 'Archive' used as a verb.\"}"
+ "Archive"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render_literal("
+ "translator_impl.prepare_literal("
+ "###, "
+ "'Archive'))";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgOnlyLiteralWithBraces() {
// Should escape '{' and '}' in format string.
// @see https://docs.python.org/2/library/string.html#formatstrings
String soyCode =
"{msg meaning=\"verb\" desc=\"The word 'Archive' used as a verb.\"}"
+ "{lb}Archive{rb}"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render_literal("
+ "translator_impl.prepare_literal("
+ "###, "
+ "'{{Archive}}'))";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgOnlyLiteralWithApostrophe() {
// Should escape '\'' in format string.
String soyCode =
"{msg meaning=\"verb\" desc=\"The word 'Archive' used as a verb.\"}"
+ "Archive's"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render_literal("
+ "translator_impl.prepare_literal("
+ "###, "
+ "'Archive\\'s'))";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgSimpleSoyExpression() {
String soyCode =
"{@param username:?}\n"
+ "{msg desc=\"var placeholder\"}"
+ "Hello {$username}"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render("
+ "translator_impl.prepare("
+ "###, "
+ "'Hello {USERNAME}', "
+ "('USERNAME',)), "
+ "{'USERNAME': str(data.get('username'))})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgMultipleSoyExpressions() {
String soyCode =
"{@param greet:?}\n"
+ "{@param username:?}\n"
+ "{msg desc=\"var placeholder\"}"
+ "{$greet} {$username}"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render("
+ "translator_impl.prepare("
+ "###, "
+ "'{GREET} {USERNAME}', "
+ "('GREET', 'USERNAME')), "
+ "{"
+ "'GREET': str(data.get('greet')), "
+ "'USERNAME': str(data.get('username'))"
+ "})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgMultipleSoyExpressionsWithBraces() {
String soyCode =
"{@param username:?}\n"
+ "{@param greet:?}\n"
+ "{msg desc=\"var placeholder\"}"
+ "{$greet} {lb}{$username}{rb}"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render("
+ "translator_impl.prepare("
+ "###, "
+ "'{GREET} {{{USERNAME}}}', "
+ "('GREET', 'USERNAME')), "
+ "{"
+ "'GREET': str(data.get('greet')), "
+ "'USERNAME': str(data.get('username'))"
+ "})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgNamespacedSoyExpression() {
String soyCode =
"{@param foo:?}\n"
+ "{msg desc=\"placeholder with namespace\"}"
+ "Hello {$foo.bar}"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render("
+ "translator_impl.prepare("
+ "###, "
+ "'Hello {BAR}', "
+ "('BAR',)), "
+ "{'BAR': str(data.get('foo').get('bar'))})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgWithArithmeticExpression() {
String soyCode =
"{@param username:?}\n"
+ "{msg desc=\"var placeholder\"}"
+ "Hello {$username + 1}"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render("
+ "translator_impl.prepare("
+ "###, "
+ "'Hello {XXX}', "
+ "('XXX',)), "
+ "{'XXX': str(runtime.type_safe_add(data.get('username'), 1))})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgWithHtmlNode() {
// msg with HTML tags and raw texts
String soyCode =
"{@param url:?}\n"
+ "{msg desc=\"with link\"}"
+ "Please click <a href='{$url}'>here</a>."
+ "{/msg}";
String expectedPyCode =
"translator_impl.render("
+ "translator_impl.prepare("
+ "###, "
+ "'Please click {START_LINK}here{END_LINK}.', "
+ "('START_LINK', 'END_LINK')), "
+ "{"
+ "'START_LINK': ''.join(['<a href=\\'',str(data.get('url')),'\\'>']), "
+ "'END_LINK': '</a>'"
+ "})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgWithPlural() {
String soyCode =
"{@param numDrafts:?}\n"
+ "{msg desc=\"simple plural\"}"
+ "{plural $numDrafts}"
+ "{case 0}No drafts"
+ "{case 1}1 draft"
+ "{default}{$numDrafts} drafts"
+ "{/plural}"
+ "{/msg}";
String expectedPyCode =
"translator_impl.render_plural("
+ "translator_impl.prepare_plural("
+ "###, "
+ "{"
+ "'=0': 'No drafts', "
+ "'=1': '1 draft', "
+ "'other': '{NUM_DRAFTS_2} drafts'"
+ "}, "
+ "('NUM_DRAFTS_1', 'NUM_DRAFTS_2')), "
+ "data.get('numDrafts'), "
+ "{"
+ "'NUM_DRAFTS_1': data.get('numDrafts'), "
+ "'NUM_DRAFTS_2': str(data.get('numDrafts'))"
+ "})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgWithPluralAndOffset() {
String soyCode =
"{@param numDrafts:?}\n"
+ "{msg desc=\"offset plural\"}"
+ "{plural $numDrafts offset=\"2\"}"
+ "{case 0}No drafts"
+ "{case 1}1 draft"
+ "{default}{remainder($numDrafts)} drafts"
+ "{/plural}"
+ "{/msg}";
String expectedPyCode =
"translator_impl.render_plural("
+ "translator_impl.prepare_plural("
+ "###, "
+ "{"
+ "'=0': 'No drafts', "
+ "'=1': '1 draft', "
+ "'other': '{XXX} drafts'"
+ "}, "
+ "('NUM_DRAFTS', 'XXX')), "
+ "data.get('numDrafts'), "
+ "{"
+ "'NUM_DRAFTS': data.get('numDrafts'), "
+ "'XXX': str(data.get('numDrafts') - 2)"
+ "})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgWithSelect() {
String soyCode =
"{@param userGender:?}\n"
+ "{@param targetGender:?}\n"
+ "{msg desc=\"...\"}\n"
+ " {select $userGender}\n"
+ " {case 'female'}\n"
+ " {select $targetGender}\n"
+ " {case 'female'}Reply to her.\n"
+ " {case 'male'}Reply to him.\n"
+ " {default}Reply to them.\n"
+ " {/select}\n"
+ " {case 'male'}\n"
+ " {select $targetGender}\n"
+ " {case 'female'}Reply to her.\n"
+ " {case 'male'}Reply to him.\n"
+ " {default}Reply to them.\n"
+ " {/select}\n"
+ " {default}\n"
+ " {select $targetGender}\n"
+ " {case 'female'}Reply to her.\n"
+ " {case 'male'}Reply to him.\n"
+ " {default}Reply to them.\n"
+ " {/select}\n"
+ " {/select}\n"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render_icu("
+ "translator_impl.prepare_icu("
+ "###, "
+ "'{USER_GENDER,select,"
+ "female{"
+ "{TARGET_GENDER,select,"
+ "female{Reply to her.}"
+ "male{Reply to him.}"
+ "other{Reply to them.}}"
+ "}"
+ "male{"
+ "{TARGET_GENDER,select,"
+ "female{Reply to her.}"
+ "male{Reply to him.}"
+ "other{Reply to them.}}"
+ "}"
+ "other{"
+ "{TARGET_GENDER,select,"
+ "female{Reply to her.}"
+ "male{Reply to him.}"
+ "other{Reply to them.}}"
+ "}"
+ "}', "
+ "('USER_GENDER', 'TARGET_GENDER')), "
+ "{"
+ "'USER_GENDER': data.get('userGender'), "
+ "'TARGET_GENDER': data.get('targetGender')"
+ "})";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
@Test
public void testMsgWithPluralWithGender() {
String soyCode =
"{@param people:?}\n"
+ "{msg genders=\"$people[0]?.gender, $people[1]?.gender\" desc=\"plural w offsets\"}\n"
+ " {plural length($people)}\n"
+ " {case 1}{$people[0].name} is attending\n"
+ " {case 2}{$people[0].name} and {$people[1]?.name} are attending\n"
+ " {case 3}{$people[0].name}, {$people[1]?.name}, and 1 other are attending\n"
+ " {default}{$people[0].name}, {$people[1]?.name}, and length($people) others\n"
+ " {/plural}\n"
+ "{/msg}\n";
String expectedPyCode =
"translator_impl.render_icu"
+ "(translator_impl.prepare_icu"
+ "(###, "
+ "'{PEOPLE_0_GENDER,select,"
+ "female{{PEOPLE_1_GENDER,select,"
+ "female{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}"
+ "}"
+ "male{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}"
+ "}"
+ "other{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}}"
+ "}"
+ "}"
+ "male{{PEOPLE_1_GENDER,select,"
+ "female{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}"
+ "}"
+ "male{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}"
+ "}"
+ "other{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}}"
+ "}"
+ "}"
+ "other{{PEOPLE_1_GENDER,select,"
+ "female{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}"
+ "}"
+ "male{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}"
+ "}"
+ "other{{NUM,plural,"
+ "=1{{NAME_1} is attending}"
+ "=2{{NAME_1} and {NAME_2} are attending}"
+ "=3{{NAME_1}, {NAME_2}, and 1 other are attending}"
+ "other{{NAME_1}, {NAME_2}, and length($people) others}}}"
+ "}"
+ "}"
+ "}', "
+ "('PEOPLE_0_GENDER', 'PEOPLE_1_GENDER', 'NUM', 'NAME_1', 'NAME_2')), "
+ "{"
+ "'PEOPLE_0_GENDER': None "
+ "if runtime.key_safe_data_access(data.get('people'), 0) is None "
+ "else runtime.key_safe_data_access(data.get('people'), 0).get('gender'), "
+ "'PEOPLE_1_GENDER': None "
+ "if runtime.key_safe_data_access(data.get('people'), 1) is None "
+ "else runtime.key_safe_data_access(data.get('people'), 1).get('gender'), "
+ "'NUM': len(data.get('people')), "
+ "'NAME_1': str(runtime.key_safe_data_access(data.get('people'), 0).get('name')), "
+ "'NAME_2': str(None "
+ "if runtime.key_safe_data_access(data.get('people'), 1) is None "
+ "else runtime.key_safe_data_access(data.get('people'), 1).get('name'))"
+ "}"
+ ")";
assertThatSoyExpr(soyCode).compilesTo(new PyExpr(expectedPyCode, Integer.MAX_VALUE));
}
}