+void TestMessageFormat::testCoverage(void) {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString testformat("{argument, plural, one{C''est # fichier} other {Ce sont # fichiers}} dans la liste.");
+ MessageFormat *msgfmt = new MessageFormat(testformat, Locale("fr"), status);
+ if (msgfmt == NULL || U_FAILURE(status)) {
+ dataerrln("FAIL: Unable to create MessageFormat.: %s", u_errorName(status));
+ return;
+ }
+ if (!msgfmt->usesNamedArguments()) {
+ errln("FAIL: Unable to detect usage of named arguments.");
+ }
+ const double limit[] = {0.0, 1.0, 2.0};
+ const UnicodeString formats[] = {"0.0<=Arg<1.0",
+ "1.0<=Arg<2.0",
+ "2.0<-Arg"};
+ ChoiceFormat cf(limit, formats, 3);
+
+ msgfmt->setFormat("set", cf, status);
+
+ StringEnumeration *en = msgfmt->getFormatNames(status);
+ if (en == NULL || U_FAILURE(status)) {
+ errln("FAIL: Unable to get format names enumeration.");
+ } else {
+ int32_t count = 0;
+ en->reset(status);
+ count = en->count(status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Unable to get format name enumeration count.");
+ } else {
+ for (int32_t i = 0; i < count; i++) {
+ en->snext(status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Error enumerating through names.");
+ break;
+ }
+ }
+ }
+ }
+
+ // adoptFormat() takes ownership of the input Format object.
+ // We need to clone the stack-allocated cf so that we do not attempt to delete cf.
+ Format *cfClone = cf.clone();
+ msgfmt->adoptFormat("adopt", cfClone, status);
+
+ delete en;
+ delete msgfmt;
+
+ msgfmt = new MessageFormat("'", status);
+ if (msgfmt == NULL || U_FAILURE(status)) {
+ errln("FAIL: Unable to create MessageFormat.");
+ return;
+ }
+ if (msgfmt->usesNamedArguments()) {
+ errln("FAIL: Unable to detect usage of named arguments.");
+ }
+
+ // Starting with ICU 4.8, we support setFormat(name, ...) and getFormatNames()
+ // on a MessageFormat without named arguments.
+ msgfmt->setFormat("formatName", cf, status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Should work to setFormat(name, ...) regardless of pattern.");
+ }
+ status = U_ZERO_ERROR;
+ en = msgfmt->getFormatNames(status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Should work to get format names enumeration regardless of pattern.");
+ }
+
+ delete en;
+ delete msgfmt;
+}
+
+void TestMessageFormat::testGetFormatNames() {
+ IcuTestErrorCode errorCode(*this, "testGetFormatNames");
+ MessageFormat msgfmt("Hello, {alice,number} {oops,date,full} {zip,spellout} World.", Locale::getRoot(), errorCode);
+ if(errorCode.errDataIfFailureAndReset("MessageFormat() failed")) {
+ return;
+ }
+ LocalPointer<StringEnumeration> names(msgfmt.getFormatNames(errorCode));
+ if(errorCode.errIfFailureAndReset("msgfmt.getFormatNames() failed")) {
+ return;
+ }
+ const UnicodeString *name;
+ name = names->snext(errorCode);
+ if (name == NULL || errorCode.isFailure()) {
+ errln("msgfmt.getFormatNames()[0] failed: %s", errorCode.errorName());
+ errorCode.reset();
+ return;
+ }
+ if (!assertEquals("msgfmt.getFormatNames()[0]", UNICODE_STRING_SIMPLE("alice"), *name)) {
+ return;
+ }
+ name = names->snext(errorCode);
+ if (name == NULL || errorCode.isFailure()) {
+ errln("msgfmt.getFormatNames()[1] failed: %s", errorCode.errorName());
+ errorCode.reset();
+ return;
+ }
+ if (!assertEquals("msgfmt.getFormatNames()[1]", UNICODE_STRING_SIMPLE("oops"), *name)) {
+ return;
+ }
+ name = names->snext(errorCode);
+ if (name == NULL || errorCode.isFailure()) {
+ errln("msgfmt.getFormatNames()[2] failed: %s", errorCode.errorName());
+ errorCode.reset();
+ return;
+ }
+ if (!assertEquals("msgfmt.getFormatNames()[2]", UNICODE_STRING_SIMPLE("zip"), *name)) {
+ return;
+ }
+ name = names->snext(errorCode);
+ if (name != NULL) {
+ errln(UnicodeString("msgfmt.getFormatNames()[3] should be NULL but is: ") + *name);
+ return;
+ }
+}
+
+void TestMessageFormat::TestTrimArgumentName() {
+ // ICU 4.8 allows and ignores white space around argument names and numbers.
+ IcuTestErrorCode errorCode(*this, "TestTrimArgumentName");
+ MessageFormat m("a { 0 , number , '#,#'#.0 } z", Locale::getEnglish(), errorCode);
+ if (errorCode.errDataIfFailureAndReset("Unable to instantiate MessageFormat")) {
+ return;
+ }
+ Formattable args[1] = { (int32_t)2 };
+ FieldPosition ignore(FieldPosition::DONT_CARE);
+ UnicodeString result;
+ assertEquals("trim-numbered-arg format() failed", "a #,#2.0 z",
+ m.format(args, 1, result, ignore, errorCode));
+
+ m.applyPattern("x { _oOo_ , number , integer } y", errorCode);
+ UnicodeString argName = UNICODE_STRING_SIMPLE("_oOo_");
+ args[0].setLong(3);
+ result.remove();
+ assertEquals("trim-named-arg format() failed", "x 3 y",
+ m.format(&argName, args, 1, result, errorCode));
+}
+
+void TestMessageFormat::TestSelectOrdinal() {
+ IcuTestErrorCode errorCode(*this, "TestSelectOrdinal");
+ // Test plural & ordinal together,
+ // to make sure that we get the correct cached PluralSelector for each.
+ MessageFormat m(
+ "{0,plural,one{1 file}other{# files}}, "
+ "{0,selectordinal,one{#st file}two{#nd file}few{#rd file}other{#th file}}",
+ Locale::getEnglish(), errorCode);
+ if (errorCode.errDataIfFailureAndReset("Unable to instantiate MessageFormat")) {
+ return;
+ }
+ Formattable args[1] = { (int32_t)21 };
+ FieldPosition ignore(FieldPosition::DONT_CARE);
+ UnicodeString result;
+ assertEquals("plural-and-ordinal format(21) failed", "21 files, 21st file",
+ m.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0].setLong(2);
+ assertEquals("plural-and-ordinal format(2) failed", "2 files, 2nd file",
+ m.format(args, 1, result.remove(), ignore, errorCode), TRUE);
+
+ args[0].setLong(1);
+ assertEquals("plural-and-ordinal format(1) failed", "1 file, 1st file",
+ m.format(args, 1, result.remove(), ignore, errorCode), TRUE);
+
+ args[0].setLong(3);
+ assertEquals("plural-and-ordinal format(3) failed", "3 files, 3rd file",
+ m.format(args, 1, result.remove(), ignore, errorCode), TRUE);
+
+ errorCode.errDataIfFailureAndReset("");
+}
+
+void TestMessageFormat::TestDecimals() {
+ IcuTestErrorCode errorCode(*this, "TestDecimals");
+ // Simple number replacement.
+ MessageFormat m(
+ "{0,plural,one{one meter}other{# meters}}",
+ Locale::getEnglish(), errorCode);
+ Formattable args[1] = { (int32_t)1 };
+ FieldPosition ignore;
+ UnicodeString result;
+ assertEquals("simple format(1)", "one meter",
+ m.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)1.5;
+ result.remove();
+ assertEquals("simple format(1.5)", "1.5 meters",
+ m.format(args, 1, result, ignore, errorCode), TRUE);
+
+ // Simple but explicit.
+ MessageFormat m0(
+ "{0,plural,one{one meter}other{{0} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = (int32_t)1;
+ result.remove();
+ assertEquals("explicit format(1)", "one meter",
+ m0.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)1.5;
+ result.remove();
+ assertEquals("explicit format(1.5)", "1.5 meters",
+ m0.format(args, 1, result, ignore, errorCode), TRUE);
+
+ // With offset and specific simple format with optional decimals.
+ MessageFormat m1(
+ "{0,plural,offset:1 one{another meter}other{{0,number,00.#} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = (int32_t)1;
+ result.remove();
+ assertEquals("offset format(1)", "01 meters",
+ m1.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (int32_t)2;
+ result.remove();
+ assertEquals("offset format(1)", "another meter",
+ m1.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)2.5;
+ result.remove();
+ assertEquals("offset format(1)", "02.5 meters",
+ m1.format(args, 1, result, ignore, errorCode), TRUE);
+
+ // With offset and specific simple format with forced decimals.
+ MessageFormat m2(
+ "{0,plural,offset:1 one{another meter}other{{0,number,0.0} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = (int32_t)1;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "1.0 meters",
+ m2.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (int32_t)2;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "2.0 meters",
+ m2.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)2.5;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "2.5 meters",
+ m2.format(args, 1, result, ignore, errorCode), TRUE);
+ errorCode.reset();
+}
+
+void TestMessageFormat::TestArgIsPrefixOfAnother() {
+ IcuTestErrorCode errorCode(*this, "TestArgIsPrefixOfAnother");
+ // Ticket #11952
+ MessageFormat mf1("{0,select,a{A}ab{AB}abc{ABC}other{?}}", Locale::getEnglish(), errorCode);
+ Formattable args[3];
+ FieldPosition ignore;
+ UnicodeString result;
+ args[0].setString("a");
+ assertEquals("a", "A", mf1.format(args, 1, result, ignore, errorCode));
+ args[0].setString("ab");
+ assertEquals("ab", "AB", mf1.format(args, 1, result.remove(), ignore, errorCode));
+ args[0].setString("abc");
+ assertEquals("abc", "ABC", mf1.format(args, 1, result.remove(), ignore, errorCode));
+
+ // Ticket #12172
+ MessageFormat mf2("{a} {aa} {aaa}", Locale::getEnglish(), errorCode);
+ UnicodeString argNames[3] = { "a", "aa", "aaa" };
+ args[0].setString("A");
+ args[1].setString("AB");
+ args[2].setString("ABC");
+ assertEquals("a aa aaa", "A AB ABC", mf2.format(argNames, args, 3, result.remove(), errorCode));
+
+ // Ticket #12172
+ MessageFormat mf3("{aa} {aaa}", Locale::getEnglish(), errorCode);
+ assertEquals("aa aaa", "AB ABC", mf3.format(argNames + 1, args + 1, 2, result.remove(), errorCode));
+}
+
+void TestMessageFormat::TestMessageFormatNumberSkeleton() {
+ IcuTestErrorCode status(*this, "TestMessageFormatNumberSkeleton");
+
+ static const struct TestCase {
+ const char16_t* messagePattern;
+ const char* localeName;
+ double arg;
+ const char16_t* expected;
+ } cases[] = {
+ { u"{0,number,::percent}", "en", 50, u"50%" },
+ { u"{0,number,::percent scale/100}", "en", 0.5, u"50%" },
+ { u"{0,number, :: percent scale/100 }", "en", 0.5, u"50%" },
+ { u"{0,number,::currency/USD}", "en", 23, u"$23.00" },
+ { u"{0,number,::precision-integer}", "en", 514.23, u"514" },
+ { u"{0,number,::.000}", "en", 514.23, u"514.230" },
+ { u"{0,number,::.}", "en", 514.23, u"514" },
+ { u"{0,number,::}", "fr", 514.23, u"514,23" },
+ { u"Cost: {0,number,::currency/EUR}.", "en", 4.3, u"Cost: €4.30." },
+ { u"{0,number,'::'0.00}", "en", 50, u"::50.00" }, // pattern literal
+ };
+
+ for (auto& cas : cases) {
+ status.setScope(cas.messagePattern);
+ MessageFormat msgf(cas.messagePattern, cas.localeName, status);
+ UnicodeString sb;
+ FieldPosition fpos(0);
+ Formattable argsArray[] = {{cas.arg}};
+ Formattable args(argsArray, 1);
+ msgf.format(args, sb, status);
+
+ assertEquals(cas.messagePattern, cas.expected, sb);
+ }
+}
+