Skip to content

Commit 6935ae4

Browse files
brad4dcopybara-github
authored andcommitted
Add tests to cover a problem with declareIcuTemplate message replacement.
PiperOrigin-RevId: 698863220
1 parent a79b5ff commit 6935ae4

File tree

2 files changed

+130
-15
lines changed

2 files changed

+130
-15
lines changed

test/com/google/javascript/jscomp/JsMessageExtractorTest.java

+69-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.common.base.Joiner;
2323
import com.google.common.collect.ImmutableMap;
24+
import com.google.javascript.jscomp.JsMessage.Part;
2425
import java.util.ArrayList;
2526
import java.util.Collection;
2627
import java.util.Iterator;
@@ -33,14 +34,44 @@
3334
@RunWith(JUnit4.class)
3435
public final class JsMessageExtractorTest {
3536

37+
// Generate IDs of the form `MEANING_PARTCOUNT[PARTCOUNT...]`
38+
// PARTCOUNT = 'sN' for a string part with N == string length
39+
// PARTCOUNT = 'pN' for a placeholder with N == length of the canonical placeholder name
40+
public static final JsMessage.IdGenerator TEST_ID_GENERATOR =
41+
new JsMessage.IdGenerator() {
42+
@Override
43+
public String generateId(String meaning, List<Part> messageParts) {
44+
StringBuilder idBuilder = new StringBuilder();
45+
idBuilder.append(meaning).append('_');
46+
for (Part messagePart : messageParts) {
47+
if (messagePart.isPlaceholder()) {
48+
idBuilder.append('p').append(messagePart.getCanonicalPlaceholderName().length());
49+
} else {
50+
idBuilder.append('s').append(messagePart.getString().length());
51+
}
52+
}
53+
54+
return idBuilder.toString();
55+
}
56+
};
57+
3658
private Collection<JsMessage> extractMessages(String... js) {
59+
return extractMessages(/* idGenerator= */ null, js);
60+
}
61+
62+
private static Collection<JsMessage> extractMessages(
63+
JsMessage.IdGenerator idGenerator, String[] js) {
3764
String sourceCode = Joiner.on("\n").join(js);
38-
return new JsMessageExtractor(null)
65+
return new JsMessageExtractor(idGenerator)
3966
.extractMessages(SourceFile.fromCode("testcode", sourceCode));
4067
}
4168

4269
private JsMessage extractMessage(String... js) {
43-
Collection<JsMessage> messages = extractMessages(js);
70+
return extractMessage(/* idGenerator= */ null, js);
71+
}
72+
73+
private JsMessage extractMessage(JsMessage.IdGenerator idGenerator, String... js) {
74+
Collection<JsMessage> messages = extractMessages(/* idGenerator= */ idGenerator, js);
4475
assertThat(messages).hasSize(1);
4576
return messages.iterator().next();
4677
}
@@ -120,6 +151,42 @@ public void testOriginalCodeAndExampleMaps() {
120151
");"));
121152
}
122153

154+
@Test
155+
public void testOriginalCodeAndExampleMapsForDeclareIcuTemplate() {
156+
// A message with placeholders and original code annotations.
157+
assertEquals(
158+
new JsMessage.Builder()
159+
.setKey("MSG_WELCOME")
160+
.appendStringPart("Hi ") // "s3" in the ID
161+
.appendCanonicalPlaceholderReference("INTERPOLATION_0") // "p15" in the ID
162+
.appendStringPart("! Welcome to ") // "s13" in the ID
163+
.appendCanonicalPlaceholderReference("INTERPOLATION_1") // "p15" in the ID
164+
.appendStringPart(".") // "s1" in the ID
165+
.setPlaceholderNameToOriginalCodeMap(
166+
ImmutableMap.of(
167+
"INTERPOLATION_0", "foo.getUserName()",
168+
"INTERPOLATION_1", "bar.getProductName()"))
169+
.setPlaceholderNameToExampleMap(
170+
ImmutableMap.of(
171+
"INTERPOLATION_0", "Ginny Weasley",
172+
"INTERPOLATION_1", "Google Muggle Finder"))
173+
.setDesc("The welcome message.")
174+
.setId("MSG_WELCOME_s3p15s13p15s1")
175+
.build(),
176+
extractMessage(
177+
TEST_ID_GENERATOR,
178+
"var MSG_WELCOME = declareIcuTemplate(",
179+
" 'Hi {INTERPOLATION_0}! Welcome to {INTERPOLATION_1}.',",
180+
" {",
181+
" description: 'The welcome message.',",
182+
" example: {",
183+
" 'INTERPOLATION_0': 'Ginny Weasley',",
184+
" 'INTERPOLATION_1': 'Google Muggle Finder',",
185+
" },",
186+
" },",
187+
");"));
188+
}
189+
123190
@Test
124191
public void testExtractNewStyleMessage2() {
125192
// A message with placeholders and meta data.

test/com/google/javascript/jscomp/ReplaceMessagesTest.java

+61-13
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@
2121
import static com.google.javascript.jscomp.JsMessageVisitor.MESSAGE_TREE_MALFORMED;
2222
import static com.google.javascript.rhino.testing.NodeSubject.assertNode;
2323

24+
import com.google.common.collect.ImmutableList;
25+
import com.google.javascript.jscomp.JsMessage.Part;
26+
import com.google.javascript.jscomp.JsMessage.PlaceholderReference;
27+
import com.google.javascript.jscomp.JsMessage.StringPart;
2428
import com.google.javascript.rhino.Node;
2529
import com.google.javascript.rhino.Node.SideEffectFlags;
2630
import java.util.HashMap;
31+
import java.util.List;
2732
import java.util.Map;
2833
import org.junit.Before;
34+
import org.junit.Ignore;
2935
import org.junit.Test;
3036
import org.junit.runner.RunWith;
3137
import org.junit.runners.JUnit4;
@@ -34,6 +40,27 @@
3440
@RunWith(JUnit4.class)
3541
public final class ReplaceMessagesTest extends CompilerTestCase {
3642

43+
// Generate IDs of the form `MEANING_PARTCOUNT[PARTCOUNT...]`
44+
// PARTCOUNT = 'sN' for a string part with N == string length
45+
// PARTCOUNT = 'pN' for a placeholder with N == length of the canonical placeholder name
46+
public static final JsMessage.IdGenerator TEST_ID_GENERATOR =
47+
new JsMessage.IdGenerator() {
48+
@Override
49+
public String generateId(String meaning, List<Part> messageParts) {
50+
StringBuilder idBuilder = new StringBuilder();
51+
idBuilder.append(meaning).append('_');
52+
for (Part messagePart : messageParts) {
53+
if (messagePart.isPlaceholder()) {
54+
idBuilder.append('p').append(messagePart.getCanonicalPlaceholderName().length());
55+
} else {
56+
idBuilder.append('s').append(messagePart.getString().length());
57+
}
58+
}
59+
60+
return idBuilder.toString();
61+
}
62+
};
63+
3764
/** Indicates which part of the replacement we're currently testing */
3865
enum TestMode {
3966
// Test full replacement from `goog.getMsg()` to final message values.
@@ -57,7 +84,12 @@ enum TestMode {
5784

5885
// Messages returned from fake bundle, keyed by `JsMessage.id`.
5986
private Map<String, JsMessage> messages;
87+
// If `true` report errors for messages that are not found in the bundle.
6088
private boolean strictReplacement;
89+
// If `true` pass TEST_ID_GENERATOR in to ReplaceMessages via the fake bundle, so it will be
90+
// used to calculate the message IDs from the meaning and parts instead of just using the message
91+
// key as its id.
92+
private boolean useTestIdGenerator;
6193
private TestMode testMode = TestMode.FULL_REPLACE;
6294

6395
@Override
@@ -188,6 +220,7 @@ public void setUp() throws Exception {
188220
super.setUp();
189221
messages = new HashMap<>();
190222
strictReplacement = false;
223+
useTestIdGenerator = false;
191224
enableTypeCheck();
192225
replaceTypesWithColors();
193226
enableTypeInfoValidation();
@@ -295,18 +328,28 @@ public void testReplaceExternalMessage() {
295328
}
296329

297330
@Test
331+
@Ignore // TODO(b/378574591): fix the bug this test exposes
298332
public void testReplaceIcuTemplateMessageWithBundleAndJsPlaceholders() {
299-
// Message in the bundle has a placeholder and is NOT in ICU selector format.
300-
//
301-
// (i.e. it does not start with "{WORD,").
302-
//
303-
// Here we want to make sure that messages created with declareIcuTemplate()
304-
// get treated as ICU messages even without that distinguishing feature.
305-
registerMessage(
306-
getTestMessageBuilder("MSG_SHOW_EMAIL")
307-
.appendStringPart("Retpoŝtadreso: ")
308-
.appendCanonicalPlaceholderReference("EMAIL")
309-
.build());
333+
useTestIdGenerator = true;
334+
strictReplacement = true;
335+
336+
String meaning = "MSG_SHOW_EMAIL";
337+
Part originalStringPart = StringPart.create("Email: ");
338+
Part originalPlaceholerPart = PlaceholderReference.createForCanonicalName("EMAIL");
339+
String expectedMessageId =
340+
TEST_ID_GENERATOR.generateId(
341+
meaning, ImmutableList.of(originalStringPart, originalPlaceholerPart));
342+
343+
// Create and register the translation we expect to find in the message bundle
344+
final JsMessage showEmailTranslatedMsg =
345+
new JsMessage.Builder()
346+
.setKey(meaning)
347+
.setMeaning(meaning)
348+
.appendStringPart("Retpoŝtadreso: ") // translated string
349+
.appendPart(originalPlaceholerPart) // placeholder is the same as original
350+
.setId(expectedMessageId) // message ID was calculated from the original
351+
.build();
352+
registerMessage(showEmailTranslatedMsg);
310353

311354
multiPhaseTest(
312355
lines(
@@ -327,6 +370,12 @@ public void testReplaceIcuTemplateMessageWithBundleAndJsPlaceholders() {
327370
lines(
328371
"const {declareIcuTemplate} = goog.require('goog.i18n.messages');",
329372
"",
373+
// TODO(b/378574591): The information gathered here isn't enough to correctly generate
374+
// the message ID for lookup. Since the "example" information is dropped, we won't
375+
// know that we need to split the message text into parts, treating "EMAIL" as a
376+
// placeholder. As a result, we calculate the message ID using just a single string
377+
// part, so we get a different message ID than we got during extraction, and we
378+
// won't be able to find the message.
330379
"const MSG_SHOW_EMAIL =",
331380
" __jscomp_define_msg__(",
332381
" {",
@@ -2107,7 +2156,6 @@ private void registerMessage(JsMessage message) {
21072156
}
21082157

21092158
private class SimpleMessageBundle implements MessageBundle {
2110-
21112159
@Override
21122160
public JsMessage getMessage(String id) {
21132161
return messages.get(id);
@@ -2120,7 +2168,7 @@ public Iterable<JsMessage> getAllMessages() {
21202168

21212169
@Override
21222170
public JsMessage.IdGenerator idGenerator() {
2123-
return null;
2171+
return useTestIdGenerator ? TEST_ID_GENERATOR : null;
21242172
}
21252173
}
21262174
}

0 commit comments

Comments
 (0)