/**
* @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 {Component, ContentChild, ContentChildren, Directive, HostBinding, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [AppComp, DirectiveWithTplRef]});
});
it('should translate text', () => {
ɵi18nConfigureLocalize({translations: {'text': 'texte'}});
const fixture = initWithTemplate(AppComp, `
text
`);
expect(fixture.nativeElement.innerHTML).toEqual(`texte
`);
});
it('should support interpolations', () => {
ɵi18nConfigureLocalize(
{translations: {'Hello {$interpolation}!': 'Bonjour {$interpolation}!'}});
const fixture = initWithTemplate(AppComp, `Hello {{name}}!
`);
expect(fixture.nativeElement.innerHTML).toEqual(`Bonjour Angular!
`);
fixture.componentRef.instance.name = `John`;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(`Bonjour John!
`);
});
it('should support interpolations with custom interpolation config', () => {
ɵi18nConfigureLocalize({translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
const interpolation = ['{%', '%}'] as[string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture = initWithTemplate(AppComp, `Hello {% name %}
`);
expect(fixture.nativeElement.innerHTML).toBe('Bonjour Angular
');
});
it('should support interpolations with complex expressions', () => {
ɵi18nConfigureLocalize({
translations:
{'{$interpolation} - {$interpolation_1}': '{$interpolation} - {$interpolation_1} (fr)'}
});
const fixture =
initWithTemplate(AppComp, `{{ name | uppercase }} - {{ obj?.a?.b }}
`);
expect(fixture.nativeElement.innerHTML).toEqual(`ANGULAR - (fr)
`);
fixture.componentRef.instance.obj = {a: {b: 'value'}};
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(`ANGULAR - value (fr)
`);
});
it('should support elements', () => {
ɵi18nConfigureLocalize({
translations: {
'Hello {$startTagSpan}world{$closeTagSpan} and {$startTagDiv}universe{$closeTagDiv}!':
'Bonjour {$startTagSpan}monde{$closeTagSpan} et {$startTagDiv}univers{$closeTagDiv}!'
}
});
const fixture = initWithTemplate(
AppComp, `Hello
world and
universe
!
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`Bonjour
monde et
univers
!
`);
});
it('should support removing elements', () => {
ɵi18nConfigureLocalize({
translations: {
'Hello {$startBoldText}my{$closeBoldText}{$startTagSpan}world{$closeTagSpan}':
'Bonjour {$startTagSpan}monde{$closeTagSpan}'
}
});
const fixture =
initWithTemplate(AppComp, `Hello myworld
!
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`Bonjour monde
!
`);
});
it('should support moving elements', () => {
ɵi18nConfigureLocalize({
translations: {
'Hello {$startTagSpan}world{$closeTagSpan} and {$startTagDiv}universe{$closeTagDiv}!':
'Bonjour {$startTagDiv}univers{$closeTagDiv} et {$startTagSpan}monde{$closeTagSpan}!'
}
});
const fixture = initWithTemplate(
AppComp, `Hello
world and
universe
!
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`Bonjour
univers
et
monde!
`);
});
it('should support template directives', () => {
ɵi18nConfigureLocalize({
translations: {
'Content: {$startTagDiv}before{$startTagSpan}middle{$closeTagSpan}after{$closeTagDiv}!':
'Contenu: {$startTagDiv}avant{$startTagSpan}milieu{$closeTagSpan}après{$closeTagDiv}!'
}
});
const fixture = initWithTemplate(
AppComp,
`Content:
beforemiddleafter
!
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`Contenu:
avantmilieuaprès
!
`);
fixture.componentRef.instance.visible = false;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(`Contenu: !
`);
});
it('should support multiple i18n blocks', () => {
ɵi18nConfigureLocalize({
translations: {
'trad {$interpolation}': 'traduction {$interpolation}',
'start {$interpolation} middle {$interpolation_1} end':
'start {$interpolation_1} middle {$interpolation} end',
'{$startTagC}trad{$closeTagC}{$startTagD}{$closeTagD}{$startTagE}{$closeTagE}':
'{$startTagE}{$closeTagE}{$startTagC}traduction{$closeTagC}'
}
});
const fixture = initWithTemplate(AppComp, `
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(
``);
});
it('should support multiple sibling i18n blocks', () => {
ɵi18nConfigureLocalize({
translations: {
'Section 1': 'Section un',
'Section 2': 'Section deux',
'Section 3': 'Section trois',
}
});
const fixture = initWithTemplate(AppComp, `
Section 1
Section 2
Section 3
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`Section un
Section deux
Section trois
`);
});
it('should support multiple sibling i18n blocks inside of a template directive', () => {
ɵi18nConfigureLocalize({
translations: {
'Section 1': 'Section un',
'Section 2': 'Section deux',
'Section 3': 'Section trois',
}
});
const fixture = initWithTemplate(AppComp, `
- Section 1
- Section 2
- Section 3
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(
`- Section un
- Section deux
- Section trois
- Section un
- Section deux
- Section trois
- Section un
- Section deux
- Section trois
`);
});
it('should properly escape quotes in content', () => {
ɵi18nConfigureLocalize({
translations: {
'\'Single quotes\' and "Double quotes"': '\'Guillemets simples\' et "Guillemets doubles"'
}
});
const fixture =
initWithTemplate(AppComp, `'Single quotes' and "Double quotes"
`);
expect(fixture.nativeElement.innerHTML)
.toEqual('\'Guillemets simples\' et "Guillemets doubles"
');
});
it('should correctly bind to context in nested template', () => {
ɵi18nConfigureLocalize({translations: {'Item {$interpolation}': 'Article {$interpolation}'}});
const fixture = initWithTemplate(AppComp, `
`);
const element = fixture.nativeElement;
for (let i = 0; i < element.children.length; i++) {
const child = element.children[i];
expect(child).toHaveText(`Article ${i + 1}`);
}
});
it('should ignore i18n attributes on self-closing tags', () => {
const fixture = initWithTemplate(AppComp, '');
expect(fixture.nativeElement.innerHTML).toBe(``);
});
it('should handle i18n attribute with directives', () => {
ɵi18nConfigureLocalize({translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
const fixture = initWithTemplate(AppComp, `Hello {{ name }}
`);
expect(fixture.nativeElement.firstChild).toHaveText('Bonjour Angular');
});
it('should work correctly with event listeners', () => {
ɵi18nConfigureLocalize({translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
@Component(
{selector: 'app-comp', template: `Hello {{ name }}
`})
class ListenerComp {
name = `Angular`;
clicks = 0;
onClick() { this.clicks++; }
}
TestBed.configureTestingModule({declarations: [ListenerComp]});
const fixture = TestBed.createComponent(ListenerComp);
fixture.detectChanges();
const element = fixture.nativeElement.firstChild;
const instance = fixture.componentInstance;
expect(element).toHaveText('Bonjour Angular');
expect(instance.clicks).toBe(0);
element.click();
expect(instance.clicks).toBe(1);
});
describe('ng-container and ng-template support', () => {
it('should support ng-container', () => {
ɵi18nConfigureLocalize({translations: {'text': 'texte'}});
const fixture = initWithTemplate(AppComp, `text`);
expect(fixture.nativeElement.innerHTML).toEqual(`texte`);
});
it('should handle single translation message within ng-template', () => {
ɵi18nConfigureLocalize(
{translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
const fixture =
initWithTemplate(AppComp, `Hello {{ name }}`);
const element = fixture.nativeElement;
expect(element).toHaveText('Bonjour Angular');
});
it('should be able to act as child elements inside i18n block (plain text content)', () => {
ɵi18nConfigureLocalize({
translations: {
'{$startTagNgTemplate} Hello {$closeTagNgTemplate}{$startTagNgContainer} Bye {$closeTagNgContainer}':
'{$startTagNgTemplate} Bonjour {$closeTagNgTemplate}{$startTagNgContainer} Au revoir {$closeTagNgContainer}'
}
});
const fixture = initWithTemplate(AppComp, `
Hello
Bye
`);
const element = fixture.nativeElement.firstChild;
expect(element.textContent.replace(/\s+/g, ' ').trim()).toBe('Bonjour Au revoir');
});
it('should be able to act as child elements inside i18n block (text + tags)', () => {
ɵi18nConfigureLocalize({
translations: {
'{$startTagNgTemplate}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgContainer}':
'{$startTagNgTemplate}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgContainer}'
}
});
const fixture = initWithTemplate(AppComp, `
Hello
Hello
`);
const element = fixture.nativeElement;
const spans = element.getElementsByTagName('span');
for (let i = 0; i < spans.length; i++) {
expect(spans[i]).toHaveText('Bonjour');
}
});
it('should be able to handle deep nested levels with templates', () => {
ɵi18nConfigureLocalize({
translations: {
'{$startTagSpan} Hello - 1 {$closeTagSpan}{$startTagSpan_1} Hello - 2 {$startTagSpan_1} Hello - 3 {$startTagSpan_1} Hello - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Hello - 5 {$closeTagSpan}':
'{$startTagSpan} Bonjour - 1 {$closeTagSpan}{$startTagSpan_1} Bonjour - 2 {$startTagSpan_1} Bonjour - 3 {$startTagSpan_1} Bonjour - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Bonjour - 5 {$closeTagSpan}'
}
});
const fixture = initWithTemplate(AppComp, `
Hello - 1
Hello - 2
Hello - 3
Hello - 4
Hello - 5
`);
const element = fixture.nativeElement;
const spans = element.getElementsByTagName('span');
for (let i = 0; i < spans.length; i++) {
expect(spans[i].innerHTML).toContain(`Bonjour - ${i + 1}`);
}
});
it('should handle self-closing tags as content', () => {
ɵi18nConfigureLocalize({
translations: {
'{$startTagSpan}My logo{$tagImg}{$closeTagSpan}':
'{$startTagSpan}Mon logo{$tagImg}{$closeTagSpan}'
}
});
const content = `My logo`;
const fixture = initWithTemplate(AppComp, `
${content}
${content}
`);
const element = fixture.nativeElement;
const spans = element.getElementsByTagName('span');
for (let i = 0; i < spans.length; i++) {
const child = spans[i];
expect(child).toHaveText('Mon logo');
}
});
});
describe('should support ICU expressions', () => {
it('with no root node', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
'{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autre}}'
}
});
const fixture =
initWithTemplate(AppComp, `{count, select, 10 {ten} 20 {twenty} other {other}}`);
const element = fixture.nativeElement;
expect(element).toHaveText('autre');
});
it('with no i18n tag', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
'{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autre}}'
}
});
const fixture = initWithTemplate(
AppComp, `{count, select, 10 {ten} 20 {twenty} other {other}}
`);
const element = fixture.nativeElement;
expect(element).toHaveText('autre');
});
it('multiple', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_PLURAL, plural, =0 {no {$startBoldText}emails{$closeBoldText}!} =1 {one {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}':
'{VAR_PLURAL, plural, =0 {aucun {$startBoldText}email{$closeBoldText}!} =1 {un {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}',
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
}
});
const fixture = initWithTemplate(AppComp, `{count, plural,
=0 {no emails!}
=1 {one email}
other {{{count}} emails}
} - {name, select,
other {({{name}})}
}
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`aucun email! - (Angular)
`);
fixture.componentRef.instance.count = 4;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`4 emails - (Angular)
`);
fixture.componentRef.instance.count = 0;
fixture.componentRef.instance.name = 'John';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(`aucun email! - (John)
`);
});
it('with custom interpolation config', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_SELECT, select, 10 {ten} other {{$interpolation}}}':
'{VAR_SELECT, select, 10 {dix} other {{$interpolation}}}'
}
});
const interpolation = ['{%', '%}'] as[string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture =
initWithTemplate(AppComp, `{count, select, 10 {ten} other {{% name %}}}
`);
expect(fixture.nativeElement).toHaveText(`Angular`);
});
it('inside HTML elements', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_PLURAL, plural, =0 {no {$startBoldText}emails{$closeBoldText}!} =1 {one {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}':
'{VAR_PLURAL, plural, =0 {aucun {$startBoldText}email{$closeBoldText}!} =1 {un {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}',
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
}
});
const fixture = initWithTemplate(AppComp, `{count, plural,
=0 {no emails!}
=1 {one email}
other {{{count}} emails}
} - {name, select,
other {({{name}})}
}
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(
`aucun email! - (Angular)
`);
fixture.componentRef.instance.count = 4;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`4 emails - (Angular)
`);
fixture.componentRef.instance.count = 0;
fixture.componentRef.instance.name = 'John';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`aucun email! - (John)
`);
});
it('inside template directives', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
}
});
const fixture = initWithTemplate(AppComp, `{name, select,
other {({{name}})}
}
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`(Angular)
`);
fixture.componentRef.instance.visible = false;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(``);
});
it('inside ng-container', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
}
});
const fixture = initWithTemplate(AppComp, `{name, select,
other {({{name}})}
}`);
expect(fixture.nativeElement.innerHTML).toEqual(`(Angular)`);
});
it('inside ', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
'{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autre}}'
}
});
const fixture = initWithTemplate(AppComp, `
{count, select, 10 {ten} 20 {twenty} other {other}}
`);
const element = fixture.nativeElement;
expect(element).toHaveText('autre');
});
it('nested', () => {
ɵi18nConfigureLocalize({
translations: {
'{VAR_PLURAL, plural, =0 {zero} other {{$interpolation} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}}!}}':
'{VAR_PLURAL, plural, =0 {zero} other {{$interpolation} {VAR_SELECT, select, cat {chats} dog {chients} other {animaux}}!}}'
}
});
const fixture = initWithTemplate(AppComp, `{count, plural,
=0 {zero}
other {{{count}} {name, select,
cat {cats}
dog {dogs}
other {animals}
}!}
}
`);
expect(fixture.nativeElement.innerHTML).toEqual(`zero
`);
fixture.componentRef.instance.count = 4;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(`4 animaux!
`);
});
});
describe('should support attributes', () => {
it('text', () => {
ɵi18nConfigureLocalize({translations: {'text': 'texte'}});
const fixture = initWithTemplate(AppComp, ``);
expect(fixture.nativeElement.innerHTML).toEqual(``);
});
it('interpolations', () => {
ɵi18nConfigureLocalize(
{translations: {'hello {$interpolation}': 'bonjour {$interpolation}'}});
const fixture =
initWithTemplate(AppComp, ``);
expect(fixture.nativeElement.innerHTML).toEqual(``);
fixture.componentRef.instance.name = 'John';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(``);
});
it('with pipes', () => {
ɵi18nConfigureLocalize(
{translations: {'hello {$interpolation}': 'bonjour {$interpolation}'}});
const fixture = initWithTemplate(
AppComp, ``);
expect(fixture.nativeElement.innerHTML).toEqual(``);
});
it('multiple attributes', () => {
ɵi18nConfigureLocalize(
{translations: {'hello {$interpolation}': 'bonjour {$interpolation}'}});
const fixture = initWithTemplate(
AppComp,
``);
expect(fixture.nativeElement.innerHTML)
.toEqual(``);
fixture.componentRef.instance.name = 'John';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(``);
});
it('on removed elements', () => {
ɵi18nConfigureLocalize(
{translations: {'text': 'texte', '{$startTagSpan}content{$closeTagSpan}': 'contenu'}});
const fixture =
initWithTemplate(AppComp, `content
`);
expect(fixture.nativeElement.innerHTML).toEqual(`contenu
`);
});
it('with custom interpolation config', () => {
ɵi18nConfigureLocalize(
{translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
const interpolation = ['{%', '%}'] as[string, string];
TestBed.overrideComponent(AppComp, {set: {interpolation}});
const fixture =
initWithTemplate(AppComp, ``);
const element = fixture.nativeElement.firstChild;
expect(element.title).toBe('Bonjour Angular');
});
it('in nested template', () => {
ɵi18nConfigureLocalize({translations: {'Item {$interpolation}': 'Article {$interpolation}'}});
const fixture = initWithTemplate(AppComp, `
`);
const element = fixture.nativeElement;
for (let i = 0; i < element.children.length; i++) {
const child = element.children[i];
expect((child as any).innerHTML).toBe(``);
}
});
it('should add i18n attributes on self-closing tags', () => {
ɵi18nConfigureLocalize(
{translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
const fixture =
initWithTemplate(AppComp, ``);
const element = fixture.nativeElement.firstChild;
expect(element.title).toBe('Bonjour Angular');
});
});
it('should work with directives and host bindings', () => {
let directiveInstances: ClsDir[] = [];
@Directive({selector: '[test]'})
class ClsDir {
@HostBinding('className')
klass = 'foo';
constructor() { directiveInstances.push(this); }
}
@Component({
selector: `my-app`,
template: `
trad: {exp1, plural,
=0 {no emails!}
=1 {one email}
other {{{exp1}} emails}
}
`
})
class MyApp {
exp1 = 1;
exp2 = 2;
}
TestBed.configureTestingModule({declarations: [ClsDir, MyApp]});
ɵi18nConfigureLocalize({
translations: {
'start {$interpolation} middle {$interpolation_1} end':
'début {$interpolation_1} milieu {$interpolation} fin',
'{VAR_PLURAL, plural, =0 {no {$startBoldText}emails{$closeBoldText}!} =1 {one {$startItalicText}email{$closeItalicText}} other {{$interpolation} emails}}':
'{VAR_PLURAL, plural, =0 {aucun {$startBoldText}email{$closeBoldText}!} =1 {un {$startItalicText}email{$closeItalicText}} other {{$interpolation} emails}}',
' trad: {$icu}': ' traduction: {$icu}'
}
});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
` traduction: un email
`);
directiveInstances.forEach(instance => instance.klass = 'bar');
fixture.componentRef.instance.exp1 = 2;
fixture.componentRef.instance.exp2 = 3;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
` traduction: 2 emails
`);
});
it('should support adding/moving/removing nodes', () => {
ɵi18nConfigureLocalize({
translations: {
'{$startTagDiv2}{$closeTagDiv2}{$startTagDiv3}{$closeTagDiv3}{$startTagDiv4}{$closeTagDiv4}{$startTagDiv5}{$closeTagDiv5}{$startTagDiv6}{$closeTagDiv6}{$startTagDiv7}{$closeTagDiv7}{$startTagDiv8}{$closeTagDiv8}':
'{$startTagDiv2}{$closeTagDiv2}{$startTagDiv8}{$closeTagDiv8}{$startTagDiv4}{$closeTagDiv4}{$startTagDiv5}{$closeTagDiv5}Bonjour monde{$startTagDiv3}{$closeTagDiv3}{$startTagDiv7}{$closeTagDiv7}'
}
});
const fixture = initWithTemplate(AppComp, `
`);
expect(fixture.nativeElement.innerHTML)
.toEqual(
``);
});
describe('projection', () => {
it('should project the translations', () => {
@Component({selector: 'child', template: '
'})
class Child {
}
@Component({
selector: 'parent',
template: `
I am projected from
{{name}}
`
})
class Parent {
name: string = 'Parent';
}
TestBed.configureTestingModule({declarations: [Parent, Child]});
ɵi18nConfigureLocalize({
translations: {
'Child of {$interpolation}': 'Enfant de {$interpolation}',
'{$startTagChild}I am projected from {$startBoldText}{$interpolation}{$startTagRemoveMe_1}{$closeTagRemoveMe_1}{$closeBoldText}{$startTagRemoveMe_2}{$closeTagRemoveMe_2}{$closeTagChild}{$startTagRemoveMe_3}{$closeTagRemoveMe_3}':
'{$startTagChild}Je suis projeté depuis {$startBoldText}{$interpolation}{$closeBoldText}{$closeTagChild}'
}
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`Je suis projeté depuis Parent
`);
});
it('should project a translated i18n block', () => {
@Component({selector: 'child', template: '
'})
class Child {
}
@Component({
selector: 'parent',
template: `
I am projected from {{name}}
`
})
class Parent {
name: string = 'Parent';
}
TestBed.configureTestingModule({declarations: [Parent, Child]});
ɵi18nConfigureLocalize({
translations: {
'Child of {$interpolation}': 'Enfant de {$interpolation}',
'I am projected from {$interpolation}': 'Je suis projeté depuis {$interpolation}'
}
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`Je suis projeté depuis Parent
`);
// it should be able to render a new component with the same template code
const fixture2 = TestBed.createComponent(Parent);
fixture2.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(fixture2.nativeElement.innerHTML);
fixture2.componentRef.instance.name = 'Parent 2';
fixture2.detectChanges();
expect(fixture2.nativeElement.innerHTML)
.toEqual(
`Je suis projeté depuis Parent 2
`);
// The first fixture should not have changed
expect(fixture.nativeElement.innerHTML).not.toEqual(fixture2.nativeElement.innerHTML);
});
it('should re-project translations when multiple projections', () => {
@Component({selector: 'grand-child', template: '
'})
class GrandChild {
}
@Component(
{selector: 'child', template: ''})
class Child {
}
@Component({selector: 'parent', template: `Hello World!`})
class Parent {
name: string = 'Parent';
}
TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
ɵi18nConfigureLocalize({
translations: {
'{$startBoldText}Hello{$closeBoldText} World!':
'{$startBoldText}Bonjour{$closeBoldText} monde!'
}
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('Bonjour monde!
');
});
// FW-1319 Runtime i18n should be able to remove projected placeholders
xit('should be able to remove projected placeholders', () => {
@Component({selector: 'grand-child', template: '
'})
class GrandChild {
}
@Component(
{selector: 'child', template: ''})
class Child {
}
@Component({selector: 'parent', template: `Hello World!`})
class Parent {
name: string = 'Parent';
}
TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
ɵi18nConfigureLocalize(
{translations: {'{$startBoldText}Hello{$closeBoldText} World!': 'Bonjour monde!'}});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('Bonjour monde!
');
});
// FW-1312: Wrong i18n code generated by the compiler when the template has 2 empty `span`
xit('should project translations with selectors', () => {
@Component({selector: 'child', template: ``})
class Child {
}
@Component({
selector: 'parent',
template: `
`
})
class Parent {
}
TestBed.configureTestingModule({declarations: [Parent, Child]});
ɵi18nConfigureLocalize({
translations: {
'{$startTagSpan}{$closeTagSpan}{$startTagSpan_1}{$closeTagSpan}':
'{$startTagSpan}Contenu{$closeTagSpan}'
}
});
const fixture = TestBed.createComponent(Parent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('Contenu');
});
});
describe('queries', () => {
function toHtml(element: Element): string {
return element.innerHTML.replace(/\sng-reflect-\S*="[^"]*"/g, '')
.replace(//g, '');
}
it('detached nodes should still be part of query', () => {
@Directive({selector: '[text]', inputs: ['text'], exportAs: 'textDir'})
class TextDirective {
// TODO(issue/24571): remove '!'.
text !: string;
constructor() {}
}
@Component({selector: 'div-query', template: ''})
class DivQuery {
// TODO(issue/24571): remove '!'.
@ContentChild(TemplateRef) template !: TemplateRef;
// TODO(issue/24571): remove '!'.
@ViewChild('vc', {read: ViewContainerRef})
vc !: ViewContainerRef;
// TODO(issue/24571): remove '!'.
@ContentChildren(TextDirective, {descendants: true})
query !: QueryList;
create() { this.vc.createEmbeddedView(this.template); }
destroy() { this.vc.clear(); }
}
TestBed.configureTestingModule({declarations: [TextDirective, DivQuery]});
ɵi18nConfigureLocalize({
translations: {
'{$startTagNgTemplate}{$startTagDiv_1}{$startTagDiv}{$startTagSpan}Content{$closeTagSpan}{$closeTagDiv}{$closeTagDiv}{$closeTagNgTemplate}':
'{$startTagNgTemplate}Contenu{$closeTagNgTemplate}'
}
});
const fixture = initWithTemplate(AppComp, `
`);
const q = fixture.debugElement.children[0].references.q;
expect(q.query.length).toEqual(0);
// Create embedded view
q.create();
fixture.detectChanges();
expect(q.query.length).toEqual(1);
expect(toHtml(fixture.nativeElement))
.toEqual(`Contenu`);
// Disable ng-if
fixture.componentInstance.visible = false;
fixture.detectChanges();
expect(q.query.length).toEqual(0);
expect(toHtml(fixture.nativeElement))
.toEqual(`Contenu`);
});
});
});
function initWithTemplate(compType: Type, template: string) {
TestBed.overrideComponent(compType, {set: {template}});
const fixture = TestBed.createComponent(compType);
fixture.detectChanges();
return fixture;
}
@Component({selector: 'app-comp', template: ``})
class AppComp {
name = `Angular`;
visible = true;
count = 0;
}
@Directive({
selector: '[tplRef]',
})
class DirectiveWithTplRef {
constructor(public vcRef: ViewContainerRef, public tplRef: TemplateRef<{}>) {}
ngOnInit() { this.vcRef.createEmbeddedView(this.tplRef, {}); }
}