using System; using System.Collections.Generic; using System.Globalization; using System.Text; using Glass.Text; using UnitTestSharp; namespace Glass.UnitTests.Text { public class PluralRulesTests : TestFixture { public static PluralCategory CardinalOf(string tag, long number) { return PluralRules.For(tag).Cardinal(Styles.Count(number)); } public static PluralCategory OrdinalOf(string tag, long number) { return PluralRules.For(tag).Ordinal(Styles.Plain(number)); } public static PluralCategory CardinalOf(string tag, double number, int decimals) { return PluralRules.For(tag).Cardinal(Styles.Fixed(number, decimals)); } // Expands CLDR's sample text, such as "@integer 1, 2~4, 100, … @decimal 1.0, 1.5~1.7", into the numbers it // names, as the operands each is shown with. Compact samples such as 1c3 are left out. public static List OperandsOfSamples(string samples) { var returnMe = new List(); foreach (string part in samples.Split(new[] { '@' }, StringSplitOptions.RemoveEmptyEntries)) { string list = part.Substring(part.IndexOf(' ') + 1); foreach (string token in list.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries)) { if (token.Contains("…") || token.Contains("c") || token.Contains("e")) { continue; } int tilde = token.IndexOf('~'); if (tilde < 0) { returnMe.Add(OperandsOf(token)); continue; } string low = token.Substring(0, tilde); string high = token.Substring(tilde + 1); int decimals = DecimalsOf(low); decimal step = 1; for (int place = 0; place < decimals; ++place) { step /= 10; } decimal end = decimal.Parse(high, CultureInfo.InvariantCulture); for (decimal value = decimal.Parse(low, CultureInfo.InvariantCulture); value <= end; value += step) { returnMe.Add(PluralOperands.OfDecimal(value, decimals)); } } } return returnMe; } public static int DecimalsOf(string text) { int point = text.IndexOf('.'); if (point < 0) { return 0; } return text.Length - point - 1; } public static PluralOperands OperandsOf(string text) { return PluralOperands.OfDecimal(decimal.Parse(text, CultureInfo.InvariantCulture), DecimalsOf(text)); } public class ForTagTests : TestFixture { public void ALanguage_ReturnsItsRules() { CheckEqual("en", PluralRules.For("en").Tag); CheckEqual("ru", PluralRules.For("ru").Tag); } public void TheSameTag_ReturnsTheSameRules() { CheckTrue(ReferenceEquals(PluralRules.For("en"), PluralRules.For("en"))); } public void ACaseDifference_IsIgnored() { CheckTrue(ReferenceEquals(PluralRules.For("en"), PluralRules.For("EN"))); } public void ARegion_FallsBackToTheLanguage() { CheckTrue(ReferenceEquals(PluralRules.For("en"), PluralRules.For("en-US"))); CheckTrue(ReferenceEquals(PluralRules.For("pt"), PluralRules.For("pt-BR"))); } public void ARegionCldrCovers_KeepsItsOwnRules() { CheckEqual("pt-PT", PluralRules.For("pt-PT").Tag); } public void AnUnderscore_ActsAsADash() { CheckTrue(ReferenceEquals(PluralRules.For("en"), PluralRules.For("en_GB"))); CheckEqual("pt-PT", PluralRules.For("pt_PT").Tag); } public void AScriptAndRegion_FallBackOneSubtagAtATime() { CheckEqual("kok-Latn", PluralRules.For("kok-Latn-IN").Tag); CheckEqual("zh", PluralRules.For("zh-Hant-TW").Tag); } public void ALanguageCldrDoesNotCover_GetsNone() { CheckTrue(ReferenceEquals(PluralRules.None, PluralRules.For("xx"))); CheckTrue(ReferenceEquals(PluralRules.None, PluralRules.For("xx-YY"))); } public void AnEmptyTag_GetsNone() { CheckTrue(ReferenceEquals(PluralRules.None, PluralRules.For(""))); } public void ANullTag_Throws() { CheckThrow(typeof(ArgumentNullException)); PluralRules.For((string)null); } } public class ForCultureTests : TestFixture { public void ACulture_ReturnsItsLanguage() { CheckTrue(ReferenceEquals(PluralRules.For("de"), PluralRules.For(new CultureInfo("de-DE")))); } public void TheInvariantCulture_IsEnglish() { CheckTrue(ReferenceEquals(PluralRules.English, PluralRules.For(CultureInfo.InvariantCulture))); } public void ANullCulture_Throws() { CheckThrow(typeof(ArgumentNullException)); PluralRules.For((CultureInfo)null); } } public class NoneTests : TestFixture { public void HasNoTagAndEveryNumberIsOther() { CheckEqual("", PluralRules.None.Tag); CheckEqual(PluralCategory.Other, PluralRules.None.Cardinal(Styles.Count(1))); CheckEqual(PluralCategory.Other, PluralRules.None.Ordinal(Styles.Plain(1))); } } public class EnglishTests : TestFixture { public void IsTheRulesOfEn() { CheckTrue(ReferenceEquals(PluralRules.For("en"), PluralRules.English)); } } public class CardinalTests : TestFixture { public void English_OneIsOneAndEverythingElseIsOther() { CheckEqual(PluralCategory.One, CardinalOf("en", 1)); CheckEqual(PluralCategory.Other, CardinalOf("en", 0)); CheckEqual(PluralCategory.Other, CardinalOf("en", 2)); CheckEqual(PluralCategory.Other, CardinalOf("en", 21)); CheckEqual(PluralCategory.Other, CardinalOf("en", 1000000)); } public void English_TheSignDoesNotMatter() { CheckEqual(PluralCategory.One, CardinalOf("en", -1)); } public void English_OneWithAVisibleDecimalIsOther() { CheckEqual(PluralCategory.One, CardinalOf("en", 1.0, 0)); CheckEqual(PluralCategory.Other, CardinalOf("en", 1.0, 1)); CheckEqual(PluralCategory.Other, CardinalOf("en", 1.5, 1)); } public void English_AValueThatRoundsToOneIsOne() { CheckEqual(PluralCategory.One, CardinalOf("en", 0.9999, 0)); } public void Russian_UsesOneFewManyAndOther() { CheckEqual(PluralCategory.One, CardinalOf("ru", 1)); CheckEqual(PluralCategory.One, CardinalOf("ru", 21)); CheckEqual(PluralCategory.Few, CardinalOf("ru", 2)); CheckEqual(PluralCategory.Few, CardinalOf("ru", 24)); CheckEqual(PluralCategory.Many, CardinalOf("ru", 0)); CheckEqual(PluralCategory.Many, CardinalOf("ru", 5)); CheckEqual(PluralCategory.Many, CardinalOf("ru", 11)); CheckEqual(PluralCategory.Many, CardinalOf("ru", 12)); CheckEqual(PluralCategory.Many, CardinalOf("ru", 111)); CheckEqual(PluralCategory.Other, CardinalOf("ru", 1.5, 1)); } public void Arabic_UsesAllSixForms() { CheckEqual(PluralCategory.Zero, CardinalOf("ar", 0)); CheckEqual(PluralCategory.One, CardinalOf("ar", 1)); CheckEqual(PluralCategory.Two, CardinalOf("ar", 2)); CheckEqual(PluralCategory.Few, CardinalOf("ar", 3)); CheckEqual(PluralCategory.Few, CardinalOf("ar", 10)); CheckEqual(PluralCategory.Many, CardinalOf("ar", 11)); CheckEqual(PluralCategory.Many, CardinalOf("ar", 99)); CheckEqual(PluralCategory.Other, CardinalOf("ar", 100)); CheckEqual(PluralCategory.Few, CardinalOf("ar", 103)); } public void Polish_SeparatesFewFromMany() { CheckEqual(PluralCategory.One, CardinalOf("pl", 1)); CheckEqual(PluralCategory.Few, CardinalOf("pl", 22)); CheckEqual(PluralCategory.Many, CardinalOf("pl", 12)); CheckEqual(PluralCategory.Many, CardinalOf("pl", 25)); CheckEqual(PluralCategory.Other, CardinalOf("pl", 1.5, 1)); } public void French_ZeroAndOneAreOneAndMillionsAreMany() { CheckEqual(PluralCategory.One, CardinalOf("fr", 0)); CheckEqual(PluralCategory.One, CardinalOf("fr", 1)); CheckEqual(PluralCategory.One, CardinalOf("fr", 1.5, 1)); CheckEqual(PluralCategory.Other, CardinalOf("fr", 2)); CheckEqual(PluralCategory.Many, CardinalOf("fr", 1000000)); CheckEqual(PluralCategory.Many, CardinalOf("fr", 3000000)); CheckEqual(PluralCategory.Other, CardinalOf("fr", 1000001)); } public void Japanese_IsAlwaysOther() { CheckEqual(PluralCategory.Other, CardinalOf("ja", 1)); CheckEqual(PluralCategory.Other, CardinalOf("ja", 2)); } public void Welsh_UsesAllSixForms() { CheckEqual(PluralCategory.Zero, CardinalOf("cy", 0)); CheckEqual(PluralCategory.One, CardinalOf("cy", 1)); CheckEqual(PluralCategory.Two, CardinalOf("cy", 2)); CheckEqual(PluralCategory.Few, CardinalOf("cy", 3)); CheckEqual(PluralCategory.Many, CardinalOf("cy", 6)); CheckEqual(PluralCategory.Other, CardinalOf("cy", 4)); } public void ANumberShownAsASymbol_IsOther() { CheckEqual(PluralCategory.Other, CardinalOf("en", double.NaN, 1)); CheckEqual(PluralCategory.Other, CardinalOf("ru", double.PositiveInfinity, 1)); CheckEqual(PluralCategory.Other, CardinalOf("ar", 1e30, 1)); } public void TheOperandOverload_AgreesWithTheNumberOverload() { PluralOperands operands = PluralOperands.OfWhole(21); CheckEqual(PluralCategory.One, PluralRules.For("ru").Cardinal(operands)); } public void AScientificNumber_Throws() { CheckThrow(typeof(ArgumentException)); PluralRules.English.Cardinal(Styles.Scientific(1, 3)); } } public class OrdinalTests : TestFixture { public void English_FollowsOneTwoFewOther() { CheckEqual(PluralCategory.One, OrdinalOf("en", 1)); CheckEqual(PluralCategory.Two, OrdinalOf("en", 2)); CheckEqual(PluralCategory.Few, OrdinalOf("en", 3)); CheckEqual(PluralCategory.Other, OrdinalOf("en", 4)); CheckEqual(PluralCategory.Other, OrdinalOf("en", 0)); } public void English_TheTeensAreOther() { CheckEqual(PluralCategory.Other, OrdinalOf("en", 11)); CheckEqual(PluralCategory.Other, OrdinalOf("en", 12)); CheckEqual(PluralCategory.Other, OrdinalOf("en", 13)); CheckEqual(PluralCategory.Other, OrdinalOf("en", 111)); } public void English_TheTwentiesFollowTheLastDigit() { CheckEqual(PluralCategory.One, OrdinalOf("en", 21)); CheckEqual(PluralCategory.Two, OrdinalOf("en", 22)); CheckEqual(PluralCategory.Few, OrdinalOf("en", 23)); CheckEqual(PluralCategory.Other, OrdinalOf("en", 24)); CheckEqual(PluralCategory.One, OrdinalOf("en", 101)); } public void ALanguageWithNoOrdinalForms_IsOther() { CheckEqual(PluralCategory.Other, OrdinalOf("de", 1)); CheckEqual(PluralCategory.Other, OrdinalOf("ja", 2)); } public void French_FirstIsOne() { CheckEqual(PluralCategory.One, OrdinalOf("fr", 1)); CheckEqual(PluralCategory.Other, OrdinalOf("fr", 2)); } public void ANumberShownAsASymbol_IsOther() { CheckEqual(PluralCategory.Other, PluralRules.English.Ordinal(Styles.Fixed(double.NaN, 1))); } public void TheOperandOverload_AgreesWithTheNumberOverload() { CheckEqual(PluralCategory.Few, PluralRules.English.Ordinal(PluralOperands.OfWhole(23))); } public void AScientificNumber_Throws() { CheckThrow(typeof(ArgumentException)); PluralRules.English.Ordinal(Styles.ShortExponent(1, 3)); } } public class EveryCldrSampleTests : TestFixture { [IgnoreTest] public string Failures(string kind) { var failures = new StringBuilder(); int checkedCount = 0; int failureCount = 0; foreach (string entry in PluralSamples.Entries) { string[] parts = entry.Split(new[] { '|' }, 4); if (parts[0] != kind) { continue; } PluralRules rules = PluralRules.For(parts[1]); string expected = parts[2]; foreach (PluralOperands operands in OperandsOfSamples(parts[3])) { PluralCategory actual = rules.Cardinal(operands); if (kind == "ordinal") { actual = rules.Ordinal(operands); } ++checkedCount; if (!string.Equals(actual.ToString(), expected, StringComparison.OrdinalIgnoreCase)) { ++failureCount; if (failureCount <= 10) { failures.Append(parts[1] + " " + operands.N + " v" + operands.V + " is " + actual + " not " + expected + "; "); } } } } if (checkedCount < 1000) { return "Only " + checkedCount + " samples were checked."; } if (failureCount == 0) { return ""; } return failureCount + " of " + checkedCount + " failed: " + failures; } public void EveryCardinalSample_FallsInItsCategory() { CheckEqual("", Failures("cardinal")); } public void EveryOrdinalSample_FallsInItsCategory() { CheckEqual("", Failures("ordinal")); } public void ThePluralSamplesParser_ExpandsRangesAndDecimals() { List operands = OperandsOfSamples("@integer 1, 3~5, 100, … @decimal 0.5~0.7, 1.00, 1c3"); CheckEqual(9, operands.Count); CheckEqual(4m, operands[2].N); CheckEqual(0.6m, operands[6].N); CheckEqual(2, operands[8].V); } } } }