| // Copyright 2010 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide('goog.i18n.MessageFormatTest'); |
| goog.setTestOnly('goog.i18n.MessageFormatTest'); |
| |
| goog.require('goog.i18n.MessageFormat'); |
| goog.require('goog.i18n.NumberFormatSymbols_hr'); |
| goog.require('goog.i18n.pluralRules'); |
| goog.require('goog.testing.PropertyReplacer'); |
| goog.require('goog.testing.jsunit'); |
| |
| // Testing stubs that autoreset after each test run. |
| var stubs = new goog.testing.PropertyReplacer(); |
| |
| function tearDown() { |
| stubs.reset(); |
| } |
| |
| function testEmptyPattern() { |
| var fmt = new goog.i18n.MessageFormat(''); |
| assertEquals('', fmt.format({})); |
| } |
| |
| function testMissingLeftCurlyBrace() { |
| var err = assertThrows(function() { |
| new goog.i18n.MessageFormat('\'\'{}}'); |
| }); |
| assertEquals('Assertion failed: No matching { for }.', err.message); |
| } |
| |
| function testTooManyLeftCurlyBraces() { |
| var err = assertThrows(function() { |
| new goog.i18n.MessageFormat('{} {'); |
| }); |
| assertEquals('Assertion failed: There are mismatched { or } in the pattern.', |
| err.message); |
| } |
| |
| function testSimpleReplacement() { |
| var fmt = new goog.i18n.MessageFormat('New York in {SEASON} is nice.'); |
| assertEquals('New York in the Summer is nice.', |
| fmt.format({'SEASON': 'the Summer'})); |
| } |
| |
| function testSimpleSelect() { |
| var fmt = new goog.i18n.MessageFormat('{GENDER, select,' + |
| 'male {His} ' + |
| 'female {Her} ' + |
| 'other {Its}}' + |
| ' bicycle is {GENDER, select, male {blue} female {red} other {green}}.'); |
| |
| assertEquals('His bicycle is blue.', fmt.format({'GENDER': 'male'})); |
| assertEquals('Her bicycle is red.', fmt.format({'GENDER': 'female'})); |
| assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'other'})); |
| assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'whatever'})); |
| } |
| |
| function testSimplePlural() { |
| var fmt = new goog.i18n.MessageFormat('I see {NUM_PEOPLE, plural, offset:1 ' + |
| '=0 {no one at all in {PLACE}.} ' + |
| '=1 {{PERSON} in {PLACE}.} ' + |
| 'one {{PERSON} and one other person in {PLACE}.} ' + |
| 'other {{PERSON} and # other people in {PLACE}.}}'); |
| |
| assertEquals('I see no one at all in Belgrade.', |
| fmt.format({'NUM_PEOPLE': 0, |
| 'PLACE': 'Belgrade'})); |
| assertEquals('I see Markus in Berlin.', |
| fmt.format({'NUM_PEOPLE': 1, |
| 'PERSON': 'Markus', |
| 'PLACE': 'Berlin'})); |
| assertEquals('I see Mark and one other person in Athens.', |
| fmt.format({'NUM_PEOPLE': 2, |
| 'PERSON': 'Mark', |
| 'PLACE': 'Athens'})); |
| assertEquals('I see Cibu and 99 other people in the cubes.', |
| fmt.format({'NUM_PEOPLE': 100, |
| 'PERSON': 'Cibu', |
| 'PLACE': 'the cubes'})); |
| } |
| |
| function testSimplePluralNoOffset() { |
| var fmt = new goog.i18n.MessageFormat('I see {NUM_PEOPLE, plural, ' + |
| '=0 {no one at all} ' + |
| '=1 {{PERSON}} ' + |
| 'one {{PERSON} and one other person} ' + |
| 'other {{PERSON} and # other people}} in {PLACE}.'); |
| |
| assertEquals('I see no one at all in Belgrade.', |
| fmt.format({'NUM_PEOPLE': 0, |
| 'PLACE': 'Belgrade'})); |
| assertEquals('I see Markus in Berlin.', |
| fmt.format({'NUM_PEOPLE': 1, |
| 'PERSON': 'Markus', |
| 'PLACE': 'Berlin'})); |
| assertEquals('I see Mark and 2 other people in Athens.', |
| fmt.format({'NUM_PEOPLE': 2, |
| 'PERSON': 'Mark', |
| 'PLACE': 'Athens'})); |
| assertEquals('I see Cibu and 100 other people in the cubes.', |
| fmt.format({'NUM_PEOPLE': 100, |
| 'PERSON': 'Cibu', |
| 'PLACE': 'the cubes'})); |
| } |
| |
| function testSelectNestedInPlural() { |
| var fmt = new goog.i18n.MessageFormat('{CIRCLES, plural, ' + |
| 'one {{GENDER, select, ' + |
| ' female {{WHO} added you to her circle} ' + |
| ' other {{WHO} added you to his circle}}} ' + |
| 'other {{GENDER, select, ' + |
| ' female {{WHO} added you to her # circles} ' + |
| ' other {{WHO} added you to his # circles}}}}'); |
| |
| assertEquals('Jelena added you to her circle', |
| fmt.format({'GENDER': 'female', |
| 'WHO': 'Jelena', |
| 'CIRCLES': 1})); |
| assertEquals('Milan added you to his 1,234 circles', |
| fmt.format({'GENDER': 'male', |
| 'WHO': 'Milan', |
| 'CIRCLES': 1234})); |
| } |
| |
| function testPluralNestedInSelect() { |
| // Added offset just for testing purposes. It doesn't make sense |
| // to have it otherwise. |
| var fmt = new goog.i18n.MessageFormat('{GENDER, select, ' + |
| 'female {{NUM_GROUPS, plural, ' + |
| ' one {{WHO} added you to her group} ' + |
| ' other {{WHO} added you to her # groups}}} ' + |
| 'other {{NUM_GROUPS, plural, offset:1' + |
| ' one {{WHO} added you to his group} ' + |
| ' other {{WHO} added you to his # groups}}}}'); |
| |
| assertEquals('Jelena added you to her group', |
| fmt.format({'GENDER': 'female', |
| 'WHO': 'Jelena', |
| 'NUM_GROUPS': 1})); |
| assertEquals('Milan added you to his 1,233 groups', |
| fmt.format({'GENDER': 'male', |
| 'WHO': 'Milan', |
| 'NUM_GROUPS': 1234})); |
| } |
| |
| function testLiteralOpenCurlyBrace() { |
| var fmt = new goog.i18n.MessageFormat("Anna's house" + |
| " has '{0} and # in the roof' and {NUM_COWS} cows."); |
| assertEquals("Anna's house has {0} and # in the roof and 5 cows.", |
| fmt.format({'NUM_COWS': '5'})); |
| } |
| |
| function testLiteralClosedCurlyBrace() { |
| var fmt = new goog.i18n.MessageFormat("Anna's house" + |
| " has '{'0'} and # in the roof' and {NUM_COWS} cows."); |
| assertEquals("Anna's house has {0} and # in the roof and 5 cows.", |
| fmt.format({'NUM_COWS': '5'})); |
| } |
| |
| function testLiteralPoundSign() { |
| var fmt = new goog.i18n.MessageFormat("Anna's house" + |
| " has '{0}' and '# in the roof' and {NUM_COWS} cows."); |
| assertEquals("Anna's house has {0} and # in the roof and 5 cows.", |
| fmt.format({'NUM_COWS': '5'})); |
| } |
| |
| function testNoLiteralsForSingleQuotes() { |
| var fmt = new goog.i18n.MessageFormat("Anna's house" + |
| " 'has {NUM_COWS} cows'."); |
| assertEquals("Anna's house 'has 5 cows'.", fmt.format({'NUM_COWS': '5'})); |
| } |
| |
| function testConsecutiveSingleQuotesAreReplacedWithOneSingleQuote() { |
| var fmt = new goog.i18n.MessageFormat("Anna''s house a'{''''b'"); |
| assertEquals("Anna's house a{''b", fmt.format({})); |
| } |
| |
| function testConsecutiveSingleQuotesBeforeSpecialCharDontCreateLiteral() { |
| var fmt = new goog.i18n.MessageFormat("a''{NUM_COWS}'b"); |
| assertEquals("a'5'b", fmt.format({'NUM_COWS': '5'})); |
| } |
| |
| function testSerbianSimpleSelect() { |
| stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); |
| |
| var fmt = new goog.i18n.MessageFormat('{GENDER, select, ' + |
| 'female {Njen} other {Njegov}} bicikl je ' + |
| '{GENDER, select, female {crven} other {plav}}.'); |
| |
| assertEquals('Njegov bicikl je plav.', fmt.format({'GENDER': 'male'})); |
| assertEquals('Njen bicikl je crven.', fmt.format({'GENDER': 'female'})); |
| } |
| |
| function testSerbianSimplePlural() { |
| stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); |
| |
| var fmt = new goog.i18n.MessageFormat('Ja {NUM_PEOPLE, plural, offset:1 ' + |
| '=0 {ne vidim nikoga} ' + |
| '=1 {vidim {PERSON}} ' + |
| 'one {vidim {PERSON} i jos # osobu} ' + |
| 'few {vidim {PERSON} i jos # osobe} ' + |
| 'many {vidim {PERSON} i jos # osoba} ' + |
| 'other {{PERSON} i jos # osoba}} ' + |
| 'u {PLACE}.'); |
| |
| assertEquals('Ja ne vidim nikoga u Beogradu.', |
| fmt.format({'NUM_PEOPLE': 0, |
| 'PLACE': 'Beogradu'})); |
| assertEquals('Ja vidim Markusa u Berlinu.', |
| fmt.format({'NUM_PEOPLE': 1, |
| 'PERSON': 'Markusa', |
| 'PLACE': 'Berlinu'})); |
| assertEquals('Ja vidim Marka i jos 1 osobu u Atini.', |
| fmt.format({'NUM_PEOPLE': 2, |
| 'PERSON': 'Marka', |
| 'PLACE': 'Atini'})); |
| assertEquals('Ja vidim Petra i jos 3 osobe u muzeju.', |
| fmt.format({'NUM_PEOPLE': 4, |
| 'PERSON': 'Petra', |
| 'PLACE': 'muzeju'})); |
| assertEquals('Ja vidim Cibua i jos 99 osoba u bazenu.', |
| fmt.format({'NUM_PEOPLE': 100, |
| 'PERSON': 'Cibua', |
| 'PLACE': 'bazenu'})); |
| } |
| |
| function testSerbianSimplePluralNoOffset() { |
| stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); |
| |
| var fmt = new goog.i18n.MessageFormat('Ja {NUM_PEOPLE, plural, ' + |
| '=0 {ne vidim nikoga} ' + |
| '=1 {vidim {PERSON}} ' + |
| 'one {vidim {PERSON} i jos # osobu} ' + |
| 'few {vidim {PERSON} i jos # osobe} ' + |
| 'many {vidim {PERSON} i jos # osoba} ' + |
| 'other {{PERSON} i jos # osoba}} ' + |
| 'u {PLACE}.'); |
| |
| assertEquals('Ja ne vidim nikoga u Beogradu.', |
| fmt.format({'NUM_PEOPLE': 0, |
| 'PLACE': 'Beogradu'})); |
| assertEquals('Ja vidim Markusa u Berlinu.', |
| fmt.format({'NUM_PEOPLE': 1, |
| 'PERSON': 'Markusa', |
| 'PLACE': 'Berlinu'})); |
| assertEquals('Ja vidim Marka i jos 21 osobu u Atini.', |
| fmt.format({'NUM_PEOPLE': 21, |
| 'PERSON': 'Marka', |
| 'PLACE': 'Atini'})); |
| assertEquals('Ja vidim Petra i jos 3 osobe u muzeju.', |
| fmt.format({'NUM_PEOPLE': 3, |
| 'PERSON': 'Petra', |
| 'PLACE': 'muzeju'})); |
| assertEquals('Ja vidim Cibua i jos 100 osoba u bazenu.', |
| fmt.format({'NUM_PEOPLE': 100, |
| 'PERSON': 'Cibua', |
| 'PLACE': 'bazenu'})); |
| } |
| |
| function testSerbianSelectNestedInPlural() { |
| stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); |
| stubs.set(goog.i18n, 'NumberFormatSymbols', goog.i18n.NumberFormatSymbols_hr); |
| |
| var fmt = new goog.i18n.MessageFormat('{CIRCLES, plural, ' + |
| 'one {{GENDER, select, ' + |
| ' female {{WHO} vas je dodala u njen # kruzok} ' + |
| ' other {{WHO} vas je dodao u njegov # kruzok}}} ' + |
| 'few {{GENDER, select, ' + |
| ' female {{WHO} vas je dodala u njena # kruzoka} ' + |
| ' other {{WHO} vas je dodao u njegova # kruzoka}}} ' + |
| 'many {{GENDER, select, ' + |
| ' female {{WHO} vas je dodala u njenih # kruzoka} ' + |
| ' other {{WHO} vas je dodao u njegovih # kruzoka}}} ' + |
| 'other {{GENDER, select, ' + |
| ' female {{WHO} vas je dodala u njenih # kruzoka} ' + |
| ' other {{WHO} vas je dodao u njegovih # kruzoka}}}}'); |
| |
| assertEquals('Jelena vas je dodala u njen 21 kruzok', |
| fmt.format({'GENDER': 'female', |
| 'WHO': 'Jelena', |
| 'CIRCLES': 21})); |
| assertEquals('Jelena vas je dodala u njena 3 kruzoka', |
| fmt.format({'GENDER': 'female', |
| 'WHO': 'Jelena', |
| 'CIRCLES': 3})); |
| assertEquals('Jelena vas je dodala u njenih 5 kruzoka', |
| fmt.format({'GENDER': 'female', |
| 'WHO': 'Jelena', |
| 'CIRCLES': 5})); |
| assertEquals('Milan vas je dodao u njegovih 1.235 kruzoka', |
| fmt.format({'GENDER': 'male', |
| 'WHO': 'Milan', |
| 'CIRCLES': 1235})); |
| } |
| |
| function testFallbackToOtherOptionInPlurals() { |
| // Use Arabic plural rules since they have all six cases. |
| // Only locale and numbers matter, the actual language of the message |
| // does not. |
| stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.arSelect_); |
| |
| var fmt = new goog.i18n.MessageFormat('{NUM_MINUTES, plural, ' + |
| 'other {# minutes}}'); |
| |
| // These numbers exercise all cases for the arabic plural rules. |
| assertEquals('0 minutes', fmt.format({'NUM_MINUTES': 0})); |
| assertEquals('1 minutes', fmt.format({'NUM_MINUTES': 1})); |
| assertEquals('2 minutes', fmt.format({'NUM_MINUTES': 2})); |
| assertEquals('3 minutes', fmt.format({'NUM_MINUTES': 3})); |
| assertEquals('11 minutes', fmt.format({'NUM_MINUTES': 11})); |
| assertEquals('1.5 minutes', fmt.format({'NUM_MINUTES': 1.5})); |
| } |
| |
| function testPoundShowsNumberMinusOffsetInAllCases() { |
| var fmt = new goog.i18n.MessageFormat('{SOME_NUM, plural, offset:1 ' + |
| '=0 {#} =1 {#} =2 {#}one {#} other {#}}'); |
| |
| assertEquals('-1', fmt.format({'SOME_NUM': '0'})); |
| assertEquals('0', fmt.format({'SOME_NUM': '1'})); |
| assertEquals('1', fmt.format({'SOME_NUM': '2'})); |
| assertEquals('20', fmt.format({'SOME_NUM': '21'})); |
| } |
| |
| function testSpecialCharactersInParamaterDontChangeFormat() { |
| var fmt = new goog.i18n.MessageFormat('{SOME_NUM, plural,' + |
| 'other {# {GROUP}}}'); |
| |
| // Test pound sign. |
| assertEquals('10 group#1', |
| fmt.format({'SOME_NUM': '10', 'GROUP': 'group#1'})); |
| // Test other special characters in parameters, like { and }. |
| assertEquals('10 } {', fmt.format({'SOME_NUM': '10', 'GROUP': '} {'})); |
| } |
| |
| function testMissingOrInvalidPluralParameter() { |
| var fmt = new goog.i18n.MessageFormat('{SOME_NUM, plural,' + |
| 'other {result}}'); |
| |
| // Key name doesn't match A != SOME_NUM. |
| assertEquals('Undefined or invalid parameter - SOME_NUM', |
| fmt.format({A: '10'})); |
| |
| // Value is not a number. |
| assertEquals('Undefined or invalid parameter - SOME_NUM', |
| fmt.format({'SOME_NUM': 'Value'})); |
| } |
| |
| function testMissingSelectParameter() { |
| var fmt = new goog.i18n.MessageFormat('{GENDER, select,' + |
| 'other {result}}'); |
| |
| // Key name doesn't match A != GENDER. |
| assertEquals('Undefined parameter - GENDER', |
| fmt.format({A: 'female'})); |
| } |
| |
| function testMissingSimplePlaceholder() { |
| var fmt = new goog.i18n.MessageFormat('{result}'); |
| |
| // Key name doesn't match A != result. |
| assertEquals('Undefined parameter - result', |
| fmt.format({A: 'none'})); |
| } |
| |
| function testPluralWithIgnorePound() { |
| var fmt = new goog.i18n.MessageFormat('{SOME_NUM, plural,' + |
| 'other {# {GROUP}}}'); |
| |
| // Test pound sign. |
| assertEquals('# group#1', |
| fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': 'group#1'})); |
| // Test other special characters in parameters, like { and }. |
| assertEquals('# } {', |
| fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': '} {'})); |
| } |
| |
| function testSimplePluralWithIgnorePound() { |
| var fmt = new goog.i18n.MessageFormat( |
| 'I see {NUM_PEOPLE, plural, offset:1 ' + |
| '=0 {no one at all in {PLACE}.} ' + |
| '=1 {{PERSON} in {PLACE}.} ' + |
| 'one {{PERSON} and one other person in {PLACE}.} ' + |
| 'other {{PERSON} and # other people in {PLACE}.}}'); |
| |
| assertEquals('I see Cibu and # other people in the cubes.', |
| fmt.formatIgnoringPound({'NUM_PEOPLE': 100, |
| 'PERSON': 'Cibu', |
| 'PLACE': 'the cubes'})); |
| } |
| |
| function testSimpleOrdinal() { |
| var fmt = new goog.i18n.MessageFormat('{NUM_FLOOR, selectordinal, ' + |
| 'one {Take the elevator to the #st floor.}' + |
| 'two {Take the elevator to the #nd floor.}' + |
| 'few {Take the elevator to the #rd floor.}' + |
| 'other {Take the elevator to the #th floor.}}'); |
| |
| assertEquals('Take the elevator to the 1st floor.', |
| fmt.format({'NUM_FLOOR': 1})); |
| assertEquals('Take the elevator to the 2nd floor.', |
| fmt.format({'NUM_FLOOR': 2})); |
| assertEquals('Take the elevator to the 3rd floor.', |
| fmt.format({'NUM_FLOOR': 3})); |
| assertEquals('Take the elevator to the 4th floor.', |
| fmt.format({'NUM_FLOOR': 4})); |
| assertEquals('Take the elevator to the 23rd floor.', |
| fmt.format({'NUM_FLOOR': 23})); |
| // Esoteric example. |
| assertEquals('Take the elevator to the 0th floor.', |
| fmt.format({'NUM_FLOOR': 0})); |
| } |
| |
| function testOrdinalWithNegativeValue() { |
| var fmt = new goog.i18n.MessageFormat('{NUM_FLOOR, selectordinal, ' + |
| 'one {Take the elevator to the #st floor.}' + |
| 'two {Take the elevator to the #nd floor.}' + |
| 'few {Take the elevator to the #rd floor.}' + |
| 'other {Take the elevator to the #th floor.}}'); |
| |
| try { |
| fmt.format({'NUM_FLOOR': -2}); |
| } catch (e) { |
| assertEquals('Assertion failed: Argument index smaller than offset.', |
| e.message); |
| return; |
| } |
| fail('Expected an error to be thrown'); |
| } |
| |
| function testSimpleOrdinalWithIgnorePound() { |
| var fmt = new goog.i18n.MessageFormat('{NUM_FLOOR, selectordinal, ' + |
| 'one {Take the elevator to the #st floor.}' + |
| 'two {Take the elevator to the #nd floor.}' + |
| 'few {Take the elevator to the #rd floor.}' + |
| 'other {Take the elevator to the #th floor.}}'); |
| |
| assertEquals('Take the elevator to the #th floor.', |
| fmt.formatIgnoringPound({'NUM_FLOOR': 100})); |
| } |
| |
| function testMissingOrInvalidOrdinalParameter() { |
| var fmt = new goog.i18n.MessageFormat('{SOME_NUM, selectordinal,' + |
| 'other {result}}'); |
| |
| // Key name doesn't match A != SOME_NUM. |
| assertEquals('Undefined or invalid parameter - SOME_NUM', |
| fmt.format({A: '10'})); |
| |
| // Value is not a number. |
| assertEquals('Undefined or invalid parameter - SOME_NUM', |
| fmt.format({'SOME_NUM': 'Value'})); |
| } |