fix(ivy): reprojected ICU expression nodes when creating embedded views (#30979)

When using `createEmbeddedView` after the creation of an ICU expression, the nodes for the current selected case were not reprojected (only the anchor comment node was moved to the new location).
Now we reproject correctly all the child nodes of an ICU expression when an anchor comment node is projected.

FW-1372 #resolve

PR Close #30979
This commit is contained in:
Olivier Combe 2019-06-11 19:01:49 +02:00 committed by Andrew Kushnir
parent 57c4788bc7
commit 65544ac742
2 changed files with 135 additions and 6 deletions

View File

@ -7,7 +7,6 @@
*/
import {ViewEncapsulation} from '../metadata/view';
import {assertLContainer, assertLView} from './assert';
import {attachPatchData} from './context_discovery';
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
@ -801,6 +800,23 @@ export function appendProjectedNodes(
}
}
/**
* Loops over all children of a TNode container and appends them to the DOM
*
* @param ngContainerChildTNode The first child of the TNode container
* @param tProjectionNode The projection (ng-content) TNode
* @param currentView Current LView
* @param projectionView Projection view (view above current)
*/
function appendProjectedChildren(
ngContainerChildTNode: TNode | null, tProjectionNode: TNode, currentView: LView,
projectionView: LView) {
while (ngContainerChildTNode) {
appendProjectedNode(ngContainerChildTNode, tProjectionNode, currentView, projectionView);
ngContainerChildTNode = ngContainerChildTNode.next;
}
}
/**
* Appends a projected node to the DOM, or in the case of a projected container,
* appends the nodes from all of the container's active views to the DOM.
@ -831,13 +847,15 @@ function appendProjectedNode(
for (let i = CONTAINER_HEADER_OFFSET; i < nodeOrContainer.length; i++) {
addRemoveViewFromContainer(nodeOrContainer[i], true, nodeOrContainer[NATIVE]);
}
} else if (projectedTNode.type === TNodeType.IcuContainer) {
// The node we are adding is an ICU container which is why we also need to project all the
// children nodes that might have been created previously and are linked to this anchor
let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode;
appendProjectedChildren(
ngContainerChildTNode, ngContainerChildTNode, projectionView, projectionView);
} else {
if (projectedTNode.type === TNodeType.ElementContainer) {
let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode;
while (ngContainerChildTNode) {
appendProjectedNode(ngContainerChildTNode, tProjectionNode, currentView, projectionView);
ngContainerChildTNode = ngContainerChildTNode.next;
}
appendProjectedChildren(projectedTNode.child, tProjectionNode, currentView, projectionView);
}
if (isLContainer(nodeOrContainer)) {

View File

@ -661,6 +661,117 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
const element = fixture.nativeElement;
expect(element).toHaveText('other');
});
it('inside a container when creating a view via vcr.createEmbeddedView', () => {
@Directive({
selector: '[someDir]',
})
class Dir {
constructor(
private readonly viewContainerRef: ViewContainerRef,
private readonly templateRef: TemplateRef<any>) {}
ngOnInit() { this.viewContainerRef.createEmbeddedView(this.templateRef); }
}
@Component({
selector: 'my-cmp',
template: `
<div *someDir>
<ng-content></ng-content>
</div>
`,
})
class Cmp {
}
@Component({
selector: 'my-app',
template: `
<my-cmp i18n="test">{
count,
plural,
=1 {ONE}
other {OTHER}
}</my-cmp>
`,
})
class App {
count = 1;
}
TestBed.configureTestingModule({
declarations: [App, Cmp, Dir],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe('<my-cmp><div>ONE<!--ICU 13--></div><!--container--></my-cmp>');
fixture.componentRef.instance.count = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe('<my-cmp><div>OTHER<!--ICU 13--></div><!--container--></my-cmp>');
});
it('with nested ICU expression and inside a container when creating a view via vcr.createEmbeddedView',
() => {
@Directive({
selector: '[someDir]',
})
class Dir {
constructor(
private readonly viewContainerRef: ViewContainerRef,
private readonly templateRef: TemplateRef<any>) {}
ngOnInit() { this.viewContainerRef.createEmbeddedView(this.templateRef); }
}
@Component({
selector: 'my-cmp',
template: `
<div *someDir>
<ng-content></ng-content>
</div>
`,
})
class Cmp {
}
@Component({
selector: 'my-app',
template: `
<my-cmp i18n="test">{
count,
plural,
=1 {ONE}
other {{{count}} {name, select,
cat {cats}
dog {dogs}
other {animals}
}!}
}</my-cmp>
`,
})
class App {
count = 1;
}
TestBed.configureTestingModule({
declarations: [App, Cmp, Dir],
});
const fixture = TestBed.createComponent(App);
fixture.componentRef.instance.count = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe(
'<my-cmp><div>2 animals<!--nested ICU 0-->!<!--ICU 15--></div><!--container--></my-cmp>');
fixture.componentRef.instance.count = 1;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe('<my-cmp><div>ONE<!--ICU 15--></div><!--container--></my-cmp>');
});
});
describe('should support attributes', () => {