fix(ivy): correct position for re-projected containers (#24721)
PR Close #24721
This commit is contained in:
parent
3553977bd7
commit
a294e0dd79
|
@ -9,7 +9,7 @@
|
||||||
import {assertEqual, assertLessThan} from './assert';
|
import {assertEqual, assertLessThan} from './assert';
|
||||||
import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions';
|
import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions';
|
||||||
import {RENDER_PARENT} from './interfaces/container';
|
import {RENDER_PARENT} from './interfaces/container';
|
||||||
import {LContainerNode, LElementNode, LNode, TNodeType} from './interfaces/node';
|
import {LContainerNode, LElementNode, LNode, TContainerNode, TNodeType} from './interfaces/node';
|
||||||
import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view';
|
import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view';
|
||||||
import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation';
|
import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation';
|
||||||
import {stringify} from './util';
|
import {stringify} from './util';
|
||||||
|
@ -241,7 +241,8 @@ function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) {
|
||||||
appendChild(parentNode, node.native || null, viewData);
|
appendChild(parentNode, node.native || null, viewData);
|
||||||
|
|
||||||
// On first pass, re-organize node tree to put this node in the correct position.
|
// On first pass, re-organize node tree to put this node in the correct position.
|
||||||
if (node.view[TVIEW].firstTemplatePass) {
|
const firstTemplatePass = node.view[TVIEW].firstTemplatePass;
|
||||||
|
if (firstTemplatePass) {
|
||||||
node.tNode.next = null;
|
node.tNode.next = null;
|
||||||
if (previousNode === parentNode && node.tNode !== parentNode.tNode.child) {
|
if (previousNode === parentNode && node.tNode !== parentNode.tNode.child) {
|
||||||
node.tNode.next = parentNode.tNode.child;
|
node.tNode.next = parentNode.tNode.child;
|
||||||
|
@ -257,7 +258,10 @@ function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) {
|
||||||
// (node.native as RComment).textContent = 'test';
|
// (node.native as RComment).textContent = 'test';
|
||||||
// console.log(node.native);
|
// console.log(node.native);
|
||||||
appendChild(parentNode, node.dynamicLContainerNode.native || null, viewData);
|
appendChild(parentNode, node.dynamicLContainerNode.native || null, viewData);
|
||||||
node.pNextOrParent = node.dynamicLContainerNode;
|
if (firstTemplatePass) {
|
||||||
|
node.tNode.dynamicContainerNode = node.dynamicLContainerNode.tNode;
|
||||||
|
node.dynamicLContainerNode.tNode.parent = node.tNode as TContainerNode;
|
||||||
|
}
|
||||||
return node.dynamicLContainerNode;
|
return node.dynamicLContainerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1920,7 +1920,7 @@ export function projectionDef(
|
||||||
* @param appendedFirst First node of the linked list to append.
|
* @param appendedFirst First node of the linked list to append.
|
||||||
* @param appendedLast Last node of the linked list to append.
|
* @param appendedLast Last node of the linked list to append.
|
||||||
*/
|
*/
|
||||||
function appendToProjectionNode(
|
function addToProjectionList(
|
||||||
projectionNode: LProjectionNode,
|
projectionNode: LProjectionNode,
|
||||||
appendedFirst: LElementNode | LTextNode | LContainerNode | null,
|
appendedFirst: LElementNode | LTextNode | LContainerNode | null,
|
||||||
appendedLast: LElementNode | LTextNode | LContainerNode | null) {
|
appendedLast: LElementNode | LTextNode | LContainerNode | null) {
|
||||||
|
@ -1965,36 +1965,33 @@ export function projection(
|
||||||
const distributedNodes = loadInternal(localIndex, componentLView) as Array<LNode[]>;
|
const distributedNodes = loadInternal(localIndex, componentLView) as Array<LNode[]>;
|
||||||
const nodesForSelector = distributedNodes[selectorIndex];
|
const nodesForSelector = distributedNodes[selectorIndex];
|
||||||
|
|
||||||
// build the linked list of projected nodes:
|
|
||||||
for (let i = 0; i < nodesForSelector.length; i++) {
|
|
||||||
const nodeToProject = nodesForSelector[i];
|
|
||||||
if (nodeToProject.tNode.type === TNodeType.Projection) {
|
|
||||||
// Reprojecting a projection -> append the list of previously projected nodes
|
|
||||||
const previouslyProjected = (nodeToProject as LProjectionNode).data;
|
|
||||||
appendToProjectionNode(node, previouslyProjected.head, previouslyProjected.tail);
|
|
||||||
} else {
|
|
||||||
// Projecting a single node
|
|
||||||
appendToProjectionNode(
|
|
||||||
node, nodeToProject as LTextNode | LElementNode | LContainerNode,
|
|
||||||
nodeToProject as LTextNode | LElementNode | LContainerNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentParent = getParentLNode(node);
|
const currentParent = getParentLNode(node);
|
||||||
if (canInsertNativeNode(currentParent, viewData)) {
|
const canInsert = canInsertNativeNode(currentParent, viewData);
|
||||||
ngDevMode && assertNodeOfPossibleTypes(currentParent, TNodeType.Element, TNodeType.View);
|
|
||||||
// process each node in the list of projected nodes:
|
|
||||||
let nodeToProject: LNode|null = node.data.head;
|
|
||||||
const lastNodeToProject = node.data.tail;
|
|
||||||
const renderParent = currentParent.tNode.type === TNodeType.View ?
|
const renderParent = currentParent.tNode.type === TNodeType.View ?
|
||||||
(getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! :
|
(getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! :
|
||||||
currentParent as LElementNode;
|
currentParent as LElementNode;
|
||||||
|
|
||||||
while (nodeToProject) {
|
for (let i = 0; i < nodesForSelector.length; i++) {
|
||||||
|
const nodeToProject = nodesForSelector[i];
|
||||||
|
let head = nodeToProject as LTextNode | LElementNode | LContainerNode | null;
|
||||||
|
let tail = nodeToProject as LTextNode | LElementNode | LContainerNode | null;
|
||||||
|
|
||||||
|
if (nodeToProject.tNode.type === TNodeType.Projection) {
|
||||||
|
const previouslyProjected = (nodeToProject as LProjectionNode).data;
|
||||||
|
head = previouslyProjected.head;
|
||||||
|
tail = previouslyProjected.tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToProjectionList(node, head, tail);
|
||||||
|
|
||||||
|
if (canInsert) {
|
||||||
|
let currentNode: LNode|null = head;
|
||||||
|
while (currentNode) {
|
||||||
appendProjectedNode(
|
appendProjectedNode(
|
||||||
nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent, viewData,
|
currentNode as LTextNode | LElementNode | LContainerNode, currentParent, viewData,
|
||||||
renderParent);
|
renderParent);
|
||||||
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent;
|
currentNode = currentNode === tail ? null : currentNode.pNextOrParent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -618,7 +618,7 @@ export function appendProjectedNode(
|
||||||
lContainer[RENDER_PARENT] = renderParent;
|
lContainer[RENDER_PARENT] = renderParent;
|
||||||
const views = lContainer[VIEWS];
|
const views = lContainer[VIEWS];
|
||||||
for (let i = 0; i < views.length; i++) {
|
for (let i = 0; i < views.length; i++) {
|
||||||
addRemoveViewFromContainer(node as LContainerNode, views[i], true, null);
|
addRemoveViewFromContainer(node as LContainerNode, views[i], true, node.native);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.dynamicLContainerNode) {
|
if (node.dynamicLContainerNode) {
|
||||||
|
|
|
@ -45,12 +45,15 @@ describe('content projection', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should project content when root.', () => {
|
it('should project content when root.', () => {
|
||||||
|
/** <ng-content></ng-content> */
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
projectionDef(0);
|
projectionDef(0);
|
||||||
projection(1, 0);
|
projection(1, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** <child>content</child> */
|
||||||
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'child');
|
elementStart(0, 'child');
|
||||||
|
@ -64,6 +67,7 @@ describe('content projection', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should re-project content when root.', () => {
|
it('should re-project content when root.', () => {
|
||||||
|
/** <div><ng-content></ng-content></div> */
|
||||||
const GrandChild = createComponent('grand-child', function(rf: RenderFlags, ctx: any) {
|
const GrandChild = createComponent('grand-child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
projectionDef(0);
|
projectionDef(0);
|
||||||
|
@ -72,6 +76,8 @@ describe('content projection', () => {
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** <grand-child><ng-content></ng-content></grand-child> */
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
projectionDef(0);
|
projectionDef(0);
|
||||||
|
@ -80,6 +86,8 @@ describe('content projection', () => {
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
}, [GrandChild]);
|
}, [GrandChild]);
|
||||||
|
|
||||||
|
/** <child><b>Hello</b>World!</child> */
|
||||||
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'child');
|
elementStart(0, 'child');
|
||||||
|
@ -137,6 +145,59 @@ describe('content projection', () => {
|
||||||
.toEqual('<child><div><projected-comp>content</projected-comp></div></child>');
|
.toEqual('<child><div><projected-comp>content</projected-comp></div></child>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should project components that have their own projection', () => {
|
||||||
|
/** <div><ng-content></ng-content></div> */
|
||||||
|
const Child = createComponent('child', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
projectionDef(0);
|
||||||
|
elementStart(1, 'div');
|
||||||
|
{ projection(2, 0); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/** <p><ng-content></ng-content></p> */
|
||||||
|
const ProjectedComp = createComponent('projected-comp', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
projectionDef(0);
|
||||||
|
elementStart(1, 'p');
|
||||||
|
projection(2, 0);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <child>
|
||||||
|
* <projected-comp>
|
||||||
|
* <div> Some content </div>
|
||||||
|
* Other content
|
||||||
|
* </projected-comp>
|
||||||
|
* </child>
|
||||||
|
*/
|
||||||
|
const Parent = createComponent('parent', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'child');
|
||||||
|
{
|
||||||
|
elementStart(1, 'projected-comp');
|
||||||
|
{
|
||||||
|
elementStart(2, 'div');
|
||||||
|
text(3, 'Some content');
|
||||||
|
elementEnd();
|
||||||
|
text(4, 'Other content');
|
||||||
|
}
|
||||||
|
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [Child, ProjectedComp]);
|
||||||
|
|
||||||
|
const parent = renderComponent(Parent);
|
||||||
|
expect(toHtml(parent))
|
||||||
|
.toEqual(
|
||||||
|
'<child><div><projected-comp><p><div>Some content</div>Other content</p></projected-comp></div></child>');
|
||||||
|
});
|
||||||
|
|
||||||
it('should project content with container.', () => {
|
it('should project content with container.', () => {
|
||||||
/** <div> <ng-content></ng-content></div> */
|
/** <div> <ng-content></ng-content></div> */
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
|
@ -414,7 +475,89 @@ describe('content projection', () => {
|
||||||
expect(toHtml(parent)).toEqual('<child><div></div></child>');
|
expect(toHtml(parent)).toEqual('<child><div></div></child>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should project containers into embedded views', () => {
|
it('should project containers into containers', () => {
|
||||||
|
/**
|
||||||
|
* <div>
|
||||||
|
* Before (inside)
|
||||||
|
* % if (!skipContent) {
|
||||||
|
* <ng-content></ng-content>
|
||||||
|
* % }
|
||||||
|
* After (inside)
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
projectionDef(0);
|
||||||
|
elementStart(1, 'div');
|
||||||
|
{
|
||||||
|
text(2, 'Before (inside)-');
|
||||||
|
container(3);
|
||||||
|
text(4, '-After (inside)');
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
containerRefreshStart(3);
|
||||||
|
{
|
||||||
|
if (!ctx.skipContent) {
|
||||||
|
let rf0 = embeddedViewStart(0);
|
||||||
|
if (rf0 & RenderFlags.Create) {
|
||||||
|
projection(0, 0);
|
||||||
|
}
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <child>
|
||||||
|
* Before text-
|
||||||
|
* % if (!skipContent) {
|
||||||
|
* content
|
||||||
|
* % }
|
||||||
|
* -After text
|
||||||
|
* </child>
|
||||||
|
*/
|
||||||
|
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'child');
|
||||||
|
{
|
||||||
|
text(1, 'Before text-');
|
||||||
|
container(2);
|
||||||
|
text(3, '-After text');
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
containerRefreshStart(2);
|
||||||
|
{
|
||||||
|
if (!ctx.skipContent) {
|
||||||
|
let rf0 = embeddedViewStart(0);
|
||||||
|
if (rf0 & RenderFlags.Create) {
|
||||||
|
text(0, 'content');
|
||||||
|
}
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
}
|
||||||
|
}, [Child]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(Parent);
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<child><div>Before (inside)-Before text-content-After text-After (inside)</div></child>');
|
||||||
|
|
||||||
|
fixture.component.skipContent = true;
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<child><div>Before (inside)-Before text--After text-After (inside)</div></child>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should re-project containers into containers', () => {
|
||||||
/**
|
/**
|
||||||
* <div>
|
* <div>
|
||||||
* % if (!skipContent) {
|
* % if (!skipContent) {
|
||||||
|
@ -446,24 +589,31 @@ describe('content projection', () => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <child>
|
* <child>
|
||||||
|
* Before text
|
||||||
* % if (!skipContent) {
|
* % if (!skipContent) {
|
||||||
* content
|
* <ng-content></ng-content>
|
||||||
* % }
|
* % }
|
||||||
|
* -After text
|
||||||
* </child>
|
* </child>
|
||||||
*/
|
*/
|
||||||
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'child');
|
projectionDef(0);
|
||||||
{ container(1); }
|
elementStart(1, 'child');
|
||||||
|
{
|
||||||
|
text(2, 'Before text');
|
||||||
|
container(3);
|
||||||
|
text(4, '-After text');
|
||||||
|
}
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
containerRefreshStart(1);
|
containerRefreshStart(3);
|
||||||
{
|
{
|
||||||
if (!ctx.skipContent) {
|
if (!ctx.skipContent) {
|
||||||
let rf0 = embeddedViewStart(0);
|
let rf0 = embeddedViewStart(0);
|
||||||
if (rf0 & RenderFlags.Create) {
|
if (rf0 & RenderFlags.Create) {
|
||||||
text(0, 'content');
|
projection(0, 0);
|
||||||
}
|
}
|
||||||
embeddedViewEnd();
|
embeddedViewEnd();
|
||||||
}
|
}
|
||||||
|
@ -472,14 +622,33 @@ describe('content projection', () => {
|
||||||
}
|
}
|
||||||
}, [Child]);
|
}, [Child]);
|
||||||
|
|
||||||
const fixture = new ComponentFixture(Parent);
|
let parent: any;
|
||||||
expect(fixture.html).toEqual('<child><div>content</div></child>');
|
/** <parent><p>text</p></parent> */
|
||||||
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'parent');
|
||||||
|
{
|
||||||
|
elementStart(1, 'p');
|
||||||
|
{ text(2, 'text'); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
// testing
|
||||||
|
parent = loadDirective(0);
|
||||||
|
}
|
||||||
|
}, [Parent]);
|
||||||
|
|
||||||
fixture.component.skipContent = true;
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual('<parent><child><div>Before text<p>text</p>-After text</div></child></parent>');
|
||||||
|
|
||||||
|
parent.skipContent = true;
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(fixture.html).toEqual('<child><div></div></child>');
|
expect(fixture.html)
|
||||||
|
.toEqual('<parent><child><div>Before text-After text</div></child></parent>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should support projection in embedded views when ng-content is a root node of an embedded view, with other nodes after',
|
it('should support projection in embedded views when ng-content is a root node of an embedded view, with other nodes after',
|
||||||
() => {
|
() => {
|
||||||
let childCmptInstance: any;
|
let childCmptInstance: any;
|
||||||
|
@ -633,6 +802,129 @@ describe('content projection', () => {
|
||||||
expect(toHtml(parent)).toEqual('<child><div>content</div></child>');
|
expect(toHtml(parent)).toEqual('<child><div>content</div></child>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should project with multiple instances of a component with projection', () => {
|
||||||
|
const ProjectionComp = createComponent('projection-comp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
projectionDef(0);
|
||||||
|
text(1, 'Before');
|
||||||
|
projection(2, 0);
|
||||||
|
text(3, 'After');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <projection-comp>
|
||||||
|
* <div>A</div>
|
||||||
|
* <p>123</p>
|
||||||
|
* </projection-comp>
|
||||||
|
* <projection-comp>
|
||||||
|
* <div>B</div>
|
||||||
|
* <p>456</p>
|
||||||
|
* </projection-comp>
|
||||||
|
*/
|
||||||
|
const AppComp = createComponent('app-comp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'projection-comp');
|
||||||
|
elementStart(1, 'div');
|
||||||
|
text(2, 'A');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(3, 'p');
|
||||||
|
text(4, '123');
|
||||||
|
elementEnd();
|
||||||
|
elementEnd();
|
||||||
|
elementStart(5, 'projection-comp');
|
||||||
|
elementStart(6, 'div');
|
||||||
|
text(7, 'B');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(8, 'p');
|
||||||
|
text(9, '456');
|
||||||
|
elementEnd();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [ProjectionComp]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComp);
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<projection-comp>Before<div>A</div><p>123</p>After</projection-comp><projection-comp>Before<div>B</div><p>456</p>After</projection-comp>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should re-project with multiple instances of a component with projection', () => {
|
||||||
|
/**
|
||||||
|
* Before
|
||||||
|
* <ng-content></ng-content>
|
||||||
|
* After
|
||||||
|
*/
|
||||||
|
const ProjectionComp = createComponent('projection-comp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
projectionDef(0);
|
||||||
|
text(1, 'Before');
|
||||||
|
projection(2, 0);
|
||||||
|
text(3, 'After');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <projection-comp>
|
||||||
|
* <div>A</div>
|
||||||
|
* <ng-content></ng-content>
|
||||||
|
* <p>123</p>
|
||||||
|
* </projection-comp>
|
||||||
|
* <projection-comp>
|
||||||
|
* <div>B</div>
|
||||||
|
* <p>456</p>
|
||||||
|
* </projection-comp>
|
||||||
|
*/
|
||||||
|
const ProjectionParent = createComponent('parent-comp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
projectionDef(0);
|
||||||
|
elementStart(1, 'projection-comp');
|
||||||
|
elementStart(2, 'div');
|
||||||
|
text(3, 'A');
|
||||||
|
elementEnd();
|
||||||
|
projection(4, 0);
|
||||||
|
elementStart(5, 'p');
|
||||||
|
text(6, '123');
|
||||||
|
elementEnd();
|
||||||
|
elementEnd();
|
||||||
|
elementStart(7, 'projection-comp');
|
||||||
|
elementStart(8, 'div');
|
||||||
|
text(9, 'B');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(10, 'p');
|
||||||
|
text(11, '456');
|
||||||
|
elementEnd();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [ProjectionComp]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <parent-comp>
|
||||||
|
* **ABC**
|
||||||
|
* </parent-comp>
|
||||||
|
* <parent-comp>
|
||||||
|
* **DEF**
|
||||||
|
* </parent-comp>
|
||||||
|
*/
|
||||||
|
const AppComp = createComponent('app-comp', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'parent-comp');
|
||||||
|
text(1, '**ABC**');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(2, 'parent-comp');
|
||||||
|
text(3, '**DEF**');
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, [ProjectionParent]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComp);
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.html)
|
||||||
|
.toEqual(
|
||||||
|
'<parent-comp><projection-comp>Before<div>A</div>**ABC**<p>123</p>After</projection-comp><projection-comp>Before<div>B</div><p>456</p>After</projection-comp></parent-comp><parent-comp><projection-comp>Before<div>A</div>**DEF**<p>123</p>After</projection-comp><projection-comp>Before<div>B</div><p>456</p>After</projection-comp></parent-comp>');
|
||||||
|
});
|
||||||
|
|
||||||
describe('with selectors', () => {
|
describe('with selectors', () => {
|
||||||
|
|
||||||
it('should project nodes using attribute selectors', () => {
|
it('should project nodes using attribute selectors', () => {
|
||||||
|
|
|
@ -727,9 +727,12 @@ describe('ViewContainerRef', () => {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'child-with-view',
|
selector: 'child-with-view',
|
||||||
template: `
|
template: `
|
||||||
|
Before (inside)-
|
||||||
% if (show) {
|
% if (show) {
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
% }`
|
% }
|
||||||
|
After (inside)
|
||||||
|
`
|
||||||
})
|
})
|
||||||
class ChildWithView {
|
class ChildWithView {
|
||||||
show: boolean = true;
|
show: boolean = true;
|
||||||
|
@ -740,10 +743,12 @@ describe('ViewContainerRef', () => {
|
||||||
template: (rf: RenderFlags, cmp: ChildWithView) => {
|
template: (rf: RenderFlags, cmp: ChildWithView) => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
projectionDef(0);
|
projectionDef(0);
|
||||||
container(1);
|
text(1, 'Before (inside)-');
|
||||||
|
container(2);
|
||||||
|
text(3, 'After (inside)');
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
containerRefreshStart(1);
|
containerRefreshStart(2);
|
||||||
if (cmp.show) {
|
if (cmp.show) {
|
||||||
let rf0 = embeddedViewStart(0);
|
let rf0 = embeddedViewStart(0);
|
||||||
if (rf0 & RenderFlags.Create) {
|
if (rf0 & RenderFlags.Create) {
|
||||||
|
@ -764,7 +769,9 @@ describe('ViewContainerRef', () => {
|
||||||
<span>{{name}}</span>
|
<span>{{name}}</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<child-with-view>
|
<child-with-view>
|
||||||
|
Before projected
|
||||||
<header vcref [tplRef]="foo" [name]="name">blah</header>
|
<header vcref [tplRef]="foo" [name]="name">blah</header>
|
||||||
|
After projected
|
||||||
</child-with-view>`
|
</child-with-view>`
|
||||||
})
|
})
|
||||||
class Parent {
|
class Parent {
|
||||||
|
@ -777,15 +784,17 @@ describe('ViewContainerRef', () => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
container(0, embeddedTemplate);
|
container(0, embeddedTemplate);
|
||||||
elementStart(1, 'child-with-view');
|
elementStart(1, 'child-with-view');
|
||||||
elementStart(2, 'header', ['vcref', '']);
|
text(2, 'Before projected');
|
||||||
text(3, 'blah');
|
elementStart(3, 'header', ['vcref', '']);
|
||||||
|
text(4, 'blah');
|
||||||
elementEnd();
|
elementEnd();
|
||||||
|
text(5, 'After projected-');
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
||||||
elementProperty(2, 'tplRef', bind(tplRef));
|
elementProperty(3, 'tplRef', bind(tplRef));
|
||||||
elementProperty(2, 'name', bind(cmp.name));
|
elementProperty(3, 'name', bind(cmp.name));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: [ChildWithView, DirectiveWithVCRef]
|
directives: [ChildWithView, DirectiveWithVCRef]
|
||||||
|
@ -794,13 +803,14 @@ describe('ViewContainerRef', () => {
|
||||||
|
|
||||||
const fixture = new ComponentFixture(Parent);
|
const fixture = new ComponentFixture(Parent);
|
||||||
expect(fixture.html)
|
expect(fixture.html)
|
||||||
.toEqual('<child-with-view><header vcref="">blah</header></child-with-view>');
|
.toEqual(
|
||||||
|
'<child-with-view>Before (inside)-Before projected<header vcref="">blah</header>After projected-After (inside)</child-with-view>');
|
||||||
|
|
||||||
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
|
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(fixture.html)
|
expect(fixture.html)
|
||||||
.toEqual(
|
.toEqual(
|
||||||
'<child-with-view><header vcref="">blah</header><span>bar</span></child-with-view>');
|
'<child-with-view>Before (inside)-Before projected<header vcref="">blah</header><span>bar</span>After projected-After (inside)</child-with-view>');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with select', () => {
|
describe('with select', () => {
|
||||||
|
|
Loading…
Reference in New Issue