// © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING #include "numbertest.h" #include "unicode/numberrangeformatter.h" #include #include // Horrible workaround for the lack of a status code in the constructor... // (Also affects numbertest_api.cpp) UErrorCode globalNumberRangeFormatterTestStatus = U_ZERO_ERROR; NumberRangeFormatterTest::NumberRangeFormatterTest() : NumberRangeFormatterTest(globalNumberRangeFormatterTestStatus) { } NumberRangeFormatterTest::NumberRangeFormatterTest(UErrorCode& status) : USD(u"USD", status), GBP(u"GBP", status), PTE(u"PTE", status) { // Check for error on the first MeasureUnit in case there is no data LocalPointer unit(MeasureUnit::createMeter(status)); if (U_FAILURE(status)) { dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status)); return; } METER = *unit; KILOMETER = *LocalPointer(MeasureUnit::createKilometer(status)); FAHRENHEIT = *LocalPointer(MeasureUnit::createFahrenheit(status)); KELVIN = *LocalPointer(MeasureUnit::createKelvin(status)); } void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { if (exec) { logln("TestSuite NumberRangeFormatterTest: "); } TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testSanity); TESTCASE_AUTO(testBasic); TESTCASE_AUTO(testCollapse); TESTCASE_AUTO(testIdentity); TESTCASE_AUTO(testDifferentFormatters); TESTCASE_AUTO(testPlurals); TESTCASE_AUTO(testFieldPositions); TESTCASE_AUTO(testCopyMove); TESTCASE_AUTO(toObject); TESTCASE_AUTO(testGetDecimalNumbers); TESTCASE_AUTO_END; } void NumberRangeFormatterTest::testSanity() { IcuTestErrorCode status(*this, "testSanity"); LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter::withLocale("en-us"); LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter::with().locale("en-us"); assertEquals("Formatters should have same behavior 1", lnrf1.formatFormattableRange(4, 6, status).toString(status), lnrf2.formatFormattableRange(4, 6, status).toString(status)); } void NumberRangeFormatterTest::testBasic() { assertFormatRange( u"Basic", NumberRangeFormatter::with(), Locale("en-us"), u"1–5", u"~5", u"~5", u"0–3", u"~0", u"3–3,000", u"3,000–5,000", u"4,999–5,001", u"~5,000", u"5,000–5,000,000"); assertFormatRange( u"Basic with units", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().unit(METER)), Locale("en-us"), u"1–5 m", u"~5 m", u"~5 m", u"0–3 m", u"~0 m", u"3–3,000 m", u"3,000–5,000 m", u"4,999–5,001 m", u"~5,000 m", u"5,000–5,000,000 m"); assertFormatRange( u"Basic with different units", NumberRangeFormatter::with() .numberFormatterFirst(NumberFormatter::with().unit(METER)) .numberFormatterSecond(NumberFormatter::with().unit(KILOMETER)), Locale("en-us"), u"1 m – 5 km", u"5 m – 5 km", u"5 m – 5 km", u"0 m – 3 km", u"0 m – 0 km", u"3 m – 3,000 km", u"3,000 m – 5,000 km", u"4,999 m – 5,001 km", u"5,000 m – 5,000 km", u"5,000 m – 5,000,000 km"); assertFormatRange( u"Basic long unit", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)), Locale("en-us"), u"1–5 meters", u"~5 meters", u"~5 meters", u"0–3 meters", u"~0 meters", u"3–3,000 meters", u"3,000–5,000 meters", u"4,999–5,001 meters", u"~5,000 meters", u"5,000–5,000,000 meters"); assertFormatRange( u"Non-English locale and unit", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)), Locale("fr-FR"), u"1–5\u00A0degrés Fahrenheit", u"≈5\u00A0degrés Fahrenheit", u"≈5\u00A0degrés Fahrenheit", u"0–3\u00A0degrés Fahrenheit", u"≈0\u00A0degré Fahrenheit", u"3–3\u202F000\u00A0degrés Fahrenheit", u"3\u202F000–5\u202F000\u00A0degrés Fahrenheit", u"4\u202F999–5\u202F001\u00A0degrés Fahrenheit", u"≈5\u202F000\u00A0degrés Fahrenheit", u"5\u202F000–5\u202F000\u202F000\u00A0degrés Fahrenheit"); assertFormatRange( u"Locale with custom range separator", NumberRangeFormatter::with(), Locale("ja"), u"1~5", u"約 5", u"約 5", u"0~3", u"約 0", u"3~3,000", u"3,000~5,000", u"4,999~5,001", u"約 5,000", u"5,000~5,000,000"); assertFormatRange( u"Locale that already has spaces around range separator", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_NONE) .numberFormatterBoth(NumberFormatter::with().unit(KELVIN)), Locale("hr"), u"1 K – 5 K", u"~5 K", u"~5 K", u"0 K – 3 K", u"~0 K", u"3 K – 3.000 K", u"3.000 K – 5.000 K", u"4.999 K – 5.001 K", u"~5.000 K", u"5.000 K – 5.000.000 K"); assertFormatRange( u"Locale with custom numbering system and no plural ranges data", NumberRangeFormatter::with(), Locale("shn@numbers=beng"), // 012459 = ০১৩৪৫৯ u"১–৫", u"~৫", u"~৫", u"০–৩", u"~০", u"৩–৩,০০০", u"৩,০০০–৫,০০০", u"৪,৯৯৯–৫,০০১", u"~৫,০০০", u"৫,০০০–৫,০০০,০০০"); assertFormatRange( u"Portuguese currency", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().unit(PTE)), Locale("pt-PT"), u"1$00 - 5$00 \u200B", u"~5$00 \u200B", u"~5$00 \u200B", u"0$00 - 3$00 \u200B", u"~0$00 \u200B", u"3$00 - 3000$00 \u200B", u"3000$00 - 5000$00 \u200B", u"4999$00 - 5001$00 \u200B", u"~5000$00 \u200B", u"5000$00 - 5,000,000$00 \u200B"); } void NumberRangeFormatterTest::testCollapse() { assertFormatRange( u"Default collapse on currency (default rounding)", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().unit(USD)), Locale("en-us"), u"$1.00 – $5.00", u"~$5.00", u"~$5.00", u"$0.00 – $3.00", u"~$0.00", u"$3.00 – $3,000.00", u"$3,000.00 – $5,000.00", u"$4,999.00 – $5,001.00", u"~$5,000.00", u"$5,000.00 – $5,000,000.00"); assertFormatRange( u"Default collapse on currency", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())), Locale("en-us"), u"$1 – $5", u"~$5", u"~$5", u"$0 – $3", u"~$0", u"$3 – $3,000", u"$3,000 – $5,000", u"$4,999 – $5,001", u"~$5,000", u"$5,000 – $5,000,000"); assertFormatRange( u"No collapse on currency", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_NONE) .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())), Locale("en-us"), u"$1 – $5", u"~$5", u"~$5", u"$0 – $3", u"~$0", u"$3 – $3,000", u"$3,000 – $5,000", u"$4,999 – $5,001", u"~$5,000", u"$5,000 – $5,000,000"); assertFormatRange( u"Unit collapse on currency", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_UNIT) .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())), Locale("en-us"), u"$1–5", u"~$5", u"~$5", u"$0–3", u"~$0", u"$3–3,000", u"$3,000–5,000", u"$4,999–5,001", u"~$5,000", u"$5,000–5,000,000"); assertFormatRange( u"All collapse on currency", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_ALL) .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())), Locale("en-us"), u"$1–5", u"~$5", u"~$5", u"$0–3", u"~$0", u"$3–3,000", u"$3,000–5,000", u"$4,999–5,001", u"~$5,000", u"$5,000–5,000,000"); assertFormatRange( u"Default collapse on currency ISO code", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with() .unit(GBP) .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE) .precision(Precision::integer())), Locale("en-us"), u"GBP 1–5", u"~GBP 5", // TODO: Fix this at some point u"~GBP 5", u"GBP 0–3", u"~GBP 0", u"GBP 3–3,000", u"GBP 3,000–5,000", u"GBP 4,999–5,001", u"~GBP 5,000", u"GBP 5,000–5,000,000"); assertFormatRange( u"No collapse on currency ISO code", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_NONE) .numberFormatterBoth(NumberFormatter::with() .unit(GBP) .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE) .precision(Precision::integer())), Locale("en-us"), u"GBP 1 – GBP 5", u"~GBP 5", // TODO: Fix this at some point u"~GBP 5", u"GBP 0 – GBP 3", u"~GBP 0", u"GBP 3 – GBP 3,000", u"GBP 3,000 – GBP 5,000", u"GBP 4,999 – GBP 5,001", u"~GBP 5,000", u"GBP 5,000 – GBP 5,000,000"); assertFormatRange( u"Unit collapse on currency ISO code", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_UNIT) .numberFormatterBoth(NumberFormatter::with() .unit(GBP) .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE) .precision(Precision::integer())), Locale("en-us"), u"GBP 1–5", u"~GBP 5", // TODO: Fix this at some point u"~GBP 5", u"GBP 0–3", u"~GBP 0", u"GBP 3–3,000", u"GBP 3,000–5,000", u"GBP 4,999–5,001", u"~GBP 5,000", u"GBP 5,000–5,000,000"); assertFormatRange( u"All collapse on currency ISO code", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_ALL) .numberFormatterBoth(NumberFormatter::with() .unit(GBP) .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE) .precision(Precision::integer())), Locale("en-us"), u"GBP 1–5", u"~GBP 5", // TODO: Fix this at some point u"~GBP 5", u"GBP 0–3", u"~GBP 0", u"GBP 3–3,000", u"GBP 3,000–5,000", u"GBP 4,999–5,001", u"~GBP 5,000", u"GBP 5,000–5,000,000"); // Default collapse on measurement unit is in testBasic() assertFormatRange( u"No collapse on measurement unit", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_NONE) .numberFormatterBoth(NumberFormatter::with().unit(METER)), Locale("en-us"), u"1 m – 5 m", u"~5 m", u"~5 m", u"0 m – 3 m", u"~0 m", u"3 m – 3,000 m", u"3,000 m – 5,000 m", u"4,999 m – 5,001 m", u"~5,000 m", u"5,000 m – 5,000,000 m"); assertFormatRange( u"Unit collapse on measurement unit", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_UNIT) .numberFormatterBoth(NumberFormatter::with().unit(METER)), Locale("en-us"), u"1–5 m", u"~5 m", u"~5 m", u"0–3 m", u"~0 m", u"3–3,000 m", u"3,000–5,000 m", u"4,999–5,001 m", u"~5,000 m", u"5,000–5,000,000 m"); assertFormatRange( u"All collapse on measurement unit", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_ALL) .numberFormatterBoth(NumberFormatter::with().unit(METER)), Locale("en-us"), u"1–5 m", u"~5 m", u"~5 m", u"0–3 m", u"~0 m", u"3–3,000 m", u"3,000–5,000 m", u"4,999–5,001 m", u"~5,000 m", u"5,000–5,000,000 m"); assertFormatRange( u"Default collapse, long-form compact notation", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())), Locale("de-CH"), u"1–5", u"≈5", u"≈5", u"0–3", u"≈0", u"3–3 Tausend", u"3–5 Tausend", u"≈5 Tausend", u"≈5 Tausend", u"5 Tausend – 5 Millionen"); assertFormatRange( u"Unit collapse, long-form compact notation", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_UNIT) .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())), Locale("de-CH"), u"1–5", u"≈5", u"≈5", u"0–3", u"≈0", u"3–3 Tausend", u"3 Tausend – 5 Tausend", u"≈5 Tausend", u"≈5 Tausend", u"5 Tausend – 5 Millionen"); assertFormatRange( u"Default collapse on measurement unit with compact-short notation", NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)), Locale("en-us"), u"1–5 m", u"~5 m", u"~5 m", u"0–3 m", u"~0 m", u"3–3K m", u"3K – 5K m", u"~5K m", u"~5K m", u"5K – 5M m"); assertFormatRange( u"No collapse on measurement unit with compact-short notation", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_NONE) .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)), Locale("en-us"), u"1 m – 5 m", u"~5 m", u"~5 m", u"0 m – 3 m", u"~0 m", u"3 m – 3K m", u"3K m – 5K m", u"~5K m", u"~5K m", u"5K m – 5M m"); assertFormatRange( u"Unit collapse on measurement unit with compact-short notation", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_UNIT) .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)), Locale("en-us"), u"1–5 m", u"~5 m", u"~5 m", u"0–3 m", u"~0 m", u"3–3K m", u"3K – 5K m", u"~5K m", u"~5K m", u"5K – 5M m"); assertFormatRange( u"All collapse on measurement unit with compact-short notation", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_ALL) .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)), Locale("en-us"), u"1–5 m", u"~5 m", u"~5 m", u"0–3 m", u"~0 m", u"3–3K m", u"3–5K m", // this one is the key use case for ALL u"~5K m", u"~5K m", u"5K – 5M m"); assertFormatRange( u"No collapse on scientific notation", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_NONE) .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())), Locale("en-us"), u"1E0 – 5E0", u"~5E0", u"~5E0", u"0E0 – 3E0", u"~0E0", u"3E0 – 3E3", u"3E3 – 5E3", u"4.999E3 – 5.001E3", u"~5E3", u"5E3 – 5E6"); assertFormatRange( u"All collapse on scientific notation", NumberRangeFormatter::with() .collapse(UNUM_RANGE_COLLAPSE_ALL) .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())), Locale("en-us"), u"1–5E0", u"~5E0", u"~5E0", u"0–3E0", u"~0E0", u"3E0 – 3E3", u"3–5E3", u"4.999–5.001E3", u"~5E3", u"5E3 – 5E6"); // TODO: Test compact currency? // The code is not smart enough to differentiate the notation from the unit. } void NumberRangeFormatterTest::testIdentity() { assertFormatRange( u"Identity fallback Range", NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_RANGE), Locale("en-us"), u"1–5", u"5–5", u"5–5", u"0–3", u"0–0", u"3–3,000", u"3,000–5,000", u"4,999–5,001", u"5,000–5,000", u"5,000–5,000,000"); assertFormatRange( u"Identity fallback Approximately or Single Value", NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE), Locale("en-us"), u"1–5", u"~5", u"5", u"0–3", u"0", u"3–3,000", u"3,000–5,000", u"4,999–5,001", u"5,000", u"5,000–5,000,000"); assertFormatRange( u"Identity fallback Single Value", NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE), Locale("en-us"), u"1–5", u"5", u"5", u"0–3", u"0", u"3–3,000", u"3,000–5,000", u"4,999–5,001", u"5,000", u"5,000–5,000,000"); assertFormatRange( u"Identity fallback Approximately or Single Value with compact notation", NumberRangeFormatter::with() .identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE) .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort())), Locale("en-us"), u"1–5", u"~5", u"5", u"0–3", u"0", u"3–3K", u"3K – 5K", u"~5K", u"5K", u"5K – 5M"); assertFormatRange( u"Approximately in middle of unit string", NumberRangeFormatter::with().numberFormatterBoth( NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)), Locale("zh-Hant"), u"華氏 1-5 度", u"華氏 ~5 度", u"華氏 ~5 度", u"華氏 0-3 度", u"華氏 ~0 度", u"華氏 3-3,000 度", u"華氏 3,000-5,000 度", u"華氏 4,999-5,001 度", u"華氏 ~5,000 度", u"華氏 5,000-5,000,000 度"); } void NumberRangeFormatterTest::testDifferentFormatters() { assertFormatRange( u"Different rounding rules", NumberRangeFormatter::with() .numberFormatterFirst(NumberFormatter::with().precision(Precision::integer())) .numberFormatterSecond(NumberFormatter::with().precision(Precision::fixedSignificantDigits(2))), Locale("en-us"), u"1–5.0", u"5–5.0", u"5–5.0", u"0–3.0", u"0–0.0", u"3–3,000", u"3,000–5,000", u"4,999–5,000", u"5,000–5,000", // TODO: Should this one be ~5,000? u"5,000–5,000,000"); } void NumberRangeFormatterTest::testPlurals() { IcuTestErrorCode status(*this, "testPlurals"); // Locale sl has interesting plural forms: // GBP{ // one{"britanski funt"} // two{"britanska funta"} // few{"britanski funti"} // other{"britanskih funtov"} // } Locale locale("sl"); UnlocalizedNumberFormatter unf = NumberFormatter::with() .unit(GBP) .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME) .precision(Precision::integer()); LocalizedNumberFormatter lnf = unf.locale(locale); // For comparison, run the non-range version of the formatter assertEquals(Int64ToUnicodeString(1), u"1 britanski funt", lnf.formatDouble(1, status).toString(status)); assertEquals(Int64ToUnicodeString(2), u"2 britanska funta", lnf.formatDouble(2, status).toString(status)); assertEquals(Int64ToUnicodeString(3), u"3 britanski funti", lnf.formatDouble(3, status).toString(status)); assertEquals(Int64ToUnicodeString(5), u"5 britanskih funtov", lnf.formatDouble(5, status).toString(status)); if (status.errIfFailureAndReset()) { return; } LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::with() .numberFormatterBoth(unf) .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE) .locale(locale); struct TestCase { double first; double second; const char16_t* expected; } cases[] = { {1, 1, u"1–1 britanski funti"}, // one + one -> few {1, 2, u"1–2 britanska funta"}, // one + two -> two {1, 3, u"1–3 britanski funti"}, // one + few -> few {1, 5, u"1–5 britanskih funtov"}, // one + other -> other {2, 1, u"2–1 britanski funti"}, // two + one -> few {2, 2, u"2–2 britanska funta"}, // two + two -> two {2, 3, u"2–3 britanski funti"}, // two + few -> few {2, 5, u"2–5 britanskih funtov"}, // two + other -> other {3, 1, u"3–1 britanski funti"}, // few + one -> few {3, 2, u"3–2 britanska funta"}, // few + two -> two {3, 3, u"3–3 britanski funti"}, // few + few -> few {3, 5, u"3–5 britanskih funtov"}, // few + other -> other {5, 1, u"5–1 britanski funti"}, // other + one -> few {5, 2, u"5–2 britanska funta"}, // other + two -> two {5, 3, u"5–3 britanski funti"}, // other + few -> few {5, 5, u"5–5 britanskih funtov"}, // other + other -> other }; for (auto& cas : cases) { UnicodeString message = Int64ToUnicodeString(static_cast(cas.first)); message += u" "; message += Int64ToUnicodeString(static_cast(cas.second)); status.setScope(message); UnicodeString actual = lnrf.formatFormattableRange(cas.first, cas.second, status).toString(status); assertEquals(message, cas.expected, actual); status.errIfFailureAndReset(); } } void NumberRangeFormatterTest::testFieldPositions() { { const char16_t* message = u"Field position test 1"; const char16_t* expectedString = u"3K – 5K m"; FormattedNumberRange result = assertFormattedRangeEquals( message, NumberRangeFormatter::with() .numberFormatterBoth(NumberFormatter::with() .unit(METER) .notation(Notation::compactShort())) .locale("en-us"), 3000, 5000, expectedString); static const UFieldPosition expectedFieldPositions[] = { // field, begin index, end index {UNUM_INTEGER_FIELD, 0, 1}, {UNUM_COMPACT_FIELD, 1, 2}, {UNUM_INTEGER_FIELD, 5, 6}, {UNUM_COMPACT_FIELD, 6, 7}, {UNUM_MEASURE_UNIT_FIELD, 8, 9}}; checkFormattedValue( message, result, expectedString, UFIELD_CATEGORY_NUMBER, expectedFieldPositions, UPRV_LENGTHOF(expectedFieldPositions)); } { const char16_t* message = u"Field position test 2"; const char16_t* expectedString = u"87,654,321–98,765,432"; FormattedNumberRange result = assertFormattedRangeEquals( message, NumberRangeFormatter::withLocale("en-us"), 87654321, 98765432, expectedString); static const UFieldPosition expectedFieldPositions[] = { // field, begin index, end index {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3}, {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7}, {UNUM_INTEGER_FIELD, 0, 10}, {UNUM_GROUPING_SEPARATOR_FIELD, 13, 14}, {UNUM_GROUPING_SEPARATOR_FIELD, 17, 18}, {UNUM_INTEGER_FIELD, 11, 21}}; checkFormattedValue( message, result, expectedString, UFIELD_CATEGORY_NUMBER, expectedFieldPositions, UPRV_LENGTHOF(expectedFieldPositions)); } } void NumberRangeFormatterTest::testCopyMove() { IcuTestErrorCode status(*this, "testCopyMove"); // Default constructors LocalizedNumberRangeFormatter l1; assertEquals("Initial behavior", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status)); if (status.errDataIfFailureAndReset()) { return; } // Setup l1 = NumberRangeFormatter::withLocale("fr-FR") .numberFormatterBoth(NumberFormatter::with().unit(USD)); assertEquals("Currency behavior", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status)); // Copy constructor LocalizedNumberRangeFormatter l2 = l1; assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status)); // Move constructor LocalizedNumberRangeFormatter l3 = std::move(l1); assertEquals("Move constructor", u"1,00–5,00 $US", l3.formatFormattableRange(1, 5, status).toString(status)); // Reset objects for assignment tests l1 = NumberRangeFormatter::withLocale("en-us"); l2 = NumberRangeFormatter::withLocale("en-us"); assertEquals("Rest behavior, l1", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status)); assertEquals("Rest behavior, l2", u"1–5", l2.formatFormattableRange(1, 5, status).toString(status)); // Copy assignment l1 = l3; assertEquals("Copy constructor", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status)); // Move assignment l2 = std::move(l3); assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status)); // FormattedNumberRange FormattedNumberRange result = l1.formatFormattableRange(1, 5, status); assertEquals("FormattedNumberRange move constructor", u"1,00–5,00 $US", result.toString(status)); result = l1.formatFormattableRange(3, 6, status); assertEquals("FormattedNumberRange move assignment", u"3,00–6,00 $US", result.toString(status)); } void NumberRangeFormatterTest::toObject() { IcuTestErrorCode status(*this, "toObject"); // const lvalue version { LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en"); LocalPointer lnf2(lnf.clone()); assertFalse("should create successfully, const lvalue", lnf2.isNull()); assertEquals("object API test, const lvalue", u"5–7", lnf2->formatFormattableRange(5, 7, status).toString(status)); } // rvalue reference version { LocalPointer lnf( NumberRangeFormatter::withLocale("en").clone()); assertFalse("should create successfully, rvalue reference", lnf.isNull()); assertEquals("object API test, rvalue reference", u"5–7", lnf->formatFormattableRange(5, 7, status).toString(status)); } // to std::unique_ptr via assignment { std::unique_ptr lnf = NumberRangeFormatter::withLocale("en").clone(); assertTrue("should create successfully, unique_ptr B", static_cast(lnf)); assertEquals("object API test, unique_ptr B", u"5–7", lnf->formatFormattableRange(5, 7, status).toString(status)); } // make sure no memory leaks { NumberRangeFormatter::with().clone(); } } void NumberRangeFormatterTest::testGetDecimalNumbers() { IcuTestErrorCode status(*this, "testGetDecimalNumbers"); LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en") .numberFormatterBoth(NumberFormatter::with().unit(USD)); // Range of numbers { FormattedNumberRange range = lnf.formatFormattableRange(1, 5, status); assertEquals("Range: Formatted string should be as expected", u"$1.00 \u2013 $5.00", range.toString(status)); auto decimalNumbers = range.getDecimalNumbers(status); // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem? if (logKnownIssue("ICU-21281")) { assertEquals("First decimal number", "1", decimalNumbers.first.c_str()); assertEquals("Second decimal number", "5", decimalNumbers.second.c_str()); } else { assertEquals("First decimal number", "1.00", decimalNumbers.first.c_str()); assertEquals("Second decimal number", "5.00", decimalNumbers.second.c_str()); } } // Identity fallback { FormattedNumberRange range = lnf.formatFormattableRange(3, 3, status); assertEquals("Identity: Formatted string should be as expected", u"~$3.00", range.toString(status)); auto decimalNumbers = range.getDecimalNumbers(status); // NOTE: DecNum doesn't retain trailing zeros. Is that a problem? // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem? if (logKnownIssue("ICU-21281")) { assertEquals("First decimal number", "3", decimalNumbers.first.c_str()); assertEquals("Second decimal number", "3", decimalNumbers.second.c_str()); } else { assertEquals("First decimal number", "3.00", decimalNumbers.first.c_str()); assertEquals("Second decimal number", "3.00", decimalNumbers.second.c_str()); } } } void NumberRangeFormatterTest::assertFormatRange( const char16_t* message, const UnlocalizedNumberRangeFormatter& f, Locale locale, const char16_t* expected_10_50, const char16_t* expected_49_51, const char16_t* expected_50_50, const char16_t* expected_00_30, const char16_t* expected_00_00, const char16_t* expected_30_3K, const char16_t* expected_30K_50K, const char16_t* expected_49K_51K, const char16_t* expected_50K_50K, const char16_t* expected_50K_50M) { LocalizedNumberRangeFormatter l = f.locale(locale); assertFormattedRangeEquals(message, l, 1, 5, expected_10_50); assertFormattedRangeEquals(message, l, 4.9999999, 5.0000001, expected_49_51); assertFormattedRangeEquals(message, l, 5, 5, expected_50_50); assertFormattedRangeEquals(message, l, 0, 3, expected_00_30); assertFormattedRangeEquals(message, l, 0, 0, expected_00_00); assertFormattedRangeEquals(message, l, 3, 3000, expected_30_3K); assertFormattedRangeEquals(message, l, 3000, 5000, expected_30K_50K); assertFormattedRangeEquals(message, l, 4999, 5001, expected_49K_51K); assertFormattedRangeEquals(message, l, 5000, 5000, expected_50K_50K); assertFormattedRangeEquals(message, l, 5e3, 5e6, expected_50K_50M); } FormattedNumberRange NumberRangeFormatterTest::assertFormattedRangeEquals( const char16_t* message, const LocalizedNumberRangeFormatter& l, double first, double second, const char16_t* expected) { IcuTestErrorCode status(*this, "assertFormattedRangeEquals"); UnicodeString fullMessage = UnicodeString(message) + u": " + DoubleToUnicodeString(first) + u", " + DoubleToUnicodeString(second); status.setScope(fullMessage); FormattedNumberRange fnr = l.formatFormattableRange(first, second, status); UnicodeString actual = fnr.toString(status); assertEquals(fullMessage, expected, actual); return fnr; } #endif