/** * @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, `
trad {{name}} hello trad
`); expect(fixture.nativeElement.innerHTML) .toEqual( `
traduction Angular hello traduction
`); }); 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, ` `); expect(fixture.nativeElement.innerHTML) .toEqual( ``); }); 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, `
Item {{ id }}
`); 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( `
Bonjour monde
`); }); 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, `
Content
`); 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, {}); } }