/*
* 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.jssrc.internal;
import static com.google.common.truth.Truth.assertThat;
import static com.google.template.soy.jssrc.dsl.CodeChunk.id;
import static com.google.template.soy.jssrc.dsl.CodeChunk.number;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.template.soy.SoyFileSetParserBuilder;
import com.google.template.soy.SoyModule;
import com.google.template.soy.base.internal.UniqueNameGenerator;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.ExplodingErrorReporter;
import com.google.template.soy.jssrc.SoyJsSrcOptions;
import com.google.template.soy.jssrc.dsl.CodeChunk;
import com.google.template.soy.jssrc.internal.GenJsExprsVisitor.GenJsExprsVisitorFactory;
import com.google.template.soy.jssrc.restricted.JsExpr;
import com.google.template.soy.shared.SharedTestUtils;
import com.google.template.soy.shared.internal.GuiceSimpleScope;
import com.google.template.soy.soytree.SoyFileSetNode;
import com.google.template.soy.soytree.SoyNode;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for GenJsExprsVisitor.
*
*/
@RunWith(JUnit4.class)
public final class GenJsExprsVisitorTest {
private static final Joiner JOINER = Joiner.on('\n');
private static final Injector INJECTOR = Guice.createInjector(new SoyModule());
// Let 'goo' simulate a local variable from a 'foreach' loop.
private static final ImmutableMap<String, CodeChunk.WithValue> LOCAL_VAR_TRANSLATIONS =
ImmutableMap.<String, CodeChunk.WithValue>builder()
.put(
"goo",
id("gooData8"))
.put(
"goo__isFirst",
id("gooIndex8")
.doubleEquals(
number(0)))
.put(
"goo__isLast",
id("gooIndex8")
.doubleEquals(
id("gooListLen8")
.minus(
number(1))))
.put(
"goo__index",
id("gooIndex8"))
.build();
private GuiceSimpleScope.InScope inScope;
@Before
public void setUp() {
SoyJsSrcOptions jsSrcOptions = new SoyJsSrcOptions();
inScope = JsSrcTestUtils.simulateNewApiCall(INJECTOR, jsSrcOptions);
}
@After
public void tearDown() {
inScope.close();
}
@Test
public void testRawText() {
assertGeneratedChunks(
"I'm feeling lucky!",
"'I\\'m feeling lucky!'");
assertGeneratedChunks("</script>", "'<\\/script>'");
// Ensure Unicode gets escaped, since there's no guarantee about the output encoding of the JS.
assertGeneratedChunks(
"More \u00BB",
"'More \\u00BB'");
}
@Test
public void testMsgHtmlTag() {
assertGeneratedJsExprs(JOINER.join(
"{@param url : ?}",
"{msg desc=\"\"}<a href=\"{$url}\">Click here</a>{/msg}"),
ImmutableList.of(
new JsExpr("'<a href=\"'", Integer.MAX_VALUE),
new JsExpr("opt_data.url", Integer.MAX_VALUE),
new JsExpr("'\">'", Integer.MAX_VALUE)),
0,
0,
0,
0);
assertGeneratedJsExprs(
"{@param url : ?}\n" + "{msg desc=\"\"}<a href=\"{$url}\">Click here</a>{/msg}",
ImmutableList.of(new JsExpr("'</a>'", Integer.MAX_VALUE)),
0,
0,
0,
2);
}
@Test
public void testPrint() {
assertGeneratedChunks(JOINER.join(
"{@param boo : ?}",
"{$boo.foo}"),
"opt_data.boo.foo");
assertGeneratedChunks(JOINER.join(
"{@param goo : ?}",
"{$goo.moo}"),
"gooData8.moo");
assertGeneratedChunks(JOINER.join(
"{@param goo : ?}",
"{isNonnull($goo)+1}"),
"(gooData8 != null) + 1");
}
@Test
public void testPrint_nonExpr() {
String soyNodeCode = JOINER.join(
"{@param boo : string}",
"{(['a': 'b', $boo: 'c'])[$boo]}");
String expectedGenCode =
JOINER.join(
"var $tmp = {a: 'b'};",
"$tmp[soy.$$checkMapKey(opt_data.boo)] = 'c';",
"$tmp[opt_data.boo]");
assertGeneratedChunks(soyNodeCode, expectedGenCode);
}
@Test
public void testXid() {
assertGeneratedChunks(
"{xid selected-option}",
"xid('selected-option')");
assertGeneratedChunks(
"{xid selected.option}",
"xid('selected.option')");
}
@Test
public void testCss() {
assertGeneratedChunks(
"{css selected-option}",
"goog.getCssName('selected-option')");
assertGeneratedChunks(JOINER.join(
"{@param foo : ?}",
"{css $foo, bar}"),
"goog.getCssName(opt_data.foo, 'bar')");
}
@Test
public void testIf() {
String soyNodeCode = JOINER.join(
"{@param boo : ?}",
"{@param goo : ?}",
"{if $boo}",
" Blah",
"{elseif not isNonnull($goo)}",
" Bleh",
"{else}",
" Bluh",
"{/if}");
String expectedJsExprText =
JOINER.join(
"var $tmp = null;",
"if (opt_data.boo) {",
" $tmp = 'Blah';",
"} else if (!(gooData8 != null)) {",
" $tmp = 'Bleh';",
"} else {",
" $tmp = 'Bluh';",
"}");
assertGeneratedChunks(soyNodeCode, expectedJsExprText);
}
@Test
public void testIfNoElse() {
String soyNodeCode =
JOINER.join(
"{@param boo : ?}",
"{@param goo : ?}",
"{if $boo}",
" Blah",
"{elseif not isNonnull($goo)}",
" Bleh",
"{/if}");
String expectedJsExprText =
JOINER.join(
"var $tmp = null;",
"if (opt_data.boo) {",
" $tmp = 'Blah';",
"} else if (!(gooData8 != null)) {",
" $tmp = 'Bleh';",
"} else {",
" $tmp = '';",
"}");
assertGeneratedChunks(soyNodeCode, expectedJsExprText);
}
@Test
public void testIfTernary() {
String soyNodeCode = JOINER.join(
"{@param boo : ?}",
"{if $boo}",
" Blah",
"{else}",
" Bleh",
"{/if}");
String expectedJsExprText = "opt_data.boo ? 'Blah' : 'Bleh'";
assertGeneratedChunks(soyNodeCode, expectedJsExprText);
}
@Test
public void testCall() {
assertGeneratedChunks(
"{call some.func data=\"all\" /}",
"some.func(opt_data, null, opt_ijData)");
String soyNodeCode = JOINER.join(
"{@param boo : ?}",
"{call some.func data=\"$boo.foo\" /}");
assertGeneratedChunks(
soyNodeCode,
"some.func(opt_data.boo.foo, null, opt_ijData)");
soyNodeCode = JOINER.join(
"{@param moo : ?}",
"{call some.func}",
" {param goo: $moo /}",
"{/call}");
assertGeneratedChunks(
soyNodeCode,
"some.func({goo: opt_data.moo}, null, opt_ijData)");
soyNodeCode =
JOINER.join(
"{@param boo : ?}",
"{call some.func data=\"$boo\"}",
" {param goo}Blah{/param}",
"{/call}");
assertGeneratedChunks(
soyNodeCode,
"some.func(soy.$$assignDefaults({goo: 'Blah'}, opt_data.boo), null, opt_ijData)");
}
@Test
public void testBlocks() {
String soyNodeCode = JOINER.join(
"{@param boo : ?}",
"{if $boo}",
" Blah {$boo} bleh.",
"{/if}");
String expectedJsExprText = "opt_data.boo ? 'Blah ' + opt_data.boo + ' bleh.' : ''";
assertGeneratedChunks(soyNodeCode, expectedJsExprText);
soyNodeCode =
JOINER.join(
"{@param goo : ?}",
"{call some.func}",
" {param goo}{lb}{isNonnull($goo)}{rb} is {$goo.moo}{/param}",
"{/call}");
expectedJsExprText =
"some.func({goo: '{' + (gooData8 != null) + '} is ' + gooData8.moo}, null, opt_ijData)";
assertGeneratedChunks(soyNodeCode, expectedJsExprText);
}
private static void assertGeneratedChunks(String soyNodeCode, String... expectedChunks) {
List<CodeChunk.WithValue> actualChunks = generateChunks(soyNodeCode, 0);
assertThat(actualChunks).hasSize(expectedChunks.length);
for (int i = 0; i < actualChunks.size(); i++) {
CodeChunk.WithValue actual = actualChunks.get(i);
String expected = expectedChunks[i];
assertThat(actual.getExpressionTestOnly()).isEqualTo(expected);
}
}
/** @param indicesToNode Series of indices for walking down to the node we want to test. */
private static void assertGeneratedJsExprs(
String soyCode, List<JsExpr> expectedJsExprs, int... indicesToNode) {
List<CodeChunk.WithValue> actualChunks = generateChunks(soyCode, indicesToNode);
List<JsExpr> actualJsExprs = new ArrayList<>();
for (CodeChunk.WithValue chunk : actualChunks) {
actualJsExprs.add(chunk.assertExpr()); // TODO(user): Fix tests to work with CodeChunks
}
assertThat(actualJsExprs).hasSize(expectedJsExprs.size());
for (int i = 0; i < expectedJsExprs.size(); i++) {
JsExpr expectedJsExpr = expectedJsExprs.get(i);
JsExpr actualJsExpr = actualJsExprs.get(i);
assertThat(actualJsExpr.getText()).isEqualTo(expectedJsExpr.getText());
assertThat(actualJsExpr.getPrecedence()).isEqualTo(expectedJsExpr.getPrecedence());
}
}
private static List<CodeChunk.WithValue> generateChunks(String soyCode, int... indicesToNode) {
ErrorReporter boom = ExplodingErrorReporter.get();
SoyFileSetNode soyTree =
SoyFileSetParserBuilder.forTemplateContents(soyCode).errorReporter(boom).parse().fileSet();
// Required by testPrintGoogMsg.
new ExtractMsgVariablesVisitor().exec(soyTree);
SoyNode node = SharedTestUtils.getNode(soyTree, indicesToNode);
UniqueNameGenerator nameGenerator = JsSrcNameGenerators.forLocalVariables();
GenJsExprsVisitor visitor =
INJECTOR
.getInstance(GenJsExprsVisitorFactory.class)
.create(
TranslationContext.of(
SoyToJsVariableMappings.startingWith(LOCAL_VAR_TRANSLATIONS),
CodeChunk.Generator.create(nameGenerator),
nameGenerator),
AliasUtils.IDENTITY_ALIASES,
boom);
return visitor.exec(node);
}
}