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:
parent
7acd33007d
commit
7a30153aa1
|
@ -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});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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({
|
||||||
|
|
Loading…
Reference in New Issue