From f00bb85b58315e8970345f9b46ab312d457cc6d1 Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Fri, 16 Aug 2019 15:46:06 +0200 Subject: [PATCH] refactor(ivy): migrate content spec from render3 (#32474) Migrate the remaining `render3/content_spec.ts` to `acceptance` (some JS block ones were already migrated with `ngIf`). PR Close #32474 --- packages/core/test/acceptance/content_spec.ts | 397 +++++ packages/core/test/render3/content_spec.ts | 1345 ----------------- 2 files changed, 397 insertions(+), 1345 deletions(-) delete mode 100644 packages/core/test/render3/content_spec.ts diff --git a/packages/core/test/acceptance/content_spec.ts b/packages/core/test/acceptance/content_spec.ts index d0915c9538..24ca05122f 100644 --- a/packages/core/test/acceptance/content_spec.ts +++ b/packages/core/test/acceptance/content_spec.ts @@ -155,6 +155,84 @@ describe('projection', () => { `

Some content
Other content

`); }); + it('should project with multiple instances of a component with projection', () => { + @Component({selector: 'child', template: `
`}) + class Child { + } + + @Component({selector: 'projected-comp', template: `BeforeAfter`}) + class ProjectedComp { + } + + @Component({ + selector: 'parent', + template: ` + +
A

123

+
B

456

+
`, + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Parent, Child, ProjectedComp]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(fixture.nativeElement.innerHTML) + .toBe( + '
' + + 'Before
A

123

After
' + + 'Before
B

456

After
' + + '
'); + }); + + it('should re-project with multiple instances of a component with projection', () => { + @Component({selector: 'child', template: `
`}) + class Child { + } + + @Component({selector: 'projected-comp', template: `BeforeAfter`}) + class ProjectedComp { + } + + @Component({ + selector: 'parent', + template: ` + +
A

123

+
B

456

+
`, + }) + class Parent { + } + + @Component({ + selector: 'app', + template: ` + **ABC** + **DEF** + `, + }) + class App { + } + + TestBed.configureTestingModule({declarations: [App, Parent, Child, ProjectedComp]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.innerHTML) + .toBe( + '
' + + 'Before
A
**ABC**

123

After
' + + 'Before
B

456

After
' + + '
' + + '
' + + 'Before
A
**DEF**

123

After
' + + 'Before
B

456

After
' + + '
'); + }); + it('should project into dynamic views (with createEmbeddedView)', () => { @Component({ selector: 'child', @@ -280,6 +358,27 @@ describe('projection', () => { .toBe(`Some content`); }); + it('should project nodes into the last ng-content', () => { + @Component({ + selector: 'child', + template: `
+ ` + }) + class Child { + } + + @Component({selector: 'parent', template: `content`}) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Parent, Child], imports: [CommonModule]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toBe('
content
'); + }); + // https://stackblitz.com/edit/angular-ceqmnw?file=src%2Fapp%2Fapp.component.ts it('should project nodes into the last ng-content unrolled by ngFor', () => { @Component({ @@ -408,6 +507,64 @@ describe('projection', () => { expect(fixture.nativeElement.querySelectorAll('div').length).toBe(0); }); + it('should project ng-container at the content root', () => { + @Component({selector: 'child', template: ``}) + class Child { + } + + @Component({ + selector: 'parent', + template: ` + + content + + + ` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Parent, Child]}); + const fixture = TestBed.createComponent(Parent); + + fixture.detectChanges(); + expect(getElementHtml(fixture.nativeElement)).toBe('content'); + }); + + it('should re-project ng-container at the content root', () => { + @Component({selector: 'grand-child', template: ``}) + class GrandChild { + } + + @Component({ + selector: 'child', + template: ` + + ` + }) + class Child { + } + + @Component({ + selector: 'parent', + template: ` + + content + + + ` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]}); + const fixture = TestBed.createComponent(Parent); + + fixture.detectChanges(); + expect(getElementHtml(fixture.nativeElement)) + .toBe('content'); + }); + it('should handle re-projection at the root of an embedded view', () => { @Component({ selector: 'child-comp', @@ -442,6 +599,246 @@ describe('projection', () => { }); describe('with selectors', () => { + it('should project nodes using attribute selectors', () => { + @Component({ + selector: 'child', + template: `
+
`, + }) + class Child { + } + + @Component({ + selector: 'parent', + template: `12` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + '
1
2
'); + }); + + it('should project nodes using class selectors', () => { + @Component({ + selector: 'child', + template: `
+
`, + }) + class Child { + } + + @Component({ + selector: 'parent', + template: `12` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + '
1
2
'); + }); + + it('should project nodes using class selectors when element has multiple classes', () => { + @Component({ + selector: 'child', + template: `
+
` + }) + class Child { + } + + @Component({ + selector: 'parent', + template: + `12` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + '
1
2
'); + }); + + it('should project nodes into the first matching selector', () => { + @Component({ + selector: 'child', + template: `
+
` + }) + class Child { + } + + @Component({ + selector: 'parent', + template: `12` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + '
12
'); + }); + + it('should allow mixing ng-content with and without selectors', () => { + @Component({ + selector: 'child', + template: `
+
` + }) + class Child { + } + + @Component({ + selector: 'parent', + template: + `1remainingmore remaining` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + '
1
remainingmore remaining
'); + }); + + it('should allow mixing ng-content with and without selectors - ng-content first', () => { + @Component({ + selector: 'child', + template: `
+
` + }) + class Child { + } + + @Component({ + selector: 'parent', + template: `12remaining` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + '
1remaining
2
'); + }); + + /** + * Descending into projected content for selector-matching purposes is not supported + * today: http://plnkr.co/edit/MYQcNfHSTKp9KvbzJWVQ?p=preview + */ + it('should not descend into re-projected content', () => { + @Component({ + selector: 'grand-child', + template: `
` + }) + class GrandChild { + } + + @Component({ + selector: 'child', + template: ` + + in child template + ` + }) + class Child { + } + + @Component({selector: 'parent', template: `parent content`}) + class Parent { + } + + TestBed.configureTestingModule({declarations: [GrandChild, Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + 'in child template
parent content
'); + }); + + it('should not descend into re-projected content', () => { + @Component({ + selector: 'card', + template: + `
` + }) + class Card { + } + + @Component({ + selector: 'card-with-title', + template: ` +

Title

+ +
` + }) + class CardWithTitle { + } + + @Component({selector: 'parent', template: `content`}) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Card, CardWithTitle, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual( + '

Title


content
'); + }); + + it('should not match selectors against node having ngProjectAs attribute', () => { + @Component({selector: 'child', template: ``}) + class Child { + } + + @Component({ + selector: 'parent', + template: + `
should not project
should project
` + }) + class Parent { + } + + TestBed.configureTestingModule({declarations: [Child, Parent]}); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(getElementHtml(fixture.nativeElement)) + .toEqual('
should project
'); + }); + // https://stackblitz.com/edit/angular-psokum?file=src%2Fapp%2Fapp.module.ts it('should project nodes where attribute selector matches a binding', () => { @Component({ diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts deleted file mode 100644 index c70a83e6f0..0000000000 --- a/packages/core/test/render3/content_spec.ts +++ /dev/null @@ -1,1345 +0,0 @@ -/** - * @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 {SelectorFlags} from '@angular/core/src/render3/interfaces/projection'; - -import {AttributeMarker, detectChanges} from '../../src/render3/index'; -import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtext} from '../../src/render3/instructions/all'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; -import {ComponentFixture, createComponent, getDirectiveOnNode, renderComponent, toHtml} from './render_util'; - -describe('content projection', () => { - it('should project containers', () => { - /**
*/ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵprojection(1); } - ɵɵelementEnd(); - } - }, 2); - - /** - * - * ( - * % if (value) { - * content - * % } - * ) - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵtext(1, '('); - ɵɵcontainer(2); - ɵɵtext(3, ')'); - } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - if (ctx.value) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵtext(0, 'content'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 4, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
()
'); - parent.value = true; - detectChanges(parent); - - expect(toHtml(parent)).toEqual('
(content)
'); - parent.value = false; - detectChanges(parent); - - expect(toHtml(parent)).toEqual('
()
'); - }); - - it('should project containers into root', () => { - /** */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵprojection(0); - } - }, 1); - - /** - * - * % if (value) { - * content - * % } - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (ctx.value) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵtext(0, 'content'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual(''); - - parent.value = true; - detectChanges(parent); - expect(toHtml(parent)).toEqual('content'); - - parent.value = false; - detectChanges(parent); - expect(toHtml(parent)).toEqual(''); - }); - - it('should project containers with if-else.', () => { - /**
*/ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵprojection(1); } - ɵɵelementEnd(); - } - }, 2); - - /** - * - * ( - * % if (value) { - * content - * % } else { - * else - * % } - * ) - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: {value: any}) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵtext(1, '('); - ɵɵcontainer(2); - ɵɵtext(3, ')'); - } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - if (ctx.value) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵtext(0, 'content'); - } - ɵɵembeddedViewEnd(); - } else { - if (ɵɵembeddedViewStart(1, 1, 0)) { - ɵɵtext(0, 'else'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 4, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
(else)
'); - parent.value = true; - detectChanges(parent); - expect(toHtml(parent)).toEqual('
(content)
'); - parent.value = false; - detectChanges(parent); - expect(toHtml(parent)).toEqual('
(else)
'); - }); - - it('should support projection into embedded views', () => { - let childCmptInstance: any; - - /** - *
- * % if (!skipContent) { - * - * - * - * % } - *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (!ctx.skipContent) { - let rf0 = ɵɵembeddedViewStart(0, 2, 0); - if (rf0 & RenderFlags.Create) { - ɵɵelementStart(0, 'span'); - ɵɵprojection(1); - ɵɵelementEnd(); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2, 0); - - /** - * - *
text
- * content - *
- */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'div'); - { ɵɵtext(2, 'text'); } - ɵɵelementEnd(); - ɵɵtext(3, 'content'); - } - ɵɵelementEnd(); - - // testing - childCmptInstance = getDirectiveOnNode(0); - } - }, 4, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
text
content
'); - - childCmptInstance.skipContent = true; - detectChanges(parent); - expect(toHtml(parent)).toEqual('
'); - }); - - it('should support projection into embedded views when no projected nodes', () => { - let childCmptInstance: any; - - /** - *
- * % if (!skipContent) { - * - * text - * % } - *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (!ctx.skipContent) { - let rf0 = ɵɵembeddedViewStart(0, 2, 0); - if (rf0 & RenderFlags.Create) { - ɵɵprojection(0); - ɵɵtext(1, 'text'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - /** */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'child'); - - // testing - childCmptInstance = getDirectiveOnNode(0); - } - }, 1, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
text
'); - - childCmptInstance.skipContent = true; - detectChanges(parent); - expect(toHtml(parent)).toEqual('
'); - }); - - it('should support projection into embedded views when ng-content is a root node of an embedded view', - () => { - let childCmptInstance: any; - - /** - *
- * % if (!skipContent) { - * - * % } - *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (!ctx.skipContent) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵprojection(0); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - /** - * content - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - childCmptInstance = getDirectiveOnNode(0); - ɵɵtext(1, 'content'); - } - ɵɵelementEnd(); - } - }, 2, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
content
'); - - childCmptInstance.skipContent = true; - detectChanges(parent); - expect(toHtml(parent)).toEqual('
'); - }); - - it('should project containers into containers', () => { - /** - *
- * Before (inside) - * % if (!skipContent) { - * - * % } - * After (inside) - *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { - ɵɵtext(1, 'Before (inside)-'); - ɵɵcontainer(2); - ɵɵtext(3, '-After (inside)'); - } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - if (!ctx.skipContent) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵprojection(0); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 4); - - /** - * - * Before text- - * % if (!skipContent) { - * content - * % } - * -After text - * - */ - 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, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵtext(0, 'content'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 4, 0, [Child]); - - const fixture = new ComponentFixture(Parent); - expect(fixture.html) - .toEqual( - '
Before (inside)-Before text-content-After text-After (inside)
'); - - fixture.component.skipContent = true; - fixture.update(); - expect(fixture.html) - .toEqual( - '
Before (inside)-Before text--After text-After (inside)
'); - }); - - it('should re-project containers into containers', () => { - /** - *
- * % if (!skipContent) { - * - * % } - *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (!ctx.skipContent) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵprojection(0); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - /** - * - * Before text - * % if (!skipContent) { - * - * % } - * -After text - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵ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, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵprojection(0); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 4, 0, [Child]); - - let parent: any; - /**

text

*/ - 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 = getDirectiveOnNode(0); - } - }, 3, 0, [Parent]); - - const fixture = new ComponentFixture(App); - expect(fixture.html) - .toEqual('
Before text

text

-After text
'); - - parent.skipContent = true; - fixture.update(); - expect(fixture.html) - .toEqual('
Before text-After text
'); - }); - - it('should support projection into embedded views when ng-content is a root node of an embedded view, with other nodes after', - () => { - let childCmptInstance: any; - - /** - *
- * % if (!skipContent) { - * before--after - * % } - *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵcontainer(1); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(1); - { - if (!ctx.skipContent) { - let rf0 = ɵɵembeddedViewStart(0, 3, 0); - if (rf0 & RenderFlags.Create) { - ɵɵtext(0, 'before-'); - ɵɵprojection(1); - ɵɵtext(2, '-after'); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 2); - - /** - * content - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - childCmptInstance = getDirectiveOnNode(0); - ɵɵtext(1, 'content'); - } - ɵɵelementEnd(); - } - }, 2, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
before-content-after
'); - - childCmptInstance.skipContent = true; - detectChanges(parent); - expect(toHtml(parent)).toEqual('
'); - }); - - it('should project nodes into the last ng-content', () => { - /** - *
- * - */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'div'); - { ɵɵprojection(1); } - ɵɵelementEnd(); - ɵɵelementStart(2, 'span'); - { ɵɵprojection(3); } - ɵɵelementEnd(); - } - }, 4); - - /** - * content - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { ɵɵtext(1, 'content'); } - ɵɵelementEnd(); - } - }, 2, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
content
'); - }); - - /** - * Warning: this test is _not_ in-line with what Angular does atm. - * Moreover the current implementation logic will result in DOM nodes - * being re-assigned from one parent to another. Proposal: have compiler - * to remove all but the latest occurrence of so we generate - * only one P(n, m, 0) instruction. It would make it consistent with the - * current Angular behavior: - * http://plnkr.co/edit/OAYkNawTDPkYBFTqovTP?p=preview - */ - it('should project nodes into the last available ng-content', () => { - let childCmptInstance: any; - /** - * - *
- * % if (show) { - * - * % } - *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵprojection(0); - ɵɵelementStart(1, 'div'); - { ɵɵcontainer(2); } - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵcontainerRefreshStart(2); - { - if (ctx.show) { - let rf0 = ɵɵembeddedViewStart(0, 1, 0); - if (rf0 & RenderFlags.Create) { - ɵɵprojection(0); - } - ɵɵembeddedViewEnd(); - } - } - ɵɵcontainerRefreshEnd(); - } - }, 3); - - /** - * content - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - childCmptInstance = getDirectiveOnNode(0); - ɵɵtext(1, 'content'); - } - ɵɵelementEnd(); - } - }, 2, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('content
'); - - childCmptInstance.show = true; - detectChanges(parent); - expect(toHtml(parent)).toEqual('
content
'); - }); - - 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(); - ɵɵtext(0, 'Before'); - ɵɵprojection(1); - ɵɵtext(2, 'After'); - } - }, 3); - - /** - * - *
A
- *

123

- *
- * - *
B
- *

456

- *
- */ - 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(); - } - }, 10, 0, [ProjectionComp]); - - const fixture = new ComponentFixture(AppComp); - fixture.update(); - expect(fixture.html) - .toEqual( - 'Before
A

123

After
' + - 'Before
B

456

After
'); - }); - - it('should re-project with multiple instances of a component with projection', () => { - /** - * Before - * - * After - */ - const ProjectionComp = createComponent('projection-comp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵtext(0, 'Before'); - ɵɵprojection(1); - ɵɵtext(2, 'After'); - } - }, 3); - - /** - * - *
A
- * - *

123

- *
- * - *
B
- *

456

- *
- */ - const ProjectionParent = createComponent('parent-comp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'projection-comp'); - { - ɵɵelementStart(1, 'div'); - { ɵɵtext(2, 'A'); } - ɵɵelementEnd(); - ɵɵprojection(3, 0); - ɵɵelementStart(4, 'p'); - { ɵɵtext(5, '123'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - ɵɵelementStart(6, 'projection-comp'); - { - ɵɵelementStart(7, 'div'); - { ɵɵtext(8, 'B'); } - ɵɵelementEnd(); - ɵɵelementStart(9, 'p'); - { ɵɵtext(10, '456'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 11, 0, [ProjectionComp]); - - /** - * - * **ABC** - * - * - * **DEF** - * - */ - 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(); - } - }, 4, 0, [ProjectionParent]); - - const fixture = new ComponentFixture(AppComp); - fixture.update(); - expect(fixture.html) - .toEqual( - '' + - 'Before
A
**ABC**

123

After
' + - 'Before
B

456

After
' + - '' + - 'Before
A
**DEF**

123

After
' + - 'Before
B

456

After
'); - }); - - it('should project ng-container at the content root', () => { - - ``; - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵprojection(0); - } - }, 1); - - ` - - - content - - - `; - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementContainerStart(1); - { - ɵɵelementContainerStart(2); - { ɵɵtext(3, 'content'); } - ɵɵelementContainerEnd(); - } - ɵɵelementContainerEnd(); - } - ɵɵelementEnd(); - } - }, 4, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('content'); - }); - - it('should re-project ng-container at the content root', () => { - - ``; - const GrandChild = createComponent('grand-child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵprojection(0); - } - }, 1); - - ` - - `; - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'grand-child'); - { ɵɵprojection(1); } - ɵɵelementEnd(); - } - }, 2, 0, [GrandChild]); - - ` - - - content - - - `; - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementContainerStart(1); - { - ɵɵelementContainerStart(2); - { ɵɵtext(3, 'content'); } - ɵɵelementContainerEnd(); - } - ɵɵelementContainerEnd(); - } - ɵɵelementEnd(); - } - }, 4, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('content'); - }); - - describe('with selectors', () => { - - it('should project nodes using attribute selectors', () => { - /** - *
- *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - // selectors are in lowercase once compiled - ɵɵprojectionDef(['*', [['span', 'title', 'tofirst']], [['span', 'title', 'tosecond']]]); - ɵɵelementStart(0, 'div', ['id', 'first']); - { ɵɵprojection(1, 1); } - ɵɵelementEnd(); - ɵɵelementStart(2, 'div', ['id', 'second']); - { ɵɵprojection(3, 2); } - ɵɵelementEnd(); - } - }, 4); - - /** - * - * 1 - * 2 - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'span', ['title', 'toFirst']); - { ɵɵtext(2, '1'); } - ɵɵelementEnd(); - ɵɵelementStart(3, 'span', ['title', 'toSecond']); - { ɵɵtext(4, '2'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 5, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)) - .toEqual( - '
1
2
'); - }); - - it('should project nodes using class selectors', () => { - /** - *
- *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - // selectors are in lowercase once compiled - ɵɵprojectionDef([ - '*', [['span', SelectorFlags.CLASS, 'tofirst']], - [['span', SelectorFlags.CLASS, 'tosecond']] - ]); - ɵɵelementStart(0, 'div', ['id', 'first']); - { ɵɵprojection(1, 1); } - ɵɵelementEnd(); - ɵɵelementStart(2, 'div', ['id', 'second']); - { ɵɵprojection(3, 2); } - ɵɵelementEnd(); - } - }, 4); - - /** - * - * 1 - * 2 - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'span', ['class', 'toFirst']); - { ɵɵtext(2, '1'); } - ɵɵelementEnd(); - ɵɵelementStart(3, 'span', ['class', 'toSecond']); - { ɵɵtext(4, '2'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 5, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)) - .toEqual( - '
1
2
'); - }); - - it('should project nodes using class selectors when element has multiple classes', () => { - /** - *
- *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - // selectors are in lowercase once compiled - ɵɵprojectionDef([ - '*', [['span', SelectorFlags.CLASS, 'tofirst']], - [['span', SelectorFlags.CLASS, 'tosecond']] - ]); - ɵɵelementStart(0, 'div', ['id', 'first']); - { ɵɵprojection(1, 1); } - ɵɵelementEnd(); - ɵɵelementStart(2, 'div', ['id', 'second']); - { ɵɵprojection(3, 2); } - ɵɵelementEnd(); - } - }, 4); - - /** - * - * 1 - * 2 - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'span', ['class', 'other toFirst']); - { ɵɵtext(2, '1'); } - ɵɵelementEnd(); - ɵɵelementStart(3, 'span', ['class', 'toSecond noise']); - { ɵɵtext(4, '2'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 5, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)) - .toEqual( - '
1
2
'); - }); - - it('should project nodes into the first matching selector', () => { - /** - *
- *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(['*', [['span']], [['span', SelectorFlags.CLASS, 'toSecond']]]); - ɵɵelementStart(0, 'div', ['id', 'first']); - { ɵɵprojection(1, 1); } - ɵɵelementEnd(); - ɵɵelementStart(2, 'div', ['id', 'second']); - { ɵɵprojection(3, 2); } - ɵɵelementEnd(); - } - }, 4); - - /** - * - * 1 - * 2 - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'span', ['class', 'toFirst']); - { ɵɵtext(2, '1'); } - ɵɵelementEnd(); - ɵɵelementStart(3, 'span', ['class', 'toSecond']); - { ɵɵtext(4, '2'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 5, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)) - .toEqual( - '
12
'); - }); - - it('should allow mixing ng-content with and without selectors', () => { - /** - *
- *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - // selectors are in lowercase once compiled - ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'tofirst']]]); - ɵɵelementStart(0, 'div', ['id', 'first']); - { ɵɵprojection(1, 1); } - ɵɵelementEnd(); - ɵɵelementStart(2, 'div', ['id', 'second']); - { ɵɵprojection(3); } - ɵɵelementEnd(); - } - }, 4); - - /** - * - * 1 - * 2 - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'span', ['class', 'toFirst']); - { ɵɵtext(2, '1'); } - ɵɵelementEnd(); - ɵɵelementStart(3, 'span'); - { ɵɵtext(4, 'remaining'); } - ɵɵelementEnd(); - ɵɵtext(5, 'more remaining'); - } - ɵɵelementEnd(); - } - }, 6, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)) - .toEqual( - '
1
remainingmore remaining
'); - }); - - it('should allow mixing ng-content with and without selectors - ng-content first', () => { - /** - *
- *
- */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - // selectors are in lowercase once compiled - ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'tosecond']]]); - ɵɵelementStart(0, 'div', ['id', 'first']); - { ɵɵprojection(1); } - ɵɵelementEnd(); - ɵɵelementStart(2, 'div', ['id', 'second']); - { ɵɵprojection(3, 1); } - ɵɵelementEnd(); - } - }, 4); - - /** - * - * 1 - * 2 - * remaining - * - */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'span'); - { ɵɵtext(2, '1'); } - ɵɵelementEnd(); - ɵɵelementStart(3, 'span', ['class', 'toSecond']); - { ɵɵtext(4, '2'); } - ɵɵelementEnd(); - ɵɵtext(5, 'remaining'); - } - ɵɵelementEnd(); - } - }, 6, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)) - .toEqual( - '
1remaining
2
'); - }); - - /** - * Descending into projected content for selector-matching purposes is not supported - * today: http://plnkr.co/edit/MYQcNfHSTKp9KvbzJWVQ?p=preview - */ - it('should not descend into re-projected content', () => { - - /** - * - *
- * - */ - const GrandChild = createComponent('grand-child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(['*', [['span']]]); - ɵɵprojection(0, 1); - ɵɵelement(1, 'hr'); - ɵɵprojection(2); - } - }, 3); - - /** - * - * - * in child template - * - */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'grand-child'); - { - ɵɵprojection(1); - ɵɵelementStart(2, 'span'); - { ɵɵtext(3, 'in child template'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 4, 0, [GrandChild]); - - /** - * - *
- * parent content - *
- *
- */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'span'); - { ɵɵtext(2, 'parent content'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 3, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)) - .toEqual( - 'in child template
parent content
'); - }); - - it('should match selectors on ng-content nodes with attributes', () => { - - /** - * - *
- * - */ - const Card = createComponent('card', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(['*', [['', 'card-title', '']], [['', 'card-content', '']]]); - ɵɵprojection(0, 1); - ɵɵelement(1, 'hr'); - ɵɵprojection(2, 2); - } - }, 3); - - /** - * - *

Title

- * - *
- */ - const CardWithTitle = createComponent('card-with-title', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(); - ɵɵelementStart(0, 'card'); - { - ɵɵelementStart(1, 'h1', ['card-title', '']); - { ɵɵtext(2, 'Title'); } - ɵɵelementEnd(); - ɵɵprojection(3, 0, ['card-content', '']); - } - ɵɵelementEnd(); - } - }, 4, 0, [Card]); - - /** - * - * content - * - */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'card-with-title'); - { ɵɵtext(1, 'content'); } - ɵɵelementEnd(); - } - }, 2, 0, [CardWithTitle]); - - const app = renderComponent(App); - expect(toHtml(app)) - .toEqual( - '

Title


content
'); - }); - - it('should not match selectors against node having ngProjectAs attribute', function() { - - /** - * - */ - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵprojectionDef(['*', [['div']]]); - ɵɵprojection(0, 1); - } - }, 1); - - /** - * - *
should not project
- *
should project
- *
- */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'child'); - { - ɵɵelementStart(1, 'div', [AttributeMarker.ProjectAs, ['span']]); - { ɵɵtext(2, 'should not project'); } - ɵɵelementEnd(); - ɵɵelementStart(3, 'div'); - { ɵɵtext(4, 'should project'); } - ɵɵelementEnd(); - } - ɵɵelementEnd(); - } - }, 5, 0, [Child]); - - const parent = renderComponent(Parent); - expect(toHtml(parent)).toEqual('
should project
'); - }); - }); - -});