// © 2017 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 "unicode/dcfmtsym.h" #include "cstr.h" #include "numbertest.h" #include "number_utils.h" #include "number_skeletons.h" #include "putilimp.h" using namespace icu::number::impl; void NumberSkeletonTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { if (exec) { logln("TestSuite AffixUtilsTest: "); } TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(validTokens); TESTCASE_AUTO(invalidTokens); TESTCASE_AUTO(unknownTokens); TESTCASE_AUTO(unexpectedTokens); TESTCASE_AUTO(duplicateValues); TESTCASE_AUTO(stemsRequiringOption); TESTCASE_AUTO(defaultTokens); TESTCASE_AUTO(flexibleSeparators); TESTCASE_AUTO_END; } void NumberSkeletonTest::validTokens() { IcuTestErrorCode status(*this, "validTokens"); // This tests only if the tokens are valid, not their behavior. // Most of these are from the design doc. static const char16_t* cases[] = { u"precision-integer", u"precision-unlimited", u"@@@##", u"@@+", u".000##", u".00+", u".", u".+", u".######", u".00/@@+", u".00/@##", u"precision-increment/3.14", u"precision-currency-standard", u"precision-integer rounding-mode-half-up", u".00# rounding-mode-ceiling", u".00/@@+ rounding-mode-floor", u"scientific", u"scientific/+ee", u"scientific/sign-always", u"scientific/+ee/sign-always", u"scientific/sign-always/+ee", u"scientific/sign-except-zero", u"engineering", u"engineering/+eee", u"compact-short", u"compact-long", u"notation-simple", u"percent", u"permille", u"measure-unit/length-meter", u"measure-unit/area-square-meter", u"measure-unit/energy-joule per-measure-unit/length-meter", u"currency/XXX", u"currency/ZZZ", u"currency/usd", u"group-off", u"group-min2", u"group-auto", u"group-on-aligned", u"group-thousands", u"integer-width/00", u"integer-width/#0", u"integer-width/+00", u"sign-always", u"sign-auto", u"sign-never", u"sign-accounting", u"sign-accounting-always", u"sign-except-zero", u"sign-accounting-except-zero", u"unit-width-narrow", u"unit-width-short", u"unit-width-iso-code", u"unit-width-full-name", u"unit-width-hidden", u"decimal-auto", u"decimal-always", u"scale/5.2", u"scale/-5.2", u"scale/100", u"scale/1E2", u"scale/1", u"latin", u"numbering-system/arab", u"numbering-system/latn", u"precision-integer/@##", u"precision-integer rounding-mode-ceiling", u"precision-currency-cash rounding-mode-ceiling"}; for (auto& cas : cases) { UnicodeString skeletonString(cas); status.setScope(skeletonString); NumberFormatter::forSkeleton(skeletonString, status); assertSuccess(CStr(skeletonString)(), status, true); status.errIfFailureAndReset(); } } void NumberSkeletonTest::invalidTokens() { static const char16_t* cases[] = { u".00x", u".00##0", u".##+", u".00##+", u".0#+", u"@@x", u"@@##0", u"@#+", u".00/@", u".00/@@", u".00/@@x", u".00/@@#", u".00/@@#+", u".00/floor/@@+", // wrong order u"precision-increment/français", // non-invariant characters for C++ u"scientific/ee", u"precision-increment/xxx", u"precision-increment/NaN", u"precision-increment/0.1.2", u"scale/xxx", u"scale/NaN", u"scale/0.1.2", u"scale/français", // non-invariant characters for C++ u"currency/dummy", u"currency/ççç", // three characters but not ASCII u"measure-unit/foo", u"integer-width/xxx", u"integer-width/0+", u"integer-width/+0#", u"scientific/foo"}; expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); } void NumberSkeletonTest::unknownTokens() { static const char16_t* cases[] = { u"maesure-unit", u"measure-unit/foo-bar", u"numbering-system/dummy", u"français", u"measure-unit/français-français", // non-invariant characters for C++ u"numbering-system/français", // non-invariant characters for C++ u"currency-USD"}; expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); } void NumberSkeletonTest::unexpectedTokens() { static const char16_t* cases[] = { u"group-thousands/foo", u"precision-integer//@## group-off", u"precision-integer//@## group-off", u"precision-integer/ group-off", u"precision-integer// group-off"}; expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); } void NumberSkeletonTest::duplicateValues() { static const char16_t* cases[] = { u"precision-integer precision-integer", u"precision-integer .00+", u"precision-integer precision-unlimited", u"precision-integer @@@", u"scientific engineering", u"engineering compact-long", u"sign-auto sign-always"}; expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); } void NumberSkeletonTest::stemsRequiringOption() { static const char16_t* stems[] = { u"precision-increment", u"measure-unit", u"per-unit", u"currency", u"integer-width", u"numbering-system", u"scale"}; static const char16_t* suffixes[] = {u"", u"/@##", u" scientific", u"/@## scientific"}; for (auto& stem : stems) { for (auto& suffix : suffixes) { UnicodeString skeletonString = UnicodeString(stem) + suffix; UErrorCode status = U_ZERO_ERROR; NumberFormatter::forSkeleton(skeletonString, status); assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); } } } void NumberSkeletonTest::defaultTokens() { IcuTestErrorCode status(*this, "defaultTokens"); static const char16_t* cases[] = { u"notation-simple", u"base-unit", u"group-auto", u"integer-width/+0", u"sign-auto", u"unit-width-short", u"decimal-auto"}; for (auto& cas : cases) { UnicodeString skeletonString(cas); status.setScope(skeletonString); UnicodeString normalized = NumberFormatter::forSkeleton( skeletonString, status).toSkeleton(status); // Skeleton should become empty when normalized assertEquals(skeletonString, u"", normalized); status.errIfFailureAndReset(); } } void NumberSkeletonTest::flexibleSeparators() { IcuTestErrorCode status(*this, "flexibleSeparators"); static struct TestCase { const char16_t* skeleton; const char16_t* expected; } cases[] = {{u"precision-integer group-off", u"5142"}, {u"precision-integer group-off", u"5142"}, {u"precision-integer/@## group-off", u"5140"}, {u"precision-integer/@## group-off", u"5140"}}; for (auto& cas : cases) { UnicodeString skeletonString(cas.skeleton); UnicodeString expected(cas.expected); status.setScope(skeletonString); UnicodeString actual = NumberFormatter::forSkeleton(skeletonString, status).locale("en") .formatDouble(5142.3, status) .toString(); if (!status.errDataIfFailureAndReset()) { assertEquals(skeletonString, expected, actual); } status.errIfFailureAndReset(); } } // In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens. void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) { for (int32_t i = 0; i < casesLen; i++) { UnicodeString skeletonString(cases[i]); UErrorCode status = U_ZERO_ERROR; NumberFormatter::forSkeleton(skeletonString, status); assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); } } #endif /* #if !UCONFIG_NO_FORMATTING */