| // © 2018 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| package com.ibm.icu.dev.test.number; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.math.RoundingMode; |
| |
| import org.junit.Test; |
| |
| import com.ibm.icu.number.NumberFormatter; |
| import com.ibm.icu.number.SkeletonSyntaxException; |
| import com.ibm.icu.util.ULocale; |
| |
| /** |
| * @author sffc |
| * |
| */ |
| public class NumberSkeletonTest { |
| |
| @Test |
| public void validTokens() { |
| // This tests only if the tokens are valid, not their behavior. |
| // Most of these are from the design doc. |
| String[] cases = { |
| "precision-integer", |
| "precision-unlimited", |
| "@@@##", |
| "@@*", |
| "@@+", |
| ".000##", |
| ".00*", |
| ".00+", |
| ".", |
| ".*", |
| ".+", |
| ".######", |
| ".00/@@*", |
| ".00/@@+", |
| ".00/@##", |
| "precision-increment/3.14", |
| "precision-currency-standard", |
| "precision-integer rounding-mode-half-up", |
| ".00# rounding-mode-ceiling", |
| ".00/@@* rounding-mode-floor", |
| ".00/@@+ rounding-mode-floor", |
| "scientific", |
| "scientific/*ee", |
| "scientific/+ee", |
| "scientific/sign-always", |
| "scientific/*ee/sign-always", |
| "scientific/+ee/sign-always", |
| "scientific/sign-always/*ee", |
| "scientific/sign-always/+ee", |
| "scientific/sign-except-zero", |
| "engineering", |
| "engineering/*eee", |
| "engineering/+eee", |
| "compact-short", |
| "compact-long", |
| "notation-simple", |
| "percent", |
| "permille", |
| "measure-unit/length-meter", |
| "measure-unit/area-square-meter", |
| "measure-unit/energy-joule per-measure-unit/length-meter", |
| "unit/square-meter-per-square-meter", |
| "currency/XXX", |
| "currency/ZZZ", |
| "currency/usd", |
| "group-off", |
| "group-min2", |
| "group-auto", |
| "group-on-aligned", |
| "group-thousands", |
| "integer-width/00", |
| "integer-width/#0", |
| "integer-width/*00", |
| "integer-width/+00", |
| "sign-always", |
| "sign-auto", |
| "sign-never", |
| "sign-accounting", |
| "sign-accounting-always", |
| "sign-except-zero", |
| "sign-accounting-except-zero", |
| "unit-width-narrow", |
| "unit-width-short", |
| "unit-width-iso-code", |
| "unit-width-full-name", |
| "unit-width-hidden", |
| "decimal-auto", |
| "decimal-always", |
| "scale/5.2", |
| "scale/-5.2", |
| "scale/100", |
| "scale/1E2", |
| "scale/1", |
| "latin", |
| "numbering-system/arab", |
| "numbering-system/latn", |
| "precision-integer/@##", |
| "precision-integer rounding-mode-ceiling", |
| "precision-currency-cash rounding-mode-ceiling", |
| "0", |
| "00", |
| "000", |
| "E0", |
| "E00", |
| "E000", |
| "EE0", |
| "EE00", |
| "EE+?0", |
| "EE+?00", |
| "EE+!0", |
| "EE+!00", |
| }; |
| |
| for (String cas : cases) { |
| try { |
| NumberFormatter.forSkeleton(cas); |
| } catch (SkeletonSyntaxException e) { |
| fail(e.getMessage()); |
| } |
| } |
| } |
| |
| @Test |
| public void invalidTokens() { |
| String[] cases = { |
| ".00x", |
| ".00##0", |
| ".##*", |
| ".00##*", |
| ".0#*", |
| "@#*", |
| ".##+", |
| ".00##+", |
| ".0#+", |
| "@#+", |
| "@@x", |
| "@@##0", |
| ".00/@", |
| ".00/@@", |
| ".00/@@x", |
| ".00/@@#", |
| ".00/@@#*", |
| ".00/floor/@@*", // wrong order |
| ".00/@@#+", |
| ".00/floor/@@+", // wrong order |
| "precision-increment/français", // non-invariant characters for C++ |
| "scientific/ee", |
| "precision-increment/xxx", |
| "precision-increment/NaN", |
| "precision-increment/0.1.2", |
| "scale/xxx", |
| "scale/NaN", |
| "scale/0.1.2", |
| "scale/français", // non-invariant characters for C++ |
| "currency/dummy", |
| "currency/ççç", // three characters but not ASCII |
| "measure-unit/foo", |
| "integer-width/xxx", |
| "integer-width/0*", |
| "integer-width/*0#", |
| "integer-width/*#", |
| "integer-width/*#0", |
| "integer-width/0+", |
| "integer-width/+0#", |
| "integer-width/+#", |
| "integer-width/+#0", |
| "scientific/foo", |
| "E", |
| "E1", |
| "E+", |
| "E+?", |
| "E+!", |
| "E+0", |
| "EE", |
| "EE+", |
| "EEE", |
| "EEE0", |
| "001", |
| "00*", |
| "00+", |
| }; |
| |
| for (String cas : cases) { |
| try { |
| NumberFormatter.forSkeleton(cas); |
| fail(cas); |
| } catch (SkeletonSyntaxException expected) { |
| assertTrue(expected.getMessage(), expected.getMessage().contains("Invalid")); |
| } |
| } |
| } |
| |
| @Test |
| public void unknownTokens() { |
| String[] cases = { |
| "maesure-unit", |
| "measure-unit/foo-bar", |
| "numbering-system/dummy", |
| "français", |
| "measure-unit/français-français", // non-invariant characters for C++ |
| "numbering-system/français", // non-invariant characters for C++ |
| "currency-USD" }; |
| |
| for (String cas : cases) { |
| try { |
| NumberFormatter.forSkeleton(cas); |
| fail(cas); |
| } catch (SkeletonSyntaxException expected) { |
| assertTrue(expected.getMessage(), expected.getMessage().contains("Unknown")); |
| } |
| } |
| } |
| |
| @Test |
| public void unexpectedTokens() { |
| String[] cases = { |
| "group-thousands/foo", |
| "precision-integer//@## group-off", |
| "precision-integer//@## group-off", |
| "precision-integer/ group-off", |
| "precision-integer// group-off" }; |
| |
| for (String cas : cases) { |
| try { |
| NumberFormatter.forSkeleton(cas); |
| fail(cas); |
| } catch (SkeletonSyntaxException expected) { |
| assertTrue(expected.getMessage(), expected.getMessage().contains("Unexpected")); |
| } |
| } |
| } |
| |
| @Test |
| public void duplicateValues() { |
| String[] cases = { |
| "precision-integer precision-integer", |
| "precision-integer .00+", |
| "precision-integer precision-unlimited", |
| "precision-integer @@@", |
| "scientific engineering", |
| "engineering compact-long", |
| "sign-auto sign-always" }; |
| |
| for (String cas : cases) { |
| try { |
| NumberFormatter.forSkeleton(cas); |
| fail(cas); |
| } catch (SkeletonSyntaxException expected) { |
| assertTrue(expected.getMessage(), expected.getMessage().contains("Duplicated")); |
| } |
| } |
| } |
| |
| @Test |
| public void stemsRequiringOption() { |
| String[] stems = { |
| "precision-increment", |
| "measure-unit", |
| "per-measure-unit", |
| "currency", |
| "integer-width", |
| "numbering-system", |
| "scale" }; |
| String[] suffixes = { "", "/@##", " scientific", "/@## scientific" }; |
| |
| for (String stem : stems) { |
| for (String suffix : suffixes) { |
| String skeletonString = stem + suffix; |
| try { |
| NumberFormatter.forSkeleton(skeletonString); |
| fail(skeletonString); |
| } catch (SkeletonSyntaxException expected) { |
| // Success |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void defaultTokens() { |
| String[] cases = { |
| "notation-simple", |
| "base-unit", |
| "group-auto", |
| "integer-width/+0", |
| "sign-auto", |
| "unit-width-short", |
| "decimal-auto" }; |
| |
| for (String skeleton : cases) { |
| String normalized = NumberFormatter.forSkeleton(skeleton).toSkeleton(); |
| assertEquals("Skeleton should become empty when normalized: " + skeleton, "", normalized); |
| } |
| } |
| |
| @Test |
| public void flexibleSeparators() { |
| String[][] cases = { |
| { "precision-integer group-off", "5142" }, |
| { "precision-integer group-off", "5142" }, |
| { "precision-integer/@## group-off", "5140" }, |
| { "precision-integer/@## group-off", "5140" }, }; |
| |
| for (String[] cas : cases) { |
| String skeleton = cas[0]; |
| String expected = cas[1]; |
| String actual = NumberFormatter.forSkeleton(skeleton).locale(ULocale.ENGLISH).format(5142.3) |
| .toString(); |
| assertEquals(skeleton, expected, actual); |
| } |
| } |
| |
| @Test |
| public void wildcardCharacters() { |
| String[][] cases = { |
| { ".00*", ".00+" }, |
| { "@@*", "@@+" }, |
| { ".00/@@*", ".00/@@+" }, |
| { "scientific/*ee", "scientific/+ee" }, |
| { "integer-width/*00", "integer-width/+00" }, |
| }; |
| |
| for (String[] cas : cases) { |
| String star = cas[0]; |
| String plus = cas[1]; |
| |
| String normalized = NumberFormatter.forSkeleton(plus) |
| .toSkeleton(); |
| assertEquals("Plus should normalize to star", star, normalized); |
| } |
| } |
| |
| @Test |
| public void roundingModeNames() { |
| for (RoundingMode mode : RoundingMode.values()) { |
| if (mode == RoundingMode.HALF_EVEN) { |
| // This rounding mode is not printed in the skeleton since it is the default |
| continue; |
| } |
| String skeleton = NumberFormatter.with().roundingMode(mode).toSkeleton(); |
| String modeString = mode.toString().toLowerCase().replace('_', '-'); |
| assertEquals(mode.toString(), modeString, skeleton.substring(14)); |
| } |
| } |
| |
| @Test |
| public void perUnitInArabic() { |
| String[][] cases = { |
| {"area", "acre"}, |
| {"digital", "bit"}, |
| {"digital", "byte"}, |
| {"temperature", "celsius"}, |
| {"length", "centimeter"}, |
| {"duration", "day"}, |
| {"angle", "degree"}, |
| {"temperature", "fahrenheit"}, |
| {"volume", "fluid-ounce"}, |
| {"length", "foot"}, |
| {"volume", "gallon"}, |
| {"digital", "gigabit"}, |
| {"digital", "gigabyte"}, |
| {"mass", "gram"}, |
| {"area", "hectare"}, |
| {"duration", "hour"}, |
| {"length", "inch"}, |
| {"digital", "kilobit"}, |
| {"digital", "kilobyte"}, |
| {"mass", "kilogram"}, |
| {"length", "kilometer"}, |
| {"volume", "liter"}, |
| {"digital", "megabit"}, |
| {"digital", "megabyte"}, |
| {"length", "meter"}, |
| {"length", "mile"}, |
| {"length", "mile-scandinavian"}, |
| {"volume", "milliliter"}, |
| {"length", "millimeter"}, |
| {"duration", "millisecond"}, |
| {"duration", "minute"}, |
| {"duration", "month"}, |
| {"mass", "ounce"}, |
| {"concentr", "percent"}, |
| {"digital", "petabyte"}, |
| {"mass", "pound"}, |
| {"duration", "second"}, |
| {"mass", "stone"}, |
| {"digital", "terabit"}, |
| {"digital", "terabyte"}, |
| {"duration", "week"}, |
| {"length", "yard"}, |
| {"duration", "year"}, |
| }; |
| |
| ULocale arabic = new ULocale("ar"); |
| for (String[] cas1 : cases) { |
| for (String[] cas2 : cases) { |
| String skeleton = "measure-unit/"; |
| skeleton += cas1[0] + "-" + cas1[1] + " per-measure-unit/" + cas2[0] + "-" + cas2[1]; |
| |
| String actual = NumberFormatter.forSkeleton(skeleton).locale(arabic).format(5142.3) |
| .toString(); |
| // Just make sure it won't throw exception |
| } |
| } |
| } |
| } |