test(core): verify that Ivy i18n works correctly with HTML namespaces (#36943)

This commit adds several tests to verify that i18n logic in Ivy handles elements with HTML namespaces correctly.

Related to #36941.

PR Close #36943
This commit is contained in:
Andrew Kushnir 2020-05-05 17:52:49 -07:00 committed by Kara Erickson
parent 7acd33007d
commit 7a30153aa1
2 changed files with 170 additions and 3 deletions

View File

@ -3832,4 +3832,102 @@ $` + String.raw`{$I18N_4$}:ICU:\`;
} }
}); });
}); });
describe('namespaces', () => {
it('should handle namespaces inside i18n blocks', () => {
const input = `
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject i18n>
<xhtml:div xmlns="http://www.w3.org/1999/xhtml">
Count: <span>5</span>
</xhtml:div>
</foreignObject>
</svg>
`;
const output = String.raw`
var $I18N_0$;
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
const $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$ = goog.getMsg("{$startTagXhtmlDiv} Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}{$closeTagXhtmlDiv}", {
"startTagXhtmlDiv": "\uFFFD#3\uFFFD",
"startTagXhtmlSpan": "\uFFFD#4\uFFFD",
"closeTagXhtmlSpan": "\uFFFD/#4\uFFFD",
"closeTagXhtmlDiv": "\uFFFD/#3\uFFFD"
});
$I18N_0$ = $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$;
}
else {
$I18N_0$ = $localize \`$` +
String.raw`{"\uFFFD#3\uFFFD"}:START_TAG__XHTML_DIV: Count: $` +
String.raw`{"\uFFFD#4\uFFFD"}:START_TAG__XHTML_SPAN:5$` +
String.raw`{"\uFFFD/#4\uFFFD"}:CLOSE_TAG__XHTML_SPAN:$` +
String.raw`{"\uFFFD/#3\uFFFD"}:CLOSE_TAG__XHTML_DIV:\`;
}
function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵnamespaceSVG();
$r3$.ɵɵelementStart(0, "svg", 0);
$r3$.ɵɵelementStart(1, "foreignObject");
$r3$.ɵɵi18nStart(2, $I18N_0$);
$r3$.ɵɵnamespaceHTML();
$r3$.ɵɵelementStart(3, "div", 1);
$r3$.ɵɵelement(4, "span");
$r3$.ɵɵelementEnd();
$r3$.ɵɵi18nEnd();
$r3$.ɵɵelementEnd();
$r3$.ɵɵelementEnd();
}
}
`;
verify(input, output);
});
it('should handle namespaces on i18n block containers', () => {
const input = `
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<xhtml:div xmlns="http://www.w3.org/1999/xhtml" i18n>
Count: <span>5</span>
</xhtml:div>
</foreignObject>
</svg>
`;
const output = String.raw`
var $I18N_0$;
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
const $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$ = goog.getMsg(" Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}", {
"startTagXhtmlSpan": "\uFFFD#4\uFFFD",
"closeTagXhtmlSpan": "\uFFFD/#4\uFFFD"
});
$I18N_0$ = $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$;
}
else {
$I18N_0$ = $localize \` Count: $` +
String.raw`{"\uFFFD#4\uFFFD"}:START_TAG__XHTML_SPAN:5$` +
String.raw`{"\uFFFD/#4\uFFFD"}:CLOSE_TAG__XHTML_SPAN:\`;
}
function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵnamespaceSVG();
$r3$.ɵɵelementStart(0, "svg", 0);
$r3$.ɵɵelementStart(1, "foreignObject");
$r3$.ɵɵnamespaceHTML();
$r3$.ɵɵelementStart(2, "div", 1);
$r3$.ɵɵi18nStart(3, $I18N_0$);
$r3$.ɵɵelement(4, "span");
$r3$.ɵɵi18nEnd();
$r3$.ɵɵelementEnd();
$r3$.ɵɵelementEnd();
$r3$.ɵɵelementEnd();
}
}
`;
verify(input, output, {verbose: true});
});
});
}); });

View File

@ -9,15 +9,15 @@
// below. This would normally be done inside the application `polyfills.ts` file. // below. This would normally be done inside the application `polyfills.ts` file.
import '@angular/localize/init'; import '@angular/localize/init';
import {CommonModule, registerLocaleData} from '@angular/common'; import {CommonModule, DOCUMENT, registerLocaleData} from '@angular/common';
import localeEs from '@angular/common/locales/es'; import localeEs from '@angular/common/locales/es';
import localeRo from '@angular/common/locales/ro'; import localeRo from '@angular/common/locales/ro';
import {computeMsgId} from '@angular/compiler'; import {computeMsgId} from '@angular/compiler';
import {Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, Input, LOCALE_ID, NO_ERRORS_SCHEMA, Pipe, PipeTransform, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; import {Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, Input, LOCALE_ID, NO_ERRORS_SCHEMA, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, Type, ViewChild, ViewContainerRef, ɵsetDocument} from '@angular/core';
import {setDelayProjection} from '@angular/core/src/render3/instructions/projection'; import {setDelayProjection} from '@angular/core/src/render3/instructions/projection';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {clearTranslations, loadTranslations} from '@angular/localize'; import {clearTranslations, loadTranslations} from '@angular/localize';
import {By} from '@angular/platform-browser'; import {By, ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing'; import {onlyInIvy} from '@angular/private/testing';
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject} from 'rxjs';
@ -530,6 +530,75 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
}); });
}); });
describe('should work correctly with namespaces', () => {
beforeEach(() => {
function _document(): any {
// Tell Ivy about the global document
ɵsetDocument(document);
return document;
}
TestBed.configureTestingModule({
providers: [
{provide: DOCUMENT, useFactory: _document, deps: []},
// TODO(FW-811): switch back to default server renderer (i.e. remove the line below)
// once it starts to support Ivy namespace format (URIs) correctly. For now, use
// `DomRenderer` that supports Ivy namespace format.
{provide: RendererFactory2, useClass: DomRendererFactory2}
],
});
});
it('should handle namespaces inside i18n blocks', () => {
loadTranslations({
[computeMsgId(
'{$START_TAG__XHTML_DIV} Hello ' +
'{$START_TAG__XHTML_SPAN}world{$CLOSE_TAG__XHTML_SPAN}{$CLOSE_TAG__XHTML_DIV}')]:
'{$START_TAG__XHTML_DIV} Bonjour ' +
'{$START_TAG__XHTML_SPAN}monde{$CLOSE_TAG__XHTML_SPAN}{$CLOSE_TAG__XHTML_DIV}'
});
const fixture = initWithTemplate(AppComp, `
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject i18n>
<xhtml:div xmlns="http://www.w3.org/1999/xhtml">
Hello <span>world</span>
</xhtml:div>
</foreignObject>
</svg>
`);
const element = fixture.nativeElement;
expect(element.textContent.trim()).toBe('Bonjour monde');
expect(element.querySelector('svg').namespaceURI).toBe('http://www.w3.org/2000/svg');
expect(element.querySelector('div').namespaceURI).toBe('http://www.w3.org/1999/xhtml');
expect(element.querySelector('span').namespaceURI).toBe('http://www.w3.org/1999/xhtml');
});
it('should handle namespaces on i18n block containers', () => {
loadTranslations({
[computeMsgId(' Hello {$START_TAG__XHTML_SPAN}world{$CLOSE_TAG__XHTML_SPAN}')]:
' Bonjour {$START_TAG__XHTML_SPAN}monde{$CLOSE_TAG__XHTML_SPAN}'
});
const fixture = initWithTemplate(AppComp, `
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<xhtml:div xmlns="http://www.w3.org/1999/xhtml" i18n>
Hello <span>world</span>
</xhtml:div>
</foreignObject>
</svg>
`);
const element = fixture.nativeElement;
expect(element.textContent.trim()).toBe('Bonjour monde');
expect(element.querySelector('svg').namespaceURI).toBe('http://www.w3.org/2000/svg');
expect(element.querySelector('div').namespaceURI).toBe('http://www.w3.org/1999/xhtml');
expect(element.querySelector('span').namespaceURI).toBe('http://www.w3.org/1999/xhtml');
});
});
describe('should support ICU expressions', () => { describe('should support ICU expressions', () => {
it('with no root node', () => { it('with no root node', () => {
loadTranslations({ loadTranslations({