/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer'; import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser'; import {I18nHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser'; import {Message, id} from '@angular/compiler/src/i18n/message'; import {deserializeXmb} from '@angular/compiler/src/i18n/xmb_serializer'; import {ParseError} from '@angular/compiler/src/parse_util'; import {ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal'; import {StringMapWrapper} from '../../src/facade/collection'; import {HtmlAttrAst, HtmlElementAst, HtmlTextAst} from '../../src/html_parser/html_ast'; import {HtmlParseTreeResult, HtmlParser} from '../../src/html_parser/html_parser'; import {InterpolationConfig} from '../../src/html_parser/interpolation_config'; import {humanizeDom} from '../html_parser/html_ast_spec_utils'; export function main() { describe('I18nHtmlParser', () => { function parse( template: string, messages: {[key: string]: string}, implicitTags: string[] = [], implicitAttrs: {[k: string]: string[]} = {}, interpolation?: InterpolationConfig): HtmlParseTreeResult { let htmlParser = new HtmlParser(); let msgs = ''; StringMapWrapper.forEach( messages, (v: string, k: string) => msgs += `${v}`); let res = deserializeXmb(`${msgs}`, 'someUrl'); const expParser = new ExpressionParser(new ExpressionLexer()); return new I18nHtmlParser( htmlParser, expParser, res.content, res.messages, implicitTags, implicitAttrs) .parse(template, 'someurl', true, interpolation); } it('should delegate to the provided parser when no i18n', () => { expect(humanizeDom(parse('
a
', {}))).toEqual([ [HtmlElementAst, 'div', 0], [HtmlTextAst, 'a', 1] ]); }); describe('interpolation', () => { it('should handle interpolation', () => { let translations: {[key: string]: string} = {}; translations[id(new Message( ' and ', null, null))] = ' or '; expect(humanizeDom(parse('
', translations))) .toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{{b}} or {{a}}']]); }); it('should handle interpolation with config', () => { let translations: {[key: string]: string} = {}; translations[id(new Message( ' and ', null, null))] = ' or '; expect(humanizeDom(parse( '
', translations, [], {}, InterpolationConfig.fromArray(['{%', '%}'])))) .toEqual([ [HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{%b%} or {%a%}'], ]); }); it('should handle interpolation with custom placeholder names', () => { let translations: {[key: string]: string} = {}; translations[id(new Message(' and ', null, null))] = ' or '; expect( humanizeDom(parse( `
`, translations))) .toEqual([ [HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{{b //i18n(ph="SECOND")}} or {{a //i18n(ph="FIRST")}}'] ]); }); it('should handle interpolation with duplicate placeholder names', () => { let translations: {[key: string]: string} = {}; translations[id(new Message(' and ', null, null))] = ' or '; expect( humanizeDom(parse( `
`, translations))) .toEqual([ [HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{{b //i18n(ph="FIRST")}} or {{a //i18n(ph="FIRST")}}'] ]); }); it('should support interpolation', () => { let translations: {[key: string]: string} = {}; translations[id(new Message( 'ab', null, null))] = 'BA'; expect(humanizeDom(parse('
ab{{i}}
', translations))).toEqual([ [HtmlElementAst, 'div', 0], [HtmlElementAst, 'b', 1], [HtmlTextAst, '{{i}}B', 2], [HtmlElementAst, 'a', 1], [HtmlTextAst, 'A', 2], ]); }); }); describe('html', () => { it('should handle nested html', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('ab', null, null))] = 'BA'; expect(humanizeDom(parse('
ab
', translations))).toEqual([ [HtmlElementAst, 'div', 0], [HtmlElementAst, 'b', 1], [HtmlTextAst, 'B', 2], [HtmlElementAst, 'a', 1], [HtmlTextAst, 'A', 2], ]); }); it('should i18n attributes of placeholder elements', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('a', null, null))] = 'A'; translations[id(new Message('b', null, null))] = 'B'; expect(humanizeDom(parse('', translations))) .toEqual([ [HtmlElementAst, 'div', 0], [HtmlElementAst, 'a', 1], [HtmlAttrAst, 'value', 'B'], [HtmlTextAst, 'A', 2], ]); }); it('should preserve non-i18n attributes', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('message', null, null))] = 'another message'; expect(humanizeDom(parse('
message
', translations))).toEqual([ [HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', 'b'], [HtmlTextAst, 'another message', 1] ]); }); it('should replace attributes', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('some message', 'meaning', null))] = 'another message'; expect( humanizeDom(parse( '
', translations))) .toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', 'another message']]); }); it('should replace elements with the i18n attr', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('message', 'meaning', null))] = 'another message'; expect(humanizeDom(parse('
message
', translations))) .toEqual([[HtmlElementAst, 'div', 0], [HtmlTextAst, 'another message', 1]]); }); }); it('should extract from partitions', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('message1', 'meaning1', null))] = 'another message1'; translations[id(new Message('message2', 'meaning2', null))] = 'another message2'; let res = parse( `message1message2`, translations); expect(humanizeDom(res)).toEqual([ [HtmlTextAst, 'another message1', 0], [HtmlTextAst, 'another message2', 0], ]); }); it('should preserve original positions', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('ab', null, null))] = 'BA'; let res = (parse('
ab
', translations).rootNodes[0]).children; expect(res[0].sourceSpan.start.offset).toEqual(18); expect(res[1].sourceSpan.start.offset).toEqual(10); }); describe('errors', () => { it('should error when giving an invalid template', () => { expect(humanizeErrors(parse('a', {}).errors)).toEqual([ 'Unexpected closing tag "b"' ]); }); it('should error when no matching message (attr)', () => { let mid = id(new Message('some message', null, null)); expect(humanizeErrors(parse('
', {}).errors)) .toEqual([`Cannot find message for id '${mid}', content 'some message'.`]); }); it('should error when no matching message (text)', () => { let mid = id(new Message('some message', null, null)); expect(humanizeErrors(parse('
some message
', {}).errors)).toEqual([ `Cannot find message for id '${mid}', content 'some message'.` ]); }); it('should error when a non-placeholder element appears in translation', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('some message', null, null))] = '
a'; expect(humanizeErrors(parse('
some message
', translations).errors)).toEqual([ `Unexpected tag "a". Only "ph" tags are allowed.` ]); }); it('should error when a placeholder element does not have the name attribute', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('some message', null, null))] = 'a'; expect(humanizeErrors(parse('
some message
', translations).errors)).toEqual([ `Missing "name" attribute.` ]); }); it('should error when the translation refers to an invalid expression', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('hi ', null, null))] = 'hi '; expect( humanizeErrors(parse('
', translations).errors)) .toEqual(['Invalid interpolation name \'INTERPOLATION_99\'']); }); }); describe('implicit translation', () => { it('should support attributes', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('some message', null, null))] = 'another message'; expect(humanizeDom(parse('', translations, [], { 'i18n-el': ['value'] }))).toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]); }); it('should support attributes with meaning and description', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('some message', 'meaning', 'description'))] = 'another message'; expect(humanizeDom(parse( '', translations, [], {'i18n-el': ['value']}))) .toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]); }); it('should support elements', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('message', null, null))] = 'another message'; expect(humanizeDom(parse('message', translations, ['i18n-el']))) .toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]); }); it('should support elements with meaning and description', () => { let translations: {[key: string]: string} = {}; translations[id(new Message('message', 'meaning', 'description'))] = 'another message'; expect(humanizeDom(parse( 'message', translations, ['i18n-el']))) .toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]); }); }); }); } function humanizeErrors(errors: ParseError[]): string[] { return errors.map(error => error.msg); }