xref: /aosp_15_r20/external/icu/icu4c/source/test/intltest/messageformat2test_features.cpp (revision 0e209d3975ff4a8c132096b14b0e9364a753506e)
1 // © 2024 and later: Unicode, Inc. and others.
2 
3 #include "unicode/utypes.h"
4 
5 #if !UCONFIG_NO_FORMATTING
6 
7 #if !UCONFIG_NO_MF2
8 
9 #include "unicode/gregocal.h"
10 #include "messageformat2test.h"
11 
12 using namespace icu::message2;
13 using namespace data_model;
14 
15 /*
16   Tests based on ICU4J's MessageFormat2Test.java
17 and Mf2FeaturesTest.java
18 */
19 
20 /*
21   TODO: Tests need to be unified in a single format that
22   both ICU4C and ICU4J can use, rather than being embedded in code.
23 */
24 
25 /*
26 Tests reflect the syntax specified in
27 
28   https://github.com/unicode-org/message-format-wg/commits/main/spec/message.abnf
29 
30 as of the following commit from 2023-05-09:
31   https://github.com/unicode-org/message-format-wg/commit/194f6efcec5bf396df36a19bd6fa78d1fa2e0867
32 
33 */
34 
testEmptyMessage(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)35 void TestMessageFormat2::testEmptyMessage(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
36     TestUtils::runTestCase(*this, testBuilder.setPattern("")
37                            .setExpected("")
38                            .build(), errorCode);
39 }
40 
testPlainText(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)41 void TestMessageFormat2::testPlainText(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
42     TestUtils::runTestCase(*this, testBuilder.setPattern("Hello World!")
43                            .setExpected("Hello World!")
44                            .build(), errorCode);
45 }
46 
testPlaceholders(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)47 void TestMessageFormat2::testPlaceholders(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
48     TestUtils::runTestCase(*this, testBuilder.setPattern("Hello, {$userName}!")
49                                 .setExpected("Hello, John!")
50                                 .setArgument("userName", "John")
51                                 .build(), errorCode);
52 }
53 
testArgumentMissing(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)54 void TestMessageFormat2::testArgumentMissing(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
55     CHECK_ERROR(errorCode);
56 
57     UnicodeString message = "Hello {$name}, today is {$today :date style=long}.";
58     LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
59     CHECK_ERROR(errorCode);
60 
61     // November 23, 2022 at 7:42:37.123 PM
62     cal->set(2022, Calendar::NOVEMBER, 23, 19, 42, 37);
63     UDate TEST_DATE = cal->getTime(errorCode);
64     CHECK_ERROR(errorCode);
65 
66     TestCase test = testBuilder.setPattern(message)
67         .clearArguments()
68         .setArgument("name", "John")
69         .setDateArgument("today", TEST_DATE)
70         .setExpected("Hello John, today is November 23, 2022.")
71         .build();
72     TestUtils::runTestCase(*this, test, errorCode);
73 
74     // Missing date argument
75     test = testBuilder.setPattern(message)
76                                 .clearArguments()
77                                 .setArgument("name", "John")
78                                 .setExpected("Hello John, today is {$today}.")
79                                 .setExpectedError(U_MF_UNRESOLVED_VARIABLE_ERROR)
80                                 .build();
81     TestUtils::runTestCase(*this, test, errorCode);
82     test = testBuilder.setPattern(message)
83                                 .clearArguments()
84                                 .setDateArgument("today", TEST_DATE)
85                                 .setExpectedError(U_MF_UNRESOLVED_VARIABLE_ERROR)
86                                 .setExpected("Hello {$name}, today is November 23, 2022.")
87                                 .build();
88     TestUtils::runTestCase(*this, test, errorCode);
89 
90     // Both arguments missing
91     test = testBuilder.setPattern(message)
92                                 .clearArguments()
93                                 .setExpectedError(U_MF_UNRESOLVED_VARIABLE_ERROR)
94                                 .setExpected("Hello {$name}, today is {$today}.")
95                                 .build();
96     TestUtils::runTestCase(*this, test, errorCode);
97 }
98 
testDefaultLocale(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)99 void TestMessageFormat2::testDefaultLocale(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
100     CHECK_ERROR(errorCode);
101 
102     LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
103     CHECK_ERROR(errorCode);
104     // November 23, 2022 at 7:42:37.123 PM
105     cal->set(2022, Calendar::NOVEMBER, 23, 19, 42, 37);
106     UDate TEST_DATE = cal->getTime(errorCode);
107     CHECK_ERROR(errorCode);
108 
109     UnicodeString message = "Date: {$date :date style=long}.";
110     UnicodeString expectedEn = "Date: November 23, 2022.";
111     UnicodeString expectedRo = "Date: 23 noiembrie 2022.";
112 
113     testBuilder.setPattern(message);
114 
115     TestCase test = testBuilder.clearArguments()
116         .setDateArgument("date", TEST_DATE)
117         .setExpected(expectedEn)
118         .setExpectSuccess()
119         .build();
120     TestUtils::runTestCase(*this, test, errorCode);
121     test = testBuilder.setExpected(expectedRo)
122                                 .setLocale(Locale("ro"))
123                                 .build();
124     TestUtils::runTestCase(*this, test, errorCode);
125 
126     Locale originalLocale = Locale::getDefault();
127     Locale::setDefault(Locale::forLanguageTag("ro", errorCode), errorCode);
128     CHECK_ERROR(errorCode);
129 
130     test = testBuilder.setExpected(expectedEn)
131                                 .setLocale(Locale("en", "US"))
132                                 .build();
133     TestUtils::runTestCase(*this, test, errorCode);
134     test = testBuilder.setExpected(expectedRo)
135                                 .setLocale(Locale::forLanguageTag("ro", errorCode))
136                                 .build();
137     TestUtils::runTestCase(*this, test, errorCode);
138 
139     Locale::setDefault(originalLocale, errorCode);
140     CHECK_ERROR(errorCode);
141 }
142 
testSpecialPluralWithDecimals(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)143 void TestMessageFormat2::testSpecialPluralWithDecimals(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
144     CHECK_ERROR(errorCode);
145 
146     UnicodeString message;
147 
148     message = ".local $amount = {$count :number}\n\
149                 .match {$amount :number}\n\
150                   1 {{I have {$amount} dollar.}}\n\
151                   * {{I have {$amount} dollars.}}";
152 
153     TestCase test = testBuilder.setPattern(message)
154         .clearArguments()
155         .setArgument("count", (int64_t) 1)
156         .setExpected("I have 1 dollar.")
157         .setLocale(Locale("en", "US"))
158         .build();
159     TestUtils::runTestCase(*this, test, errorCode);
160 }
161 
testDefaultFunctionAndOptions(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)162 void TestMessageFormat2::testDefaultFunctionAndOptions(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
163     CHECK_ERROR(errorCode);
164 
165     LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
166     CHECK_ERROR(errorCode);
167     // November 23, 2022 at 7:42:37.123 PM
168     cal->set(2022, Calendar::NOVEMBER, 23, 19, 42, 37);
169     UDate TEST_DATE = cal->getTime(errorCode);
170     CHECK_ERROR(errorCode);
171 
172     TestCase test = testBuilder.setPattern("Testing date formatting: {$date}.")
173         .clearArguments()
174         .setDateArgument("date", TEST_DATE)
175         .setExpected("Testing date formatting: 23.11.2022, 19:42.")
176         .setLocale(Locale("ro"))
177         .build();
178     TestUtils::runTestCase(*this, test, errorCode);
179     test = testBuilder.setPattern("Testing date formatting: {$date :datetime}.")
180                                 .setExpected("Testing date formatting: 23.11.2022, 19:42.")
181                                 .setLocale(Locale("ro"))
182                                 .build();
183     TestUtils::runTestCase(*this, test, errorCode);
184 }
185 
testSimpleSelection(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)186 void TestMessageFormat2::testSimpleSelection(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
187     (void) testBuilder;
188     (void) errorCode;
189 
190     /* Covered by testPlural */
191 }
192 
testComplexSelection(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)193 void TestMessageFormat2::testComplexSelection(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
194     CHECK_ERROR(errorCode);
195 
196     UnicodeString message = ".match {$photoCount :number} {$userGender :string}\n\
197                   1 masculine {{{$userName} added a new photo to his album.}}\n\
198                   1 feminine {{{$userName} added a new photo to her album.}}\n\
199                   1 * {{{$userName} added a new photo to their album.}}\n\
200                   * masculine {{{$userName} added {$photoCount} photos to his album.}}\n\
201                   * feminine {{{$userName} added {$photoCount} photos to her album.}}\n\
202                   * * {{{$userName} added {$photoCount} photos to their album.}}";
203     testBuilder.setPattern(message);
204 
205     int64_t count = 1;
206     TestCase test = testBuilder.clearArguments().setArgument("photoCount", count)
207         .setArgument("userGender", "masculine")
208         .setArgument("userName", "John")
209         .setExpected("John added a new photo to his album.")
210         .build();
211     TestUtils::runTestCase(*this, test, errorCode);
212     test = testBuilder.setArgument("userGender", "feminine")
213                       .setArgument("userName", "Anna")
214                       .setExpected("Anna added a new photo to her album.")
215                       .build();
216     TestUtils::runTestCase(*this, test, errorCode);
217     test = testBuilder.setArgument("userGender", "unknown")
218                       .setArgument("userName", "Anonymous")
219                       .setExpected("Anonymous added a new photo to their album.")
220                       .build();
221     TestUtils::runTestCase(*this, test, errorCode);
222 
223     count = 13;
224     test = testBuilder.clearArguments().setArgument("photoCount", count)
225                                 .setArgument("userGender", "masculine")
226                                 .setArgument("userName", "John")
227                                 .setExpected("John added 13 photos to his album.")
228                                 .build();
229     TestUtils::runTestCase(*this, test, errorCode);
230     test = testBuilder.setArgument("userGender", "feminine")
231                       .setArgument("userName", "Anna")
232                       .setExpected("Anna added 13 photos to her album.")
233                       .build();
234     TestUtils::runTestCase(*this, test, errorCode);
235     test = testBuilder.setArgument("userGender", "unknown")
236                       .setArgument("userName", "Anonymous")
237                       .setExpected("Anonymous added 13 photos to their album.")
238                       .build();
239     TestUtils::runTestCase(*this, test, errorCode);
240 }
241 
testSimpleLocalVariable(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)242 void TestMessageFormat2::testSimpleLocalVariable(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
243     CHECK_ERROR(errorCode);
244 
245     LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
246     CHECK_ERROR(errorCode);
247     // November 23, 2022 at 7:42:37.123 PM
248     cal->set(2022, Calendar::NOVEMBER, 23, 19, 42, 37);
249     UDate TEST_DATE = cal->getTime(errorCode);
250     CHECK_ERROR(errorCode);
251 
252     testBuilder.setPattern(".input {$expDate :date style=medium}\n\
253                             {{Your tickets expire on {$expDate}.}}");
254 
255     int64_t count = 1;
256     TestUtils::runTestCase(*this, testBuilder.clearArguments().setArgument("count", count)
257                       .setLocale(Locale("en"))
258                       .setDateArgument("expDate", TEST_DATE)
259                       .setExpected("Your tickets expire on Nov 23, 2022.")
260                       .build(), errorCode);
261 }
262 
testLocalVariableWithSelect(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)263 void TestMessageFormat2::testLocalVariableWithSelect(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
264     CHECK_ERROR(errorCode);
265 
266     LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
267     CHECK_ERROR(errorCode);
268     // November 23, 2022 at 7:42:37.123 PM
269     cal->set(2022, Calendar::NOVEMBER, 23, 19, 42, 37);
270     UDate TEST_DATE = cal->getTime(errorCode);
271     CHECK_ERROR(errorCode);
272 
273     testBuilder.setPattern(".input {$expDate :date style=medium}\n\
274                 .match {$count :number}\n\
275                  1 {{Your ticket expires on {$expDate}.}}\n\
276                  * {{Your {$count} tickets expire on {$expDate}.}}");
277 
278     int64_t count = 1;
279     TestCase test = testBuilder.clearArguments().setArgument("count", count)
280                       .setLocale(Locale("en"))
281                       .setDateArgument("expDate", TEST_DATE)
282                       .setExpected("Your ticket expires on Nov 23, 2022.")
283                       .build();
284     TestUtils::runTestCase(*this, test, errorCode);
285     count = 3;
286     test = testBuilder.setArgument("count", count)
287                       .setExpected("Your 3 tickets expire on Nov 23, 2022.")
288                       .build();
289     TestUtils::runTestCase(*this, test, errorCode);
290 }
291 
testDateFormat(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)292 void TestMessageFormat2::testDateFormat(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
293     LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
294     CHECK_ERROR(errorCode);
295 
296     cal->set(2022, Calendar::OCTOBER, 27, 0, 0, 0);
297     UDate expiration = cal->getTime(errorCode);
298     CHECK_ERROR(errorCode);
299 
300     TestCase test = testBuilder.clearArguments().setPattern("Your card expires on {$exp :date style=medium}!")
301                                 .setLocale(Locale("en"))
302                                 .setExpected("Your card expires on Oct 27, 2022!")
303                                 .setDateArgument("exp", expiration)
304                                 .build();
305     TestUtils::runTestCase(*this, test, errorCode);
306 
307     test = testBuilder.clearArguments().setPattern("Your card expires on {$exp :date style=full}!")
308                       .setExpected("Your card expires on Thursday, October 27, 2022!")
309                       .setDateArgument("exp", expiration)
310                       .build();
311     TestUtils::runTestCase(*this, test, errorCode);
312 
313     test = testBuilder.clearArguments().setPattern("Your card expires on {$exp :date style=long}!")
314                       .setExpected("Your card expires on October 27, 2022!")
315                       .setDateArgument("exp", expiration)
316                       .build();
317     TestUtils::runTestCase(*this, test, errorCode);
318 
319     test = testBuilder.clearArguments().setPattern("Your card expires on {$exp :date style=medium}!")
320                       .setExpected("Your card expires on Oct 27, 2022!")
321                       .setDateArgument("exp", expiration)
322                       .build();
323     TestUtils::runTestCase(*this, test, errorCode);
324 
325     test = testBuilder.clearArguments().setPattern("Your card expires on {$exp :date style=short}!")
326                       .setExpected("Your card expires on 10/27/22!")
327                       .setDateArgument("exp", expiration)
328                       .build();
329     TestUtils::runTestCase(*this, test, errorCode);
330 
331 /*
332   This test would require the calendar to be passed as a UObject* with the datetime formatter
333   doing an RTTI check -- however, that would be awkward, since it would have to check the tag for each
334   possible subclass of `Calendar`. datetime currently has no support for formatting any object argument
335 
336     cal.adoptInstead(new GregorianCalendar(2022, Calendar::OCTOBER, 27, errorCode));
337     if (cal.isValid()) {
338         test = testBuilder.setPattern("Your card expires on {$exp :datetime skeleton=yMMMdE}!")
339                           .setExpected("Your card expires on Thu, Oct 27, 2022!")
340                           .setArgument("exp", cal.orphan(), errorCode)
341                           .build();
342         TestUtils::runTestCase(*this, test, errorCode);
343     }
344 */
345 
346     // Implied function based on type of the object to format
347     test = testBuilder.clearArguments().setPattern("Your card expires on {$exp}!")
348                       .setExpected(CharsToUnicodeString("Your card expires on 10/27/22, 12:00\\u202FAM!"))
349                       .setDateArgument("exp", expiration)
350                       .build();
351     TestUtils::runTestCase(*this, test, errorCode);
352 }
353 
testPlural(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)354 void TestMessageFormat2::testPlural(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
355     UnicodeString message = ".match {$count :number}\n\
356                  1 {{You have one notification.}}\n           \
357                  * {{You have {$count} notifications.}}";
358 
359     int64_t count = 1;
360     TestCase test = testBuilder.clearArguments().setPattern(message)
361                                 .setExpected("You have one notification.")
362                                 .setArgument("count", count)
363                                 .build();
364     TestUtils::runTestCase(*this, test, errorCode);
365 
366     count = 42;
367     test = testBuilder.clearArguments().setExpected("You have 42 notifications.")
368                       .setArgument("count", count)
369                       .build();
370     TestUtils::runTestCase(*this, test, errorCode);
371 
372     count = 1;
373     test = testBuilder.clearArguments().setPattern(message)
374                       .setExpected("You have one notification.")
375                       .setArgument("count", "1")
376                       .build();
377     TestUtils::runTestCase(*this, test, errorCode);
378 
379     count = 42;
380     test = testBuilder.clearArguments().setExpected("You have 42 notifications.")
381                       .setArgument("count", "42")
382                       .build();
383     TestUtils::runTestCase(*this, test, errorCode);
384 }
385 
testPluralOrdinal(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)386 void TestMessageFormat2::testPluralOrdinal(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
387     UnicodeString message =  ".match {$place :number select=ordinal}\n\
388                  1 {{You got the gold medal}}\n            \
389                  2 {{You got the silver medal}}\n          \
390                  3 {{You got the bronze medal}}\n\
391                  one {{You got in the {$place}st place}}\n\
392                  two {{You got in the {$place}nd place}}\n \
393                  few {{You got in the {$place}rd place}}\n \
394                  * {{You got in the {$place}th place}}";
395 
396     TestCase test = testBuilder.clearArguments().setPattern(message)
397                                 .setExpected("You got the gold medal")
398                                 .setArgument("place", "1")
399                                 .build();
400     TestUtils::runTestCase(*this, test, errorCode);
401 
402     test = testBuilder.clearArguments().setExpected("You got the silver medal")
403                           .setArgument("place", "2")
404                           .build();
405     TestUtils::runTestCase(*this, test, errorCode);
406 
407     test = testBuilder.clearArguments().setExpected("You got the bronze medal")
408                           .setArgument("place", "3")
409                           .build();
410     TestUtils::runTestCase(*this, test, errorCode);
411 
412     test = testBuilder.clearArguments().setExpected("You got in the 21st place")
413                           .setArgument("place", "21")
414                           .build();
415     TestUtils::runTestCase(*this, test, errorCode);
416 
417     test = testBuilder.clearArguments().setExpected("You got in the 32nd place")
418                           .setArgument("place", "32")
419                           .build();
420     TestUtils::runTestCase(*this, test, errorCode);
421 
422     test = testBuilder.clearArguments().setExpected("You got in the 23rd place")
423                           .setArgument("place", "23")
424                           .build();
425     TestUtils::runTestCase(*this, test, errorCode);
426 
427     test = testBuilder.clearArguments().setExpected("You got in the 15th place")
428                           .setArgument("place", "15")
429                           .build();
430     TestUtils::runTestCase(*this, test, errorCode);
431 }
432 
testDeclareBeforeUse(TestCase::Builder & testBuilder,IcuTestErrorCode & errorCode)433 void TestMessageFormat2::testDeclareBeforeUse(TestCase::Builder& testBuilder, IcuTestErrorCode& errorCode) {
434 
435     UnicodeString message = ".local $foo = {$baz :number}\n\
436                  .local $bar = {$foo}\n                    \
437                  .local $baz = {$bar}\n                    \
438                  {{The message uses {$baz} and works}}";
439     testBuilder.setPattern(message);
440     testBuilder.setName("declare-before-use");
441 
442     TestCase test = testBuilder.clearArguments().setExpected("The message uses {$baz} and works")
443                                 .setExpectedError(U_MF_DUPLICATE_DECLARATION_ERROR)
444                                 .build();
445     TestUtils::runTestCase(*this, test, errorCode);
446 }
447 
448 
featureTests()449 void TestMessageFormat2::featureTests() {
450     IcuTestErrorCode errorCode(*this, "featureTests");
451 
452     TestCase::Builder testBuilder;
453     testBuilder.setName("featureTests");
454 
455     testEmptyMessage(testBuilder, errorCode);
456     testPlainText(testBuilder, errorCode);
457     testPlaceholders(testBuilder, errorCode);
458     testArgumentMissing(testBuilder, errorCode);
459     testDefaultLocale(testBuilder, errorCode);
460     testSpecialPluralWithDecimals(testBuilder, errorCode);
461     testDefaultFunctionAndOptions(testBuilder, errorCode);
462     testSimpleSelection(testBuilder, errorCode);
463     testComplexSelection(testBuilder, errorCode);
464     testSimpleLocalVariable(testBuilder, errorCode);
465     testLocalVariableWithSelect(testBuilder, errorCode);
466 
467     testDateFormat(testBuilder, errorCode);
468     testPlural(testBuilder, errorCode);
469     testPluralOrdinal(testBuilder, errorCode);
470     testDeclareBeforeUse(testBuilder, errorCode);
471 }
472 
~TestCase()473 TestCase::~TestCase() {}
~Builder()474 TestCase::Builder::~Builder() {}
475 
476 #endif /* #if !UCONFIG_NO_MF2 */
477 
478 #endif /* #if !UCONFIG_NO_FORMATTING */
479