2018-06-18 10:55:43 -04:00
/ * *
* @license
2020-05-19 15:08:49 -04:00
* Copyright Google LLC All Rights Reserved .
2018-06-18 10:55:43 -04:00
*
* 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
* /
2020-08-06 17:01:59 -04:00
import { ɵ ɵ i18nAttributes , ɵ ɵ i18nPostprocess , ɵ ɵ i18nStart } from '@angular/core' ;
import { getTranslationForTemplate } from '@angular/core/src/render3/i18n/i18n_parse' ;
2018-11-13 03:36:30 -05:00
import { noop } from '../../../compiler/src/render3/view/util' ;
2019-05-31 11:11:57 -04:00
import { setDelayProjection , ɵ ɵ elementEnd , ɵ ɵ elementStart } from '../../src/render3/instructions/all' ;
2020-07-21 01:06:09 -04:00
import { I18nUpdateOpCodes , TI18n , TIcu } from '../../src/render3/interfaces/i18n' ;
2019-04-04 14:41:52 -04:00
import { HEADER_OFFSET , LView , TVIEW } from '../../src/render3/interfaces/view' ;
2019-05-06 09:33:34 -04:00
import { getNativeByIndex } from '../../src/render3/util/view_utils' ;
2020-08-06 17:01:59 -04:00
2019-05-06 09:33:34 -04:00
import { TemplateFixture } from './render_util' ;
2020-07-21 01:06:09 -04:00
import { debugMatch } from './utils' ;
2018-10-30 20:03:01 -04:00
2018-06-18 10:55:43 -04:00
describe ( 'Runtime i18n' , ( ) = > {
2020-04-13 19:40:21 -04:00
afterEach ( ( ) = > {
setDelayProjection ( false ) ;
} ) ;
2018-11-13 03:36:30 -05:00
describe ( 'getTranslationForTemplate' , ( ) = > {
it ( 'should crop messages for the selected template' , ( ) = > {
let message = ` simple text ` ;
expect ( getTranslationForTemplate ( message ) ) . toEqual ( message ) ;
message = ` Hello <20> 0<EFBFBD> ! ` ;
expect ( getTranslationForTemplate ( message ) ) . toEqual ( message ) ;
message = ` Hello <20> #2<> <32> 0<EFBFBD> <30> /#2<> ! ` ;
expect ( getTranslationForTemplate ( message ) ) . toEqual ( message ) ;
// Embedded sub-templates
message = ` <EFBFBD> 0<EFBFBD> is rendered as: <20> *2:1<> before<72> *1:2<> middle<6C> /*1:2<> after<65> /*2:1<> !` ;
expect ( getTranslationForTemplate ( message ) ) . toEqual ( '<27> 0<EFBFBD> is rendered as: <20> *2:1<> <31> /*2:1<> !' ) ;
expect ( getTranslationForTemplate ( message , 1 ) ) . toEqual ( 'before<72> *1:2<> <32> /*1:2<> after' ) ;
expect ( getTranslationForTemplate ( message , 2 ) ) . toEqual ( 'middle' ) ;
// Embedded & sibling sub-templates
message =
` <EFBFBD> 0<EFBFBD> is rendered as: <20> *2:1<> before<72> *1:2<> middle<6C> /*1:2<> after<65> /*2:1<> and also <20> *4:3<> before<72> *1:4<> middle<6C> /*1:4<> after<65> /*4:3<> !` ;
expect ( getTranslationForTemplate ( message ) )
. toEqual ( '<27> 0<EFBFBD> is rendered as: <20> *2:1<> <31> /*2:1<> and also <20> *4:3<> <33> /*4:3<> !' ) ;
expect ( getTranslationForTemplate ( message , 1 ) ) . toEqual ( 'before<72> *1:2<> <32> /*1:2<> after' ) ;
expect ( getTranslationForTemplate ( message , 2 ) ) . toEqual ( 'middle' ) ;
expect ( getTranslationForTemplate ( message , 3 ) ) . toEqual ( 'before<72> *1:4<> <34> /*1:4<> after' ) ;
expect ( getTranslationForTemplate ( message , 4 ) ) . toEqual ( 'middle' ) ;
} ) ;
2018-07-09 14:59:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'should throw if the template is malformed' , ( ) = > {
const message = ` <EFBFBD> *2:1<> message!` ;
expect ( ( ) = > getTranslationForTemplate ( message ) ) . toThrowError ( /Tag mismatch/ ) ;
} ) ;
2018-06-18 10:55:43 -04:00
} ) ;
2018-11-13 03:36:30 -05:00
function prepareFixture (
createTemplate : ( ) = > void , updateTemplate : ( ( ) = > void ) | null , nbConsts = 0 ,
nbVars = 0 ) : TemplateFixture {
return new TemplateFixture ( createTemplate , updateTemplate || noop , nbConsts , nbVars ) ;
}
function getOpCodes (
createTemplate : ( ) = > void , updateTemplate : ( ( ) = > void ) | null , nbConsts : number ,
index : number ) : TI18n | I18nUpdateOpCodes {
const fixture = prepareFixture ( createTemplate , updateTemplate , nbConsts ) ;
const tView = fixture . hostView [ TVIEW ] ;
return tView . data [ index + HEADER_OFFSET ] as TI18n ;
}
describe ( 'i18nStart' , ( ) = > {
it ( 'for text' , ( ) = > {
const MSG_DIV = ` simple text ` ;
const nbConsts = 1 ;
const index = 0 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
2020-07-21 01:06:09 -04:00
ɵ ɵ i18nStart ( index , MSG_DIV ) ;
} , null , nbConsts , index ) as TI18n ;
2019-03-15 18:04:34 -04:00
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
vars : 1 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'lView[1] = document.createTextNode("simple text")' ,
'(lView[0] as Element).appendChild(lView[1])'
] ) ,
2018-11-13 03:36:30 -05:00
update : [ ] ,
icus : null
2018-11-13 03:36:30 -05:00
} ) ;
2018-11-13 03:36:30 -05:00
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for elements' , ( ) = > {
const MSG_DIV = ` Hello <20> #2<> world<6C> /#2<> and <20> #3<> universe<73> /#3<> ! ` ;
// Template: `<div>Hello <div>world</div> and <span>universe</span>!`
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
const nbConsts = 4 ;
const index = 1 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nStart ( index , MSG_DIV ) ;
} , null , nbConsts , index ) ;
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
vars : 5 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'lView[4] = document.createTextNode("Hello ")' ,
'(lView[1] as Element).appendChild(lView[4])' ,
'(lView[1] as Element).appendChild(lView[2])' ,
'lView[5] = document.createTextNode("world")' ,
'(lView[2] as Element).appendChild(lView[5])' ,
'setPreviousOrParentTNode(tView.data[2] as TNode)' ,
'lView[6] = document.createTextNode(" and ")' ,
'(lView[1] as Element).appendChild(lView[6])' ,
'(lView[1] as Element).appendChild(lView[3])' ,
'lView[7] = document.createTextNode("universe")' ,
'(lView[3] as Element).appendChild(lView[7])' ,
'setPreviousOrParentTNode(tView.data[3] as TNode)' ,
'lView[8] = document.createTextNode("!")' ,
'(lView[1] as Element).appendChild(lView[8])' ,
] ) ,
2018-11-13 03:36:30 -05:00
update : [ ] ,
icus : null
} ) ;
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for simple bindings' , ( ) = > {
const MSG_DIV = ` Hello <20> 0<EFBFBD> ! ` ;
const nbConsts = 2 ;
const index = 1 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nStart ( index , MSG_DIV ) ;
} , null , nbConsts , index ) ;
2018-11-14 13:23:21 -05:00
2020-07-21 01:06:09 -04:00
expect ( ( opCodes as any ) . update . debug ) . toEqual ( [
'if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }'
2019-03-15 18:04:34 -04:00
] ) ;
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
vars : 1 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'lView[2] = document.createTextNode("")' ,
'(lView[1] as Element).appendChild(lView[2])' ,
] ) ,
update : debugMatch (
[ 'if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }' ] ) ,
2018-11-13 03:36:30 -05:00
icus : null
} ) ;
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for multiple bindings' , ( ) = > {
const MSG_DIV = ` Hello <20> 0<EFBFBD> and <20> 1<EFBFBD> , again <20> 0<EFBFBD> ! ` ;
const nbConsts = 2 ;
const index = 1 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nStart ( index , MSG_DIV ) ;
} , null , nbConsts , index ) ;
2018-11-14 13:23:21 -05:00
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
2018-08-18 14:14:50 -04:00
vars : 1 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'lView[2] = document.createTextNode("")' , '(lView[1] as Element).appendChild(lView[2])'
] ) ,
update : debugMatch ( [
'if (mask & 0b11) { (lView[2] as Text).textContent = `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`; }'
] ) ,
2018-11-13 03:36:30 -05:00
icus : null
2018-06-18 10:55:43 -04:00
} ) ;
2018-11-13 03:36:30 -05:00
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for sub-templates' , ( ) = > {
// Template:
// <div>
// {{value}} is rendered as:
// <span *ngIf>
// before <b *ngIf>middle</b> after
// </span>
// !
// </div>
const MSG_DIV =
` <EFBFBD> 0<EFBFBD> is rendered as: <20> *2:1<> <31> #1:1<> before<72> *2:2<> <32> #1:2<> middle<6C> /#1:2<> <32> /*2:2<> after<65> /#1:1<> <31> /*2:1<> !` ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
/**** Root template ****/
// <20> 0<EFBFBD> is rendered as: <20> *2:1<> <31> /*2:1<> !
let nbConsts = 3 ;
let index = 1 ;
const firstTextNode = 3 ;
2020-02-11 17:44:13 -05:00
const rootTemplate = 2 ;
2020-04-13 19:40:21 -04:00
let opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nStart ( index , MSG_DIV ) ;
} , null , nbConsts , index ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
vars : 2 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'lView[3] = document.createTextNode("")' , '(lView[1] as Element).appendChild(lView[3])' ,
'(lView[1] as Element).appendChild(lView[16381])' ,
'lView[4] = document.createTextNode("!")' , '(lView[1] as Element).appendChild(lView[4])'
] ) ,
update : debugMatch ( [
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} is rendered as: `; }'
] ) ,
2018-11-13 03:36:30 -05:00
icus : null
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
/**** First sub-template ****/
// <20> #1:1<> before<72> *2:2<> middle<6C> /*2:2<> after<65> /#1:1<>
nbConsts = 3 ;
index = 0 ;
const spanElement = 1 ;
const bElementSubTemplate = 2 ;
2020-04-13 19:40:21 -04:00
opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nStart ( index , MSG_DIV , 1 ) ;
} , null , nbConsts , index ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
2018-08-21 03:03:21 -04:00
vars : 2 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'(lView[0] as Element).appendChild(lView[1])' ,
'lView[3] = document.createTextNode("before")' ,
'(lView[1] as Element).appendChild(lView[3])' ,
'(lView[1] as Element).appendChild(lView[16381])' ,
'lView[4] = document.createTextNode("after")' ,
'(lView[1] as Element).appendChild(lView[4])' ,
'setPreviousOrParentTNode(tView.data[1] as TNode)'
] ) ,
2018-11-13 03:36:30 -05:00
update : [ ] ,
icus : null
2018-06-18 10:55:43 -04:00
} ) ;
2018-11-13 03:36:30 -05:00
/**** Second sub-template ****/
// middle
nbConsts = 2 ;
index = 0 ;
const bElement = 1 ;
2020-04-13 19:40:21 -04:00
opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nStart ( index , MSG_DIV , 2 ) ;
} , null , nbConsts , index ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
vars : 1 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'(lView[0] as Element).appendChild(lView[1])' ,
'lView[2] = document.createTextNode("middle")' ,
'(lView[1] as Element).appendChild(lView[2])' ,
'setPreviousOrParentTNode(tView.data[1] as TNode)'
] ) ,
2018-11-13 03:36:30 -05:00
update : [ ] ,
icus : null
} ) ;
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for ICU expressions' , ( ) = > {
2019-02-20 17:21:20 -05:00
const MSG_DIV = ` {<7B> 0<EFBFBD> , plural,
= 0 { no < b title = "none" > emails < / b > ! }
= 1 { one < i > email < / i > }
2018-11-13 03:36:30 -05:00
other { <EFBFBD> 0 <EFBFBD> < span title = "<22> 1<EFBFBD> " > emails < / span > }
} ` ;
const nbConsts = 1 ;
const index = 0 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
2020-07-21 01:06:09 -04:00
ɵ ɵ i18nStart ( index , MSG_DIV ) ;
} , null , nbConsts , index ) as TI18n ;
2019-03-15 18:04:34 -04:00
2018-11-13 03:36:30 -05:00
expect ( opCodes ) . toEqual ( {
2020-08-04 15:42:12 -04:00
vars : 6 ,
2020-07-21 01:06:09 -04:00
update : debugMatch ( [
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 0, `${lView[1]}`); }' ,
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 0); }' ,
] ) ,
create : debugMatch ( [
'lView[1] = document.createComment("ICU 1")' ,
'(lView[0] as Element).appendChild(lView[1])' ,
] ) ,
icus : [ < TIcu > {
2018-11-13 03:36:30 -05:00
type : 1 ,
2020-08-04 15:42:12 -04:00
currentCaseLViewIndex : 22 ,
vars : [ 5 , 4 , 4 ] ,
2018-11-13 03:36:30 -05:00
childIcus : [ [ ] , [ ] , [ ] ] ,
cases : [ '0' , '1' , 'other' ] ,
create : [
2020-07-21 01:06:09 -04:00
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[3] = document.createTextNode("no ")' ,
2020-07-21 01:06:09 -04:00
'(lView[1] as Element).appendChild(lView[3])' ,
2020-08-04 15:42:12 -04:00
'lView[4] = document.createElement("b")' ,
'(lView[1] as Element).appendChild(lView[4])' ,
'(lView[4] as Element).setAttribute("title", "none")' ,
'lView[5] = document.createTextNode("emails")' ,
'(lView[4] as Element).appendChild(lView[5])' ,
'lView[6] = document.createTextNode("!")' ,
'(lView[1] as Element).appendChild(lView[6])' ,
2020-07-21 01:06:09 -04:00
] ) ,
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[3] = document.createTextNode("one ")' ,
2020-07-21 01:06:09 -04:00
'(lView[1] as Element).appendChild(lView[3])' ,
2020-08-04 15:42:12 -04:00
'lView[4] = document.createElement("i")' ,
'(lView[1] as Element).appendChild(lView[4])' ,
'lView[5] = document.createTextNode("email")' ,
'(lView[4] as Element).appendChild(lView[5])' ,
2020-07-21 01:06:09 -04:00
] ) ,
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[3] = document.createTextNode("")' ,
2020-07-21 01:06:09 -04:00
'(lView[1] as Element).appendChild(lView[3])' ,
2020-08-04 15:42:12 -04:00
'lView[4] = document.createElement("span")' ,
'(lView[1] as Element).appendChild(lView[4])' ,
'lView[5] = document.createTextNode("emails")' ,
'(lView[4] as Element).appendChild(lView[5])' ,
2020-07-21 01:06:09 -04:00
] )
2018-11-13 03:36:30 -05:00
] ,
remove : [
2020-07-21 01:06:09 -04:00
debugMatch ( [
'(lView[0] as Element).remove(lView[3])' ,
'(lView[0] as Element).remove(lView[5])' ,
2020-08-04 15:42:12 -04:00
'(lView[0] as Element).remove(lView[4])' ,
'(lView[0] as Element).remove(lView[6])' ,
2020-07-21 01:06:09 -04:00
] ) ,
debugMatch ( [
'(lView[0] as Element).remove(lView[3])' ,
2020-08-04 15:42:12 -04:00
'(lView[0] as Element).remove(lView[5])' ,
'(lView[0] as Element).remove(lView[4])' ,
2020-07-21 01:06:09 -04:00
] ) ,
debugMatch ( [
'(lView[0] as Element).remove(lView[3])' ,
2020-08-04 15:42:12 -04:00
'(lView[0] as Element).remove(lView[5])' ,
'(lView[0] as Element).remove(lView[4])' ,
2020-07-21 01:06:09 -04:00
] )
2018-11-13 03:36:30 -05:00
] ,
update : [
2020-07-21 01:06:09 -04:00
debugMatch ( [ ] ) , debugMatch ( [ ] ) , debugMatch ( [
2020-08-04 15:42:12 -04:00
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} `; }' ,
'if (mask & 0b10) { (lView[4] as Element).setAttribute(\'title\', `${lView[2]}`); }'
2020-07-21 01:06:09 -04:00
] )
2018-11-13 03:36:30 -05:00
]
} ]
} ) ;
} ) ;
2018-07-09 14:59:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for nested ICU expressions' , ( ) = > {
const MSG_DIV = ` {<7B> 0<EFBFBD> , plural,
2019-02-20 17:21:20 -05:00
= 0 { zero }
other { <EFBFBD> 0 <EFBFBD> { <EFBFBD> 1 <EFBFBD> , select ,
cat { cats }
dog { dogs }
2018-11-13 03:36:30 -05:00
other { animals }
} ! }
} ` ;
const nbConsts = 1 ;
const index = 0 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nStart ( index , MSG_DIV ) ;
} , null , nbConsts , index ) ;
2018-11-13 03:36:30 -05:00
const icuCommentNodeIndex = index + 1 ;
2019-01-13 05:10:04 -05:00
const firstTextNodeIndex = index + 2 ;
2018-11-13 03:36:30 -05:00
const nestedIcuCommentNodeIndex = index + 3 ;
2019-01-13 05:10:04 -05:00
const lastTextNodeIndex = index + 4 ;
const nestedTextNodeIndex = index + 5 ;
2018-11-13 03:36:30 -05:00
const tIcuIndex = 1 ;
const nestedTIcuIndex = 0 ;
expect ( opCodes ) . toEqual ( {
2020-08-04 15:42:12 -04:00
vars : 9 ,
2020-07-21 01:06:09 -04:00
create : debugMatch ( [
'lView[1] = document.createComment("ICU 1")' ,
'(lView[0] as Element).appendChild(lView[1])'
] ) ,
update : debugMatch ( [
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 1, `${lView[1]}`); }' ,
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 1); }'
] ) ,
2018-11-13 03:36:30 -05:00
icus : [
{
type : 0 ,
2020-08-04 15:42:12 -04:00
vars : [ 2 , 2 , 2 ] ,
currentCaseLViewIndex : 26 ,
2018-11-13 03:36:30 -05:00
childIcus : [ [ ] , [ ] , [ ] ] ,
cases : [ 'cat' , 'dog' , 'other' ] ,
create : [
2020-07-21 01:06:09 -04:00
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[7] = document.createTextNode("cats")' ,
'(lView[4] as Element).appendChild(lView[7])'
2020-07-21 01:06:09 -04:00
] ) ,
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[7] = document.createTextNode("dogs")' ,
'(lView[4] as Element).appendChild(lView[7])'
2020-07-21 01:06:09 -04:00
] ) ,
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[7] = document.createTextNode("animals")' ,
'(lView[4] as Element).appendChild(lView[7])'
2020-07-21 01:06:09 -04:00
] ) ,
2018-11-13 03:36:30 -05:00
] ,
remove : [
2020-08-04 15:42:12 -04:00
debugMatch ( [ '(lView[0] as Element).remove(lView[7])' ] ) ,
debugMatch ( [ '(lView[0] as Element).remove(lView[7])' ] ) ,
debugMatch ( [ '(lView[0] as Element).remove(lView[7])' ] )
2018-11-13 03:36:30 -05:00
] ,
2020-07-21 01:06:09 -04:00
update : [
debugMatch ( [ ] ) ,
debugMatch ( [ ] ) ,
debugMatch ( [ ] ) ,
]
2018-11-13 03:36:30 -05:00
} ,
{
type : 1 ,
2020-08-04 15:42:12 -04:00
vars : [ 2 , 6 ] ,
2018-11-13 03:36:30 -05:00
childIcus : [ [ ] , [ 0 ] ] ,
2020-08-04 15:42:12 -04:00
currentCaseLViewIndex : 22 ,
2018-11-13 03:36:30 -05:00
cases : [ '0' , 'other' ] ,
create : [
2020-07-21 01:06:09 -04:00
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[3] = document.createTextNode("zero")' ,
'(lView[1] as Element).appendChild(lView[3])'
2020-07-21 01:06:09 -04:00
] ) ,
debugMatch ( [
2020-08-04 15:42:12 -04:00
'lView[3] = document.createTextNode("")' ,
2020-07-21 01:06:09 -04:00
'(lView[1] as Element).appendChild(lView[3])' ,
2020-08-04 15:42:12 -04:00
'lView[4] = document.createComment("nested ICU 0")' ,
'(lView[1] as Element).appendChild(lView[4])' ,
'lView[5] = document.createTextNode("!")' ,
'(lView[1] as Element).appendChild(lView[5])'
2020-07-21 01:06:09 -04:00
] ) ,
2018-11-13 03:36:30 -05:00
] ,
remove : [
2020-08-04 15:42:12 -04:00
debugMatch ( [ '(lView[0] as Element).remove(lView[3])' ] ) ,
2020-07-21 01:06:09 -04:00
debugMatch ( [
2020-08-04 15:42:12 -04:00
'(lView[0] as Element).remove(lView[3])' , '(lView[0] as Element).remove(lView[5])' ,
'removeNestedICU(0)' , '(lView[0] as Element).remove(lView[4])'
2020-07-21 01:06:09 -04:00
] ) ,
2018-11-13 03:36:30 -05:00
] ,
update : [
2020-07-21 01:06:09 -04:00
debugMatch ( [ ] ) ,
debugMatch ( [
2020-08-04 15:42:12 -04:00
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} `; }' ,
'if (mask & 0b10) { icuSwitchCase(lView[4] as Comment, 0, `${lView[2]}`); }' ,
'if (mask & 0b10) { icuUpdateCase(lView[4] as Comment, 0); }'
2020-07-21 01:06:09 -04:00
] ) ,
2018-11-13 03:36:30 -05:00
]
2018-06-18 10:55:43 -04:00
}
2018-11-13 03:36:30 -05:00
]
2018-06-18 10:55:43 -04:00
} ) ;
2018-11-13 03:36:30 -05:00
} ) ;
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
describe ( ` i18nAttribute ` , ( ) = > {
it ( 'for text' , ( ) = > {
const MSG_title = ` Hello world! ` ;
const MSG_div_attr = [ 'title' , MSG_title ] ;
const nbConsts = 2 ;
const index = 1 ;
const fixture = prepareFixture ( ( ) = > {
2019-05-17 21:49:21 -04:00
ɵ ɵ elementStart ( 0 , 'div' ) ;
ɵ ɵ i18nAttributes ( index , MSG_div_attr ) ;
ɵ ɵ elementEnd ( ) ;
2018-11-13 03:36:30 -05:00
} , null , nbConsts , index ) ;
const tView = fixture . hostView [ TVIEW ] ;
const opCodes = tView . data [ index + HEADER_OFFSET ] as I18nUpdateOpCodes ;
expect ( opCodes ) . toEqual ( [ ] ) ;
2018-11-22 00:14:06 -05:00
expect (
( getNativeByIndex ( 0 , fixture . hostView as LView ) as any as Element ) . getAttribute ( 'title' ) )
2018-11-13 03:36:30 -05:00
. toEqual ( MSG_title ) ;
} ) ;
2018-08-16 21:53:21 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for simple bindings' , ( ) = > {
const MSG_title = ` Hello <20> 0<EFBFBD> ! ` ;
const MSG_div_attr = [ 'title' , MSG_title ] ;
const nbConsts = 2 ;
const index = 1 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nAttributes ( index , MSG_div_attr ) ;
} , null , nbConsts , index ) ;
2018-11-13 03:36:30 -05:00
2020-07-21 01:06:09 -04:00
expect ( opCodes ) . toEqual ( debugMatch ( [
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }'
] ) ) ;
2018-11-13 03:36:30 -05:00
} ) ;
2018-08-16 21:53:21 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for multiple bindings' , ( ) = > {
const MSG_title = ` Hello <20> 0<EFBFBD> and <20> 1<EFBFBD> , again <20> 0<EFBFBD> ! ` ;
const MSG_div_attr = [ 'title' , MSG_title ] ;
const nbConsts = 2 ;
const index = 1 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nAttributes ( index , MSG_div_attr ) ;
} , null , nbConsts , index ) ;
2018-11-13 03:36:30 -05:00
2020-07-21 01:06:09 -04:00
expect ( opCodes ) . toEqual ( debugMatch ( [
'if (mask & 0b11) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`); }'
] ) ) ;
2018-11-13 03:36:30 -05:00
} ) ;
2018-06-18 10:55:43 -04:00
2018-11-13 03:36:30 -05:00
it ( 'for multiple attributes' , ( ) = > {
const MSG_title = ` Hello <20> 0<EFBFBD> ! ` ;
const MSG_div_attr = [ 'title' , MSG_title , 'aria-label' , MSG_title ] ;
const nbConsts = 2 ;
const index = 1 ;
2020-04-13 19:40:21 -04:00
const opCodes = getOpCodes ( ( ) = > {
ɵ ɵ i18nAttributes ( index , MSG_div_attr ) ;
} , null , nbConsts , index ) ;
2018-11-13 03:36:30 -05:00
2020-07-21 01:06:09 -04:00
expect ( opCodes ) . toEqual ( debugMatch ( [
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }' ,
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'aria-label\', `Hello ${lView[1]}!`); }'
] ) ) ;
2018-11-13 03:36:30 -05:00
} ) ;
} ) ;
2018-06-18 10:55:43 -04:00
2018-10-18 13:08:51 -04:00
describe ( 'i18nPostprocess' , ( ) = > {
it ( 'should handle valid cases' , ( ) = > {
2019-01-13 19:56:00 -05:00
const arr = [ '<27> *1:1<> <31> #2:1<> ' , '<27> #4:1<> ' , '<27> 6:1<> ' , '<27> /#2:1<> <31> /*1:1<> ' ] ;
2018-10-18 13:08:51 -04:00
const str = ` [ ${ arr . join ( '|' ) } ] ` ;
const cases = [
// empty string
[ '' , { } , '' ] ,
// string without any special cases
[ 'Foo [1,2,3] Bar - no ICU here' , { } , 'Foo [1,2,3] Bar - no ICU here' ] ,
// multi-value cases
[
` Start: ${ str } , ${ str } and ${ str } , ${ str } end. ` , { } ,
` Start: ${ arr [ 0 ] } , ${ arr [ 1 ] } and ${ arr [ 2 ] } , ${ arr [ 3 ] } end. `
] ,
// replace VAR_SELECT
[
'My ICU: {VAR_SELECT, select, =1 {one} other {other}}' , { VAR_SELECT : '<27> 1:2<> ' } ,
'My ICU: {<7B> 1:2<> , select, =1 {one} other {other}}'
] ,
[
'My ICU: {\n\n\tVAR_SELECT_1 \n\n, select, =1 {one} other {other}}' ,
{ VAR_SELECT_1 : '<27> 1:2<> ' } , 'My ICU: {\n\n\t<> 1:2<> \n\n, select, =1 {one} other {other}}'
] ,
// replace VAR_PLURAL
[
'My ICU: {VAR_PLURAL, plural, one {1} other {other}}' , { VAR_PLURAL : '<27> 1:2<> ' } ,
'My ICU: {<7B> 1:2<> , plural, one {1} other {other}}'
] ,
[
'My ICU: {\n\n\tVAR_PLURAL_1 \n\n, select, =1 {one} other {other}}' ,
{ VAR_PLURAL_1 : '<27> 1:2<> ' } , 'My ICU: {\n\n\t<> 1:2<> \n\n, select, =1 {one} other {other}}'
] ,
// do not replace VAR_* anywhere else in a string (only in ICU)
[
'My ICU: {VAR_PLURAL, plural, one {1} other {other}} VAR_PLURAL and VAR_SELECT' ,
{ VAR_PLURAL : '<27> 1:2<> ' } ,
'My ICU: {<7B> 1:2<> , plural, one {1} other {other}} VAR_PLURAL and VAR_SELECT'
] ,
// replace VAR_*'s in nested ICUs
[
'My ICU: {VAR_PLURAL, plural, one {1 - {VAR_SELECT, age, 50 {fifty} other {other}}} other {other}}' ,
{ VAR_PLURAL : '<27> 1:2<> ' , VAR_SELECT : '<27> 5<EFBFBD> ' } ,
'My ICU: {<7B> 1:2<> , plural, one {1 - {<7B> 5<EFBFBD> , age, 50 {fifty} other {other}}} other {other}}'
] ,
[
'My ICU: {VAR_PLURAL, plural, one {1 - {VAR_PLURAL_1, age, 50 {fifty} other {other}}} other {other}}' ,
{ VAR_PLURAL : '<27> 1:2<> ' , VAR_PLURAL_1 : '<27> 5<EFBFBD> ' } ,
'My ICU: {<7B> 1:2<> , plural, one {1 - {<7B> 5<EFBFBD> , age, 50 {fifty} other {other}}} other {other}}'
] ,
// ICU replacement
[
'My ICU #1: <20> I18N_EXP_ICU<43> , My ICU #2: <20> I18N_EXP_ICU<43> ' ,
{ ICU : [ 'ICU_VALUE_1' , 'ICU_VALUE_2' ] } , 'My ICU #1: ICU_VALUE_1, My ICU #2: ICU_VALUE_2'
] ,
// mixed case
[
` Start: ${ str } , ${ str } . ICU: {VAR_SELECT, count, 10 {ten} other {other}}.
Another ICU : <EFBFBD> I18N_EXP_ICU <EFBFBD> and $ { str } , $ { str } and one more ICU : <EFBFBD> I18N_EXP_ICU <EFBFBD> and end . ` ,
{ VAR_SELECT : '<27> 1:2<> ' , ICU : [ 'ICU_VALUE_1' , 'ICU_VALUE_2' ] } ,
` Start: ${ arr [ 0 ] } , ${ arr [ 1 ] } . ICU: {<7B> 1:2<> , count, 10 {ten} other {other}}.
Another ICU : ICU_VALUE_1 and $ { arr [ 2 ] } , $ { arr [ 3 ] } and one more ICU : ICU_VALUE_2 and end . ` ,
] ,
] ;
cases . forEach ( ( [ input , replacements , output ] ) = > {
2019-05-17 21:49:21 -04:00
expect ( ɵ ɵ i18nPostprocess ( input as string , replacements as any ) ) . toEqual ( output as string ) ;
2018-10-18 13:08:51 -04:00
} ) ;
} ) ;
2019-01-13 19:56:00 -05:00
it ( 'should handle nested template represented by multi-value placeholders' , ( ) = > {
/ * *
* < div i18n >
* < span >
* Hello - 1
* < / span >
* < span * ngIf = "visible" >
* Hello - 2
* < span * ngIf = "visible" >
* Hello - 3
* < span * ngIf = "visible" >
* Hello - 4
* < / span >
* < / span >
* < / span >
* < span >
* Hello - 5
* < / span >
* < / div >
* /
const generated = `
[ <EFBFBD> # 2 <EFBFBD> | <EFBFBD> # 4 <EFBFBD> ] Bonjour - 1 [ <EFBFBD> / # 2 <EFBFBD> | <EFBFBD> / # 1 :3 <EFBFBD> <EFBFBD> / * 2 :3 <EFBFBD> | <EFBFBD> / # 1 :2 <EFBFBD> <EFBFBD> / * 2 :2 <EFBFBD> | <EFBFBD> / # 1 :1 <EFBFBD> <EFBFBD> / * 3 :1 <EFBFBD> | <EFBFBD> / # 4 <EFBFBD> ]
[ <EFBFBD> * 3 :1 <EFBFBD> <EFBFBD> # 1 :1 <EFBFBD> | <EFBFBD> * 2 :2 <EFBFBD> <EFBFBD> # 1 :2 <EFBFBD> | <EFBFBD> * 2 :3 <EFBFBD> <EFBFBD> # 1 :3 <EFBFBD> ]
Bonjour - 2
[ <EFBFBD> * 3 :1 <EFBFBD> <EFBFBD> # 1 :1 <EFBFBD> | <EFBFBD> * 2 :2 <EFBFBD> <EFBFBD> # 1 :2 <EFBFBD> | <EFBFBD> * 2 :3 <EFBFBD> <EFBFBD> # 1 :3 <EFBFBD> ]
Bonjour - 3
[ <EFBFBD> * 3 :1 <EFBFBD> <EFBFBD> # 1 :1 <EFBFBD> | <EFBFBD> * 2 :2 <EFBFBD> <EFBFBD> # 1 :2 <EFBFBD> | <EFBFBD> * 2 :3 <EFBFBD> <EFBFBD> # 1 :3 <EFBFBD> ] Bonjour - 4 [ <EFBFBD> / # 2 <EFBFBD> | <EFBFBD> / # 1 :3 <EFBFBD> <EFBFBD> / * 2 :3 <EFBFBD> | <EFBFBD> / # 1 :2 <EFBFBD> <EFBFBD> / * 2 :2 <EFBFBD> | <EFBFBD> / # 1 :1 <EFBFBD> <EFBFBD> / * 3 :1 <EFBFBD> | <EFBFBD> / # 4 <EFBFBD> ]
[ <EFBFBD> / # 2 <EFBFBD> | <EFBFBD> / # 1 :3 <EFBFBD> <EFBFBD> / * 2 :3 <EFBFBD> | <EFBFBD> / # 1 :2 <EFBFBD> <EFBFBD> / * 2 :2 <EFBFBD> | <EFBFBD> / # 1 :1 <EFBFBD> <EFBFBD> / * 3 :1 <EFBFBD> | <EFBFBD> / # 4 <EFBFBD> ]
[ <EFBFBD> / # 2 <EFBFBD> | <EFBFBD> / # 1 :3 <EFBFBD> <EFBFBD> / * 2 :3 <EFBFBD> | <EFBFBD> / # 1 :2 <EFBFBD> <EFBFBD> / * 2 :2 <EFBFBD> | <EFBFBD> / # 1 :1 <EFBFBD> <EFBFBD> / * 3 :1 <EFBFBD> | <EFBFBD> / # 4 <EFBFBD> ]
[ <EFBFBD> # 2 <EFBFBD> | <EFBFBD> # 4 <EFBFBD> ] Bonjour - 5 [ <EFBFBD> / # 2 <EFBFBD> | <EFBFBD> / # 1 :3 <EFBFBD> <EFBFBD> / * 2 :3 <EFBFBD> | <EFBFBD> / # 1 :2 <EFBFBD> <EFBFBD> / * 2 :2 <EFBFBD> | <EFBFBD> / # 1 :1 <EFBFBD> <EFBFBD> / * 3 :1 <EFBFBD> | <EFBFBD> / # 4 <EFBFBD> ]
` ;
const final = `
<EFBFBD> # 2 <EFBFBD> Bonjour - 1 <EFBFBD> / # 2 <EFBFBD>
<EFBFBD> * 3 :1 <EFBFBD>
<EFBFBD> # 1 :1 <EFBFBD>
Bonjour - 2
<EFBFBD> * 2 :2 <EFBFBD>
<EFBFBD> # 1 :2 <EFBFBD>
Bonjour - 3
<EFBFBD> * 2 :3 <EFBFBD>
<EFBFBD> # 1 :3 <EFBFBD> Bonjour - 4 <EFBFBD> / # 1 :3 <EFBFBD>
<EFBFBD> / * 2 :3 <EFBFBD>
<EFBFBD> / # 1 :2 <EFBFBD>
<EFBFBD> / * 2 :2 <EFBFBD>
<EFBFBD> / # 1 :1 <EFBFBD>
<EFBFBD> / * 3 :1 <EFBFBD>
<EFBFBD> # 4 <EFBFBD> Bonjour - 5 <EFBFBD> / # 4 <EFBFBD>
` ;
2019-05-17 21:49:21 -04:00
expect ( ɵ ɵ i18nPostprocess ( generated . replace ( /\s+/g , '' ) ) ) . toEqual ( final . replace ( /\s+/g , '' ) ) ;
2019-01-13 19:56:00 -05:00
} ) ;
2018-10-18 13:08:51 -04:00
it ( 'should throw in case we have invalid string' , ( ) = > {
2019-05-23 09:17:42 -04:00
expect (
( ) = > ɵ ɵ i18nPostprocess (
'My ICU #1: <20> I18N_EXP_ICU<43> , My ICU #2: <20> I18N_EXP_ICU<43> ' , { ICU : [ 'ICU_VALUE_1' ] } ) )
. toThrowError ( ) ;
2018-10-18 13:08:51 -04:00
} ) ;
} ) ;
2018-06-18 10:55:43 -04:00
} ) ;