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