import {Xliff2MessageParser} from './xliff2-message-parser'; import {ParsedMessage} from './parsed-message'; import {INormalizedMessage} from '../api/index'; import {IMessageParser} from './i-message-parser'; import {XliffMessageParser} from './xliff-message-parser'; import {XmbMessageParser} from './xmb-message-parser'; /** * Created by martin on 17.05.2017. * Testcases for parsed messages. */ describe('normalized message test spec', () => { /** * Helperfunction to create a parsed message from normalized string. * @param normalizedString normalizedString * @param sourceMessage sourceMessage * @return new parsed message */ function parsedMessageFor(normalizedString: string, sourceMessage?: ParsedMessage): ParsedMessage { const parser = new Xliff2MessageParser(); // parser does not matter here, every format should be the same. return parser.parseNormalizedString(normalizedString, sourceMessage); } /** * Helperfunction to create an ICU Message. * @param icuMessageString icuMessageString * @param sourceMessage sourceMessage * @param parserType (optional) xlf or xlf2 or xmb, default xlf2 * @return new isu message */ function parsedICUMessage(icuMessageString: string, sourceMessage?: ParsedMessage, parserType?: string): INormalizedMessage { let parser: IMessageParser; if (sourceMessage) { parser = sourceMessage.getParser(); } else { if (parserType) { switch (parserType) { case 'xlf': parser = new XliffMessageParser(); break; case 'xlf': parser = new XmbMessageParser(); break; case 'xlf2': parser = new Xliff2MessageParser(); break; } } else { parser = new Xliff2MessageParser(); // parser does not matter here, every format should be the same. } } return parser.parseICUMessage(icuMessageString, sourceMessage); } describe('validation test cases ', () => { it('should find nothing wrong with simple text message', () => { const translation = 'a text without anything special'; const parsedMessage = parsedMessageFor(translation); expect(parsedMessage.validate()).toBeFalsy(); expect(parsedMessage.validateWarnings()).toBeFalsy(); }); it('should find nothing wrong with simple text as translation of simple text', () => { const original = 'any text'; const translation = 'a text without anything special'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); expect(translatedMessage.validateWarnings()).toBeFalsy(); }); it('should warn if you remove a placeholder in the translation', () => { const original = 'a text with placeholder: {{0}}'; const translation = 'a text without anything special'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); const warnings = translatedMessage.validateWarnings(); expect(warnings).toBeTruthy(); expect(warnings.placeholderRemoved).toBe('removed placeholder 0 from original message'); }); it('should warn if you remove 2 placeholders in the translation', () => { const original = 'a text with placeholders: {{0}} and {{1}}'; const translation = 'a text without anything special'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); const warnings = translatedMessage.validateWarnings(); expect(warnings).toBeTruthy(); expect(warnings.placeholderRemoved).toBe('removed placeholders 0, 1 from original message'); }); it('should report an error if you add a new placeholder in the translation', () => { const original = 'a text with placeholder: {{0}}'; const translation = 'a text with 2 placeholders: {{0}} and {{1}}'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validateWarnings()).toBeFalsy(); const errors = translatedMessage.validate(); expect(errors).toBeTruthy(); expect(errors.placeholderAdded).toBe('added placeholder 1, which is not in original message'); }); it('should report an error if you add 2 new placeholders in the translation', () => { const original = 'a text with placeholder: {{0}}'; const translation = 'a text with 3 placeholders: {{0}} and {{1}} and {{2}}'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validateWarnings()).toBeFalsy(); const errors = translatedMessage.validate(); expect(errors).toBeTruthy(); expect(errors.placeholderAdded).toBe('added placeholders 1, 2, which are not in original message'); }); it('should not report an error if you duplicate a placeholder in the translation', () => { const original = 'a text with placeholder: {{0}}'; const translation = 'a text with a duplicated placeholders: {{0}} and {{0}}'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); expect(translatedMessage.validateWarnings()).toBeFalsy(); }); it('should warn if you remove a tag in the translation', () => { const original = 'a bold text'; const translation = 'a non bold text'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); const warnings = translatedMessage.validateWarnings(); expect(warnings).toBeTruthy(); expect(warnings.tagRemoved).toBe('removed tag from original message'); }); it('should warn if you add a tag in the translation', () => { const original = 'a normal text'; const translation = 'a text'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); const warnings = translatedMessage.validateWarnings(); expect(warnings).toBeTruthy(); expect(warnings.tagAdded).toBe('added tag , which is not in original message'); }); it('should warn if you remove an empty tag in the translation', () => { const original = 'a text with
line break and '; const translation = 'a text'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); const warnings = translatedMessage.validateWarnings(); expect(warnings).toBeTruthy(); expect(warnings.tagRemoved).toBe('removed tags
, from original message'); }); it('should warn if you add an empty tag in the translation', () => { const original = 'a normal text'; const translation = 'a normal text with
line break'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); const warnings = translatedMessage.validateWarnings(); expect(warnings).toBeTruthy(); expect(warnings.tagAdded).toBe('added tag
, which is not in original message'); }); it('should warn if you add 2 empty tags in the translation', () => { const original = 'a normal text'; const translation = 'a normal text with
line break and '; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validate()).toBeFalsy(); const warnings = translatedMessage.validateWarnings(); expect(warnings).toBeTruthy(); expect(warnings.tagAdded).toBe('added tags
, , which are not in original message'); }); it('should find nothing wrong with text containing line breaks', () => { const original = 'a text without\na line break'; const translation = 'a text without\na line break'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.asDisplayString()).toBe(translation); expect(translatedMessage.validate()).toBeFalsy(); expect(translatedMessage.validateWarnings()).toBeFalsy(); }); it('should find nothing wrong in complex nested tags', () => { const original = 'a bold text and a bold text'; const translation = 'a bold text and a bold text'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.asDisplayString()).toBe(translation); expect(translatedMessage.validate()).toBeFalsy(); expect(translatedMessage.validateWarnings()).toBeFalsy(); }); it('should parse and detect ICU message refs', () => { const original = 'a text with '; const sourceMessage = parsedMessageFor(original); expect(sourceMessage.containsICUMessageRef()); expect(parsedMessageFor('no ICU message ref').containsICUMessageRef()).toBeFalsy(); }); it('should report an error if you remove an ICU ref in the translation', () => { const original = 'a text with '; const translation = 'a text without icu-ref'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validateWarnings()).toBeFalsy(); const errors = translatedMessage.validate(); expect(errors).toBeTruthy(); expect(errors.icuMessageRefRemoved).toBe('removed ICU message reference 0 from original message'); }); it('should report an error if you remove 2 ICU refs in the translation', () => { const original = 'a text with and '; const translation = 'a text without icu-ref'; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validateWarnings()).toBeFalsy(); const errors = translatedMessage.validate(); expect(errors).toBeTruthy(); expect(errors.icuMessageRefRemoved).toBe('removed ICU message references 0, 1 from original message'); }); it('should report an error if you add an ICU ref in the translation', () => { const original = 'a text with '; const translation = 'a text with and '; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validateWarnings()).toBeFalsy(); const errors = translatedMessage.validate(); expect(errors).toBeTruthy(); expect(errors.icuMessageRefAdded).toBe('added ICU message reference 1, which is not in original message'); }); it('should report an error if you add 2 ICU refs in the translation', () => { const original = 'a text with '; const translation = 'a text with and and '; const sourceMessage = parsedMessageFor(original); const translatedMessage = sourceMessage.translate(translation); expect(translatedMessage.validateWarnings()).toBeFalsy(); const errors = translatedMessage.validate(); expect(errors).toBeTruthy(); expect(errors.icuMessageRefAdded).toBe('added ICU message references 1, 2, which are not in original message'); }); it('should parse tags with - and [0-9]', () => { const original = '

info

'; const sourceMessage = parsedMessageFor(original); expect(sourceMessage.parts().length).toBe(5); }); }); describe('ICU test cases', () => { it('should parse ICU plural message', () => { const original = '{n, plural, =0 {kein Schaf} =1 {ein Schaf} other {Schafe}}'; const sourceICUMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); expect(sourceICUMessage.isICUMessage); expect(sourceICUMessage.getICUMessage()).toBeTruthy(); expect(sourceICUMessage.getICUMessage().isPluralMessage()).toBeTruthy(); expect(sourceICUMessage.getICUMessage().getCategories().length).toBe(3); expect(sourceICUMessage.getICUMessage().getCategories()[0].getCategory()).toBe('=0'); expect(sourceICUMessage.getICUMessage().getCategories()[0].getMessageNormalized().asDisplayString()).toBe('kein Schaf'); expect(sourceICUMessage.getICUMessage().asNativeString()) .toBe('{VAR_PLURAL, plural, =0 {kein Schaf} =1 {ein Schaf} other {Schafe}}'); }); it('should parse ICU select message', () => { const original = '{gender, select, m {männlich} f {weiblich}}'; const sourceICUMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); expect(sourceICUMessage.isICUMessage); expect(sourceICUMessage.getICUMessage()).toBeTruthy(); expect(sourceICUMessage.getICUMessage().isPluralMessage()).toBeFalsy(); expect(sourceICUMessage.getICUMessage().getCategories().length).toBe(2); expect(sourceICUMessage.getICUMessage().getCategories()[0].getCategory()).toBe('m'); expect(sourceICUMessage.getICUMessage().getCategories()[0].getMessageNormalized().asDisplayString()).toBe('männlich'); expect(sourceICUMessage.getICUMessage().asNativeString()).toBe('{VAR_SELECT, select, m {männlich} f {weiblich}}'); }); it('should parse ICU select message with select or plural in message text', () => { const original = '{VAR_SELECT, select, wert0 {value0 selected} wert1 {plural selected} wert2 {anything else selected} }'; const sourceICUMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); expect(sourceICUMessage.getICUMessage()).toBeTruthy(); expect(sourceICUMessage.getICUMessage().isPluralMessage()).toBeFalsy(); expect(sourceICUMessage.getICUMessage().getCategories().length).toBe(3); expect(sourceICUMessage.getICUMessage().getCategories()[0].getCategory()).toBe('wert0'); expect(sourceICUMessage.getICUMessage().getCategories()[0].getMessageNormalized().asDisplayString()).toBe('value0 selected'); }); it('should parse ICU select message with masked } {', () => { const original = '{VAR_SELECT, select, wert0 {value0 \'}\'\'\'\'{\'}}'; const sourceICUMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); expect(sourceICUMessage.getICUMessage()).toBeTruthy(); expect(sourceICUMessage.getICUMessage().isPluralMessage()).toBeFalsy(); expect(sourceICUMessage.getICUMessage().getCategories().length).toBe(1); expect(sourceICUMessage.getICUMessage().getCategories()[0].getCategory()).toBe('wert0'); expect(sourceICUMessage.getICUMessage().getCategories()[0].getMessageNormalized().asDisplayString()).toBe('value0 }\'{'); }); it('should translate ICU plural message', () => { const original = '{n, plural, =0 {kein Schaf} =1 {ein Schaf} other {Schafe}}'; const sourceICUMessage: INormalizedMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); const translatedICUMessage = sourceICUMessage.translateICUMessage({ '=0': 'no sheep', '=1': 'one sheep', 'other': 'sheep' }); expect(translatedICUMessage.asNativeString()).toBe('{VAR_PLURAL, plural, =0 {no sheep} =1 {one sheep} other {sheep}}'); }); it('should translate ICU plural message with new categories', () => { const original = '{n, plural, =0 {kein Schaf} =1 {ein Schaf} other {Schafe}}'; const sourceICUMessage: INormalizedMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); const translatedICUMessage = sourceICUMessage.translateICUMessage({ '=0': 'no sheep', 'many': 'a lot of sheep' }); expect(translatedICUMessage.asNativeString()) .toBe('{VAR_PLURAL, plural, =0 {no sheep} =1 {ein Schaf} other {Schafe} many {a lot of sheep}}'); }); it('should throw an error when translation of ICU plural message adds invalid category', () => { const original = '{n, plural, =0 {kein Schaf} =1 {ein Schaf} other {Schafe}}'; const sourceICUMessage: INormalizedMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); try { const translatedICUMessage = sourceICUMessage.translateICUMessage({ '=0': 'no sheep', 'verdammtviele': 'a lot of sheep' }); expect('').toBe('should have thrown an error "invalid category"'); } catch (error) { expect(error.toString()) .toBe('Error: invalid plural category "verdammtviele", allowed are = and zero,one,two,few,many,other'); } }); it('should translate ICU select message', () => { const original = '{gender, select, m {männlich} f {weiblich}}'; const sourceICUMessage: INormalizedMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); const translatedICUMessage = sourceICUMessage.translateICUMessage({ 'm': 'male', 'f': 'female' }); expect(translatedICUMessage.asNativeString()).toBe('{VAR_SELECT, select, m {male} f {female}}'); }); it('should partially translate ICU select message', () => { const original = '{gender, select, m {männlich} f {weiblich}}'; const sourceICUMessage: INormalizedMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); // only translate one part of message const translatedICUMessage = sourceICUMessage.translateICUMessage({ 'f': 'female' }); expect(translatedICUMessage.asNativeString()).toBe('{VAR_SELECT, select, m {männlich} f {female}}'); }); it('should throw an error if translation of ICU select message contains additional categories', () => { const original = '{gender, select, m {männlich} f {weiblich}}'; const sourceICUMessage: INormalizedMessage = parsedICUMessage(original); expect(sourceICUMessage).toBeTruthy(); try { // a category not part of the message const translatedICUMessage = sourceICUMessage.translateICUMessage({ 'u': 'unknown' }); expect('').toBe('should have thrown an error "unknown category"'); } catch (error) { expect(error.toString()).toBe('Error: adding a new category not allowed for select messages ("u" is not part of message)'); } }); it('should parse plural ICU message with placeholder in xlf format', () => { const original = '{minutes, plural, =0 {just now} =1 {one minute ago} other { minutes ago} }'; const parsedMessage: INormalizedMessage = parsedICUMessage(original, null, 'xlf'); expect(parsedMessage.asDisplayString()).toBe(''); expect(parsedMessage.getICUMessage()).toBeTruthy(); const icuMessage = parsedMessage.getICUMessage(); expect(icuMessage.getCategories().length).toBe(3); expect(icuMessage.getCategories()[2].getMessageNormalized().asDisplayString()).toBe('{{0}} minutes ago'); }); it('should parse plural ICU message with placeholder in xlf2 format', () => { const original = '{minutes, plural, =0 {just now} =1 {one minute ago} other { minutes ago} }'; const parsedMessage: INormalizedMessage = parsedICUMessage(original); expect(parsedMessage.asDisplayString()).toBe(''); expect(parsedMessage.getICUMessage()).toBeTruthy(); const icuMessage = parsedMessage.getICUMessage(); expect(icuMessage.getCategories().length).toBe(3); expect(icuMessage.getCategories()[2].getMessageNormalized().asDisplayString()).toBe('{{0}} minutes ago'); }); it('should parse nested ICU messages', () => { const original = `{gender_of_host, select, female { {num_guests, plural, =0 {{host} does not give a party.} =1 {{host} invites {guest} to her party.} =2 {{host} invites {guest} and one other person to her party.} other {{host} invites {guest} and # other people to her party.}}} male { {num_guests, plural, =0 {{host} does not give a party.} =1 {{host} invites {guest} to his party.} =2 {{host} invites {guest} and one other person to his party.} other {{host} invites {guest} and # other people to his party.}}} other { {num_guests, plural, =0 {{host} does not give a party.} =1 {{host} invites {guest} to their party.} =2 {{host} invites {guest} and one other person to their party.} other {{host} invites {guest} and # other people to their party.}}}}`; const parsedMessage: INormalizedMessage = parsedICUMessage(original, null, 'xlf'); expect(parsedMessage.asDisplayString()).toBe(''); expect(parsedMessage.getICUMessage()).toBeTruthy(); const outerIcuMessage = parsedMessage.getICUMessage(); expect(outerIcuMessage.getCategories().length).toBe(3); expect(outerIcuMessage.getCategories()[0].getCategory()).toBe('female'); expect(outerIcuMessage.getCategories()[1].getCategory()).toBe('male'); expect(outerIcuMessage.getCategories()[2].getCategory()).toBe('other'); const innerMessage = outerIcuMessage.getCategories()[1].getMessageNormalized(); expect(innerMessage.asDisplayString()).toBe(''); const innerIcuMessage = innerMessage.getICUMessage(); expect(innerIcuMessage).toBeTruthy(); expect(innerIcuMessage.getCategories().length).toBe(4); expect(innerIcuMessage.getCategories()[0].getCategory()).toBe('=0'); expect(innerIcuMessage.getCategories()[1].getCategory()).toBe('=1'); expect(innerIcuMessage.getCategories()[2].getCategory()).toBe('=2'); expect(innerIcuMessage.getCategories()[3].getCategory()).toBe('other'); }); it('should parse nested ICU messages with placeholder', () => { const original = `{gender_of_host, select, female { {num_guests, plural, =0 {{host} does not give a party.} =1 {{host} invites {guest} to her party.} =2 {{host} invites {guest} and one other person to her party.} other {{host} invites {guest} and # other people to her party.}}} male { {num_guests, plural, =0 { does not give a party.} =1 { invites {guest} to his party.} =2 { invites {guest} and one other person to his party.} other { invites {guest} and # other people to his party.}}} other { {num_guests, plural, =0 {{host} does not give a party.} =1 {{host} invites {guest} to their party.} =2 {{host} invites {guest} and one other person to their party.} other {{host} invites {guest} and # other people to their party.}}}}`; const parsedMessage: INormalizedMessage = parsedICUMessage(original, null, 'xlf'); expect(parsedMessage.asDisplayString()).toBe(''); expect(parsedMessage.getICUMessage()).toBeTruthy(); const outerIcuMessage = parsedMessage.getICUMessage(); expect(outerIcuMessage.getCategories().length).toBe(3); expect(outerIcuMessage.getCategories()[0].getCategory()).toBe('female'); expect(outerIcuMessage.getCategories()[1].getCategory()).toBe('male'); expect(outerIcuMessage.getCategories()[2].getCategory()).toBe('other'); const innerMessage = outerIcuMessage.getCategories()[1].getMessageNormalized(); expect(innerMessage.asDisplayString()).toBe(''); const innerIcuMessage = innerMessage.getICUMessage(); expect(innerIcuMessage).toBeTruthy(); expect(innerIcuMessage.getCategories().length).toBe(4); expect(innerIcuMessage.getCategories()[0].getCategory()).toBe('=0'); expect(innerIcuMessage.getCategories()[0].getMessageNormalized().asDisplayString()).toBe('{{0}} does not give a party.'); }); }); });