test(ivy): run browser-specific @angular/core tests on CI (#27545)

PR Close #27545
This commit is contained in:
Pawel Kozlowski 2018-12-07 14:05:03 +01:00 committed by Alex Rickabaugh
parent aa48810d80
commit 5657126d86
12 changed files with 2751 additions and 2645 deletions

View File

@ -66,9 +66,6 @@ jasmine_node_test(
ts_web_test_suite( ts_web_test_suite(
name = "test_web", name = "test_web",
tags = [
"fixme-ivy-aot",
],
deps = [ deps = [
":test_lib", ":test_lib",
], ],

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/brow
import {Component, HostBinding} from '@angular/core'; import {Component, HostBinding} from '@angular/core';
import {TestBed, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {TestBed, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {fixmeIvy} from '@angular/private/testing';
import {ActivatedRoute, Router, RouterOutlet} from '@angular/router'; import {ActivatedRoute, Router, RouterOutlet} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing'; import {RouterTestingModule} from '@angular/router/testing';
@ -33,349 +34,354 @@ import {RouterTestingModule} from '@angular/router/testing';
}); });
}); });
it('should query the old and new routes via :leave and :enter', fakeAsync(() => { fixmeIvy('unknown').it(
@Component({ 'should query the old and new routes via :leave and :enter', fakeAsync(() => {
animations: [ @Component({
trigger( animations: [
'routerAnimations', trigger(
[ 'routerAnimations',
transition( [
'page1 => page2', transition(
[ 'page1 => page2',
query(':leave', animateChild()), [
query(':enter', animateChild()), query(':leave', animateChild()),
]), query(':enter', animateChild()),
]), ]),
], ]),
template: ` ],
template: `
<div [@routerAnimations]="prepareRouteAnimation(r)"> <div [@routerAnimations]="prepareRouteAnimation(r)">
<router-outlet #r="outlet"></router-outlet> <router-outlet #r="outlet"></router-outlet>
</div> </div>
` `
}) })
class ContainerCmp { class ContainerCmp {
constructor(public router: Router) {} constructor(public router: Router) {}
prepareRouteAnimation(r: RouterOutlet) { prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation']; const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null; const value = animation ? animation['value'] : null;
return value; return value;
} }
} }
@Component({ @Component({
selector: 'page1', selector: 'page1',
template: `page1`, template: `page1`,
animations: [ animations: [
trigger( trigger(
'page1Animation', 'page1Animation',
[ [
transition( transition(
':leave', ':leave',
[ [
style({width: '200px'}), style({width: '200px'}),
animate(1000, style({width: '0px'})), animate(1000, style({width: '0px'})),
]), ]),
]), ]),
] ]
}) })
class Page1Cmp { class Page1Cmp {
@HostBinding('@page1Animation') public doAnimate = true; @HostBinding('@page1Animation') public doAnimate = true;
} }
@Component({ @Component({
selector: 'page2', selector: 'page2',
template: `page2`, template: `page2`,
animations: [ animations: [
trigger( trigger(
'page2Animation', 'page2Animation',
[ [
transition( transition(
':enter', ':enter',
[ [
style({opacity: 0}), style({opacity: 0}),
animate(1000, style({opacity: 1})), animate(1000, style({opacity: 1})),
]), ]),
]), ]),
] ]
}) })
class Page2Cmp { class Page2Cmp {
@HostBinding('@page2Animation') public doAnimate = true; @HostBinding('@page2Animation') public doAnimate = true;
} }
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp], declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([ imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')}, {path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')} {path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])] ])]
}); });
const engine = TestBed.get(ɵAnimationEngine); const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp); const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance; const cmp = fixture.componentInstance;
cmp.router.initialNavigation(); cmp.router.initialNavigation();
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page1'); cmp.router.navigateByUrl('/page1');
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page2'); cmp.router.navigateByUrl('/page2');
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
const player = engine.players[0] !; const player = engine.players[0] !;
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer; const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[]; const players = groupPlayer.players as MockAnimationPlayer[];
expect(players.length).toEqual(2); expect(players.length).toEqual(2);
const [p1, p2] = players; const [p1, p2] = players;
expect(p1.duration).toEqual(1000); expect(p1.duration).toEqual(1000);
expect(p1.keyframes).toEqual([ expect(p1.keyframes).toEqual([
{offset: 0, width: '200px'}, {offset: 0, width: '200px'},
{offset: 1, width: '0px'}, {offset: 1, width: '0px'},
]); ]);
expect(p2.duration).toEqual(2000); expect(p2.duration).toEqual(2000);
expect(p2.keyframes).toEqual([ expect(p2.keyframes).toEqual([
{offset: 0, opacity: '0'}, {offset: 0, opacity: '0'},
{offset: .5, opacity: '0'}, {offset: .5, opacity: '0'},
{offset: 1, opacity: '1'}, {offset: 1, opacity: '1'},
]); ]);
})); }));
it('should allow inner enter animations to be emulated within a routed item', fakeAsync(() => { fixmeIvy('unknown').it(
@Component({ 'should allow inner enter animations to be emulated within a routed item', fakeAsync(() => {
animations: [ @Component({
trigger( animations: [
'routerAnimations', trigger(
[ 'routerAnimations',
transition( [
'page1 => page2', transition(
[ 'page1 => page2',
query(':enter', animateChild()), [
]), query(':enter', animateChild()),
]), ]),
], ]),
template: ` ],
template: `
<div [@routerAnimations]="prepareRouteAnimation(r)"> <div [@routerAnimations]="prepareRouteAnimation(r)">
<router-outlet #r="outlet"></router-outlet> <router-outlet #r="outlet"></router-outlet>
</div> </div>
` `
}) })
class ContainerCmp { class ContainerCmp {
constructor(public router: Router) {} constructor(public router: Router) {}
prepareRouteAnimation(r: RouterOutlet) { prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation']; const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null; const value = animation ? animation['value'] : null;
return value; return value;
} }
} }
@Component({selector: 'page1', template: `page1`, animations: []}) @Component({selector: 'page1', template: `page1`, animations: []})
class Page1Cmp { class Page1Cmp {
} }
@Component({ @Component({
selector: 'page2', selector: 'page2',
template: ` template: `
<h1>Page 2</h1> <h1>Page 2</h1>
<div *ngIf="exp" class="if-one" @ifAnimation></div> <div *ngIf="exp" class="if-one" @ifAnimation></div>
<div *ngIf="exp" class="if-two" @ifAnimation></div> <div *ngIf="exp" class="if-two" @ifAnimation></div>
`, `,
animations: [ animations: [
trigger( trigger(
'page2Animation', 'page2Animation',
[ [
transition( transition(
':enter', ':enter',
[query('.if-one', animateChild()), query('.if-two', animateChild())]), [query('.if-one', animateChild()), query('.if-two', animateChild())]),
]), ]),
trigger( trigger(
'ifAnimation', 'ifAnimation',
[transition( [transition(
':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])]) ':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])])
] ]
}) })
class Page2Cmp { class Page2Cmp {
@HostBinding('@page2Animation') public doAnimate = true; @HostBinding('@page2Animation') public doAnimate = true;
public exp = true; public exp = true;
} }
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp], declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([ imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')}, {path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')} {path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])] ])]
}); });
const engine = TestBed.get(ɵAnimationEngine); const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp); const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance; const cmp = fixture.componentInstance;
cmp.router.initialNavigation(); cmp.router.initialNavigation();
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page1'); cmp.router.navigateByUrl('/page1');
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page2'); cmp.router.navigateByUrl('/page2');
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
const player = engine.players[0] !; const player = engine.players[0] !;
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer; const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[]; const players = groupPlayer.players as MockAnimationPlayer[];
expect(players.length).toEqual(2); expect(players.length).toEqual(2);
const [p1, p2] = players; const [p1, p2] = players;
expect(p1.keyframes).toEqual([ expect(p1.keyframes).toEqual([
{offset: 0, opacity: '0'}, {offset: 0, opacity: '0'},
{offset: 1, opacity: '1'}, {offset: 1, opacity: '1'},
]); ]);
expect(p2.keyframes).toEqual([ expect(p2.keyframes).toEqual([
{offset: 0, opacity: '0'}, {offset: 0, opacity: '0'},
{offset: .5, opacity: '0'}, {offset: .5, opacity: '0'},
{offset: 1, opacity: '1'}, {offset: 1, opacity: '1'},
]); ]);
})); }));
it('should allow inner leave animations to be emulated within a routed item', fakeAsync(() => { fixmeIvy('unknown').it(
@Component({ 'should allow inner leave animations to be emulated within a routed item', fakeAsync(() => {
animations: [ @Component({
trigger( animations: [
'routerAnimations', trigger(
[ 'routerAnimations',
transition( [
'page1 => page2', transition(
[ 'page1 => page2',
query(':leave', animateChild()), [
]), query(':leave', animateChild()),
]), ]),
], ]),
template: ` ],
template: `
<div [@routerAnimations]="prepareRouteAnimation(r)"> <div [@routerAnimations]="prepareRouteAnimation(r)">
<router-outlet #r="outlet"></router-outlet> <router-outlet #r="outlet"></router-outlet>
</div> </div>
` `
}) })
class ContainerCmp { class ContainerCmp {
constructor(public router: Router) {} constructor(public router: Router) {}
prepareRouteAnimation(r: RouterOutlet) { prepareRouteAnimation(r: RouterOutlet) {
const animation = r.activatedRouteData['animation']; const animation = r.activatedRouteData['animation'];
const value = animation ? animation['value'] : null; const value = animation ? animation['value'] : null;
return value; return value;
} }
} }
@Component({ @Component({
selector: 'page1', selector: 'page1',
template: ` template: `
<h1>Page 1</h1> <h1>Page 1</h1>
<div *ngIf="exp" class="if-one" @ifAnimation></div> <div *ngIf="exp" class="if-one" @ifAnimation></div>
<div *ngIf="exp" class="if-two" @ifAnimation></div> <div *ngIf="exp" class="if-two" @ifAnimation></div>
`, `,
animations: [ animations: [
trigger( trigger(
'page1Animation', 'page1Animation',
[ [
transition( transition(
':leave', ':leave',
[query('.if-one', animateChild()), query('.if-two', animateChild())]), [query('.if-one', animateChild()), query('.if-two', animateChild())]),
]), ]),
trigger( trigger(
'ifAnimation', 'ifAnimation',
[transition(':leave', [style({opacity: 1}), animate(1000, style({opacity: 0}))])]), [transition(
] ':leave', [style({opacity: 1}), animate(1000, style({opacity: 0}))])]),
}) ]
class Page1Cmp { })
@HostBinding('@page1Animation') public doAnimate = true; class Page1Cmp {
@HostBinding('@page1Animation') public doAnimate = true;
public exp = true; public exp = true;
} }
@Component({selector: 'page2', template: `page2`, animations: []}) @Component({selector: 'page2', template: `page2`, animations: []})
class Page2Cmp { class Page2Cmp {
} }
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp], declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([ imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')}, {path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')} {path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])] ])]
}); });
const engine = TestBed.get(ɵAnimationEngine); const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp); const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance; const cmp = fixture.componentInstance;
cmp.router.initialNavigation(); cmp.router.initialNavigation();
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page1'); cmp.router.navigateByUrl('/page1');
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page2'); cmp.router.navigateByUrl('/page2');
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
const player = engine.players[0] !; const player = engine.players[0] !;
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer; const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
const players = groupPlayer.players as MockAnimationPlayer[]; const players = groupPlayer.players as MockAnimationPlayer[];
expect(players.length).toEqual(2); expect(players.length).toEqual(2);
const [p1, p2] = players; const [p1, p2] = players;
expect(p1.keyframes).toEqual([ expect(p1.keyframes).toEqual([
{offset: 0, opacity: '1'}, {offset: 0, opacity: '1'},
{offset: 1, opacity: '0'}, {offset: 1, opacity: '0'},
]); ]);
expect(p2.keyframes).toEqual([ expect(p2.keyframes).toEqual([
{offset: 0, opacity: '1'}, {offset: 0, opacity: '1'},
{offset: .5, opacity: '1'}, {offset: .5, opacity: '1'},
{offset: 1, opacity: '0'}, {offset: 1, opacity: '0'},
]); ]);
})); }));
it('should properly collect :enter / :leave router nodes even when another non-router *template component is within the trigger boundaries', fixmeIvy('unknown').it(
fakeAsync(() => { 'should properly collect :enter / :leave router nodes even when another non-router *template component is within the trigger boundaries',
@Component({ fakeAsync(() => {
selector: 'ani-cmp', @Component({
animations: [ selector: 'ani-cmp',
trigger( animations: [
'pageAnimation', trigger(
[ 'pageAnimation',
transition( [
'page1 => page2', transition(
[ 'page1 => page2',
query('.router-container :leave', animate('1s', style({opacity: 0}))), [
query('.router-container :enter', animate('1s', style({opacity: 1}))), query('.router-container :leave', animate('1s', style({opacity: 0}))),
]), query('.router-container :enter', animate('1s', style({opacity: 1}))),
]), ]),
], ]),
template: ` ],
template: `
<div [@pageAnimation]="prepRoute(outlet)"> <div [@pageAnimation]="prepRoute(outlet)">
<header> <header>
<div class="inner"> <div class="inner">
@ -388,136 +394,139 @@ import {RouterTestingModule} from '@angular/router/testing';
</section> </section>
</div> </div>
` `
}) })
class ContainerCmp { class ContainerCmp {
loading = false; loading = false;
constructor(public router: Router) {} constructor(public router: Router) {}
prepRoute(outlet: any) { return outlet.activatedRouteData['animation']; } prepRoute(outlet: any) { return outlet.activatedRouteData['animation']; }
} }
@Component({selector: 'page1', template: `page1`}) @Component({selector: 'page1', template: `page1`})
class Page1Cmp { class Page1Cmp {
} }
@Component({selector: 'page2', template: `page2`}) @Component({selector: 'page2', template: `page2`})
class Page2Cmp { class Page2Cmp {
} }
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [Page1Cmp, Page2Cmp, ContainerCmp], declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
imports: [RouterTestingModule.withRoutes([ imports: [RouterTestingModule.withRoutes([
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')}, {path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')} {path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
])] ])]
}); });
const engine = TestBed.get(ɵAnimationEngine); const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(ContainerCmp); const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance; const cmp = fixture.componentInstance;
cmp.router.initialNavigation(); cmp.router.initialNavigation();
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page1'); cmp.router.navigateByUrl('/page1');
tick(); tick();
cmp.loading = true; cmp.loading = true;
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
cmp.router.navigateByUrl('/page2'); cmp.router.navigateByUrl('/page2');
tick(); tick();
cmp.loading = false; cmp.loading = false;
fixture.detectChanges(); fixture.detectChanges();
engine.flush(); engine.flush();
const players = engine.players; const players = engine.players;
expect(players.length).toEqual(1); expect(players.length).toEqual(1);
const [p1] = players; const [p1] = players;
const innerPlayers = p1.getRealPlayer().players; const innerPlayers = p1.getRealPlayer().players;
expect(innerPlayers.length).toEqual(2); expect(innerPlayers.length).toEqual(2);
const [ip1, ip2] = innerPlayers; const [ip1, ip2] = innerPlayers;
expect(ip1.element.innerText).toEqual('page1'); expect(ip1.element.innerText).toEqual('page1');
expect(ip2.element.innerText).toEqual('page2'); expect(ip2.element.innerText).toEqual('page2');
})); }));
it('should allow a recursive set of :leave animations to occur for nested routes', fixmeIvy('unknown').it(
fakeAsync(() => { 'should allow a recursive set of :leave animations to occur for nested routes',
@Component({selector: 'ani-cmp', template: '<router-outlet name="recur"></router-outlet>'}) fakeAsync(() => {
class ContainerCmp { @Component(
constructor(private _router: Router) {} {selector: 'ani-cmp', template: '<router-outlet name="recur"></router-outlet>'})
log: string[] = []; class ContainerCmp {
constructor(private _router: Router) {}
log: string[] = [];
enter() { this._router.navigateByUrl('/(recur:recur/nested)'); } enter() { this._router.navigateByUrl('/(recur:recur/nested)'); }
leave() { this._router.navigateByUrl('/'); } leave() { this._router.navigateByUrl('/'); }
} }
@Component({ @Component({
selector: 'recur-page', selector: 'recur-page',
template: 'Depth: {{ depth }} \n <router-outlet></router-outlet>', template: 'Depth: {{ depth }} \n <router-outlet></router-outlet>',
animations: [ animations: [
trigger( trigger(
'pageAnimations', 'pageAnimations',
[ [
transition(':leave', [group([ transition(
sequence([style({opacity: 1}), animate('1s', style({opacity: 0}))]), ':leave', [group([
query('@*', animateChild(), {optional: true}) sequence([style({opacity: 1}), animate('1s', style({opacity: 0}))]),
])]), query('@*', animateChild(), {optional: true})
]), ])]),
] ]),
}) ]
class RecurPageCmp { })
@HostBinding('@pageAnimations') public animatePage = true; class RecurPageCmp {
@HostBinding('@pageAnimations') public animatePage = true;
@HostBinding('attr.data-depth') public depth = 0; @HostBinding('attr.data-depth') public depth = 0;
constructor(private container: ContainerCmp, private route: ActivatedRoute) { constructor(private container: ContainerCmp, private route: ActivatedRoute) {
this.route.data.subscribe(data => { this.route.data.subscribe(data => {
this.container.log.push(`DEPTH ${data.depth}`); this.container.log.push(`DEPTH ${data.depth}`);
this.depth = data.depth; this.depth = data.depth;
}); });
} }
} }
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ContainerCmp, RecurPageCmp], declarations: [ContainerCmp, RecurPageCmp],
imports: [RouterTestingModule.withRoutes([{ imports: [RouterTestingModule.withRoutes([{
path: 'recur', path: 'recur',
component: RecurPageCmp, component: RecurPageCmp,
outlet: 'recur', outlet: 'recur',
data: {depth: 0}, data: {depth: 0},
children: [{path: 'nested', component: RecurPageCmp, data: {depth: 1}}] children: [{path: 'nested', component: RecurPageCmp, data: {depth: 1}}]
}])] }])]
}); });
const fixture = TestBed.createComponent(ContainerCmp); const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance; const cmp = fixture.componentInstance;
cmp.enter(); cmp.enter();
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
flushMicrotasks(); flushMicrotasks();
expect(cmp.log).toEqual([ expect(cmp.log).toEqual([
'DEPTH 0', 'DEPTH 0',
'DEPTH 1', 'DEPTH 1',
]); ]);
cmp.leave(); cmp.leave();
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
const players = getLog(); const players = getLog();
expect(players.length).toEqual(2); expect(players.length).toEqual(2);
const [p1, p2] = players; const [p1, p2] = players;
expect(p1.element.getAttribute('data-depth')).toEqual('0'); expect(p1.element.getAttribute('data-depth')).toEqual('0');
expect(p2.element.getAttribute('data-depth')).toEqual('1'); expect(p2.element.getAttribute('data-depth')).toEqual('1');
})); }));
}); });
}); });

View File

@ -5,15 +5,15 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {animate, group, query, state, style, transition, trigger} from '@angular/animations'; import {animate, query, state, style, transition, trigger} from '@angular/animations';
import {AnimationDriver, ɵAnimationEngine, ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser'; import {AnimationDriver, ɵAnimationEngine, ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser';
import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine'; import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine';
import {AnimationGroupPlayer} from '@angular/animations/src/players/animation_group_player'; import {AnimationGroupPlayer} from '@angular/animations/src/players/animation_group_player';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
import {fixmeIvy} from '@angular/private/testing';
import {TestBed} from '../../testing';
(function() { (function() {
// these tests are only mean't to be run within the DOM (for now) // these tests are only mean't to be run within the DOM (for now)
@ -242,9 +242,10 @@ import {TestBed} from '../../testing';
]); ]);
}); });
it('should treat * styles as ! for queried items that are collected in a container that is being removed', fixmeIvy('unknown').it(
() => { 'should treat * styles as ! for queried items that are collected in a container that is being removed',
@Component({ () => {
@Component({
selector: 'my-app', selector: 'my-app',
styles: [` styles: [`
.list .outer { .list .outer {
@ -285,58 +286,58 @@ import {TestBed} from '../../testing';
] ]
}) })
class Cmp { class Cmp {
items: any[] = []; items: any[] = [];
get exp() { return this.items.length ? 'full' : 'empty'; } get exp() { return this.items.length ? 'full' : 'empty'; }
empty() { this.items = []; } empty() { this.items = []; }
full() { this.items = [0, 1, 2, 3, 4]; } full() { this.items = [0, 1, 2, 3, 4]; }
} }
TestBed.configureTestingModule({declarations: [Cmp]}); TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.get(ɵAnimationEngine); const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp); const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance; const cmp = fixture.componentInstance;
cmp.empty(); cmp.empty();
fixture.detectChanges(); fixture.detectChanges();
let player = engine.players[0] !as TransitionAnimationPlayer; let player = engine.players[0] !as TransitionAnimationPlayer;
player.finish(); player.finish();
cmp.full(); cmp.full();
fixture.detectChanges(); fixture.detectChanges();
player = engine.players[0] !as TransitionAnimationPlayer; player = engine.players[0] !as TransitionAnimationPlayer;
let queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players; let queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
expect(queriedPlayers.length).toEqual(5); expect(queriedPlayers.length).toEqual(5);
let i = 0; let i = 0;
for (i = 0; i < queriedPlayers.length; i++) { for (i = 0; i < queriedPlayers.length; i++) {
let player = queriedPlayers[i] as ɵWebAnimationsPlayer; let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
expect(player.keyframes).toEqual([ expect(player.keyframes).toEqual([
{height: '0px', offset: 0}, {height: '0px', offset: 0},
{height: '50px', offset: 1}, {height: '50px', offset: 1},
]); ]);
player.finish(); player.finish();
} }
cmp.empty(); cmp.empty();
fixture.detectChanges(); fixture.detectChanges();
player = engine.players[0] !as TransitionAnimationPlayer; player = engine.players[0] !as TransitionAnimationPlayer;
queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players; queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
expect(queriedPlayers.length).toEqual(5); expect(queriedPlayers.length).toEqual(5);
for (i = 0; i < queriedPlayers.length; i++) { for (i = 0; i < queriedPlayers.length; i++) {
let player = queriedPlayers[i] as ɵWebAnimationsPlayer; let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
expect(player.keyframes).toEqual([ expect(player.keyframes).toEqual([
{height: '50px', offset: 0}, {height: '50px', offset: 0},
{height: '0px', offset: 1}, {height: '0px', offset: 1},
]); ]);
} }
}); });
it('should compute intermediate styles properly when an animation is cancelled', () => { it('should compute intermediate styles properly when an animation is cancelled', () => {
@Component({ @Component({

View File

@ -13,6 +13,7 @@ import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy} from '@angular/private/testing';
export function createUrlResolverWithoutPackagePrefix(): UrlResolver { export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
return new UrlResolver(); return new UrlResolver();
@ -445,13 +446,14 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
})); }));
it('should ignore empty bindings', fakeAsync(() => { fixmeIvy('FW-814: Bindings with an empty value should be ignored in the compiler')
const ctx = _bindSimpleProp('[someProp]', TestData); .it('should ignore empty bindings', fakeAsync(() => {
ctx.componentInstance.a = 'value'; const ctx = _bindSimpleProp('[someProp]', TestData);
ctx.detectChanges(false); ctx.componentInstance.a = 'value';
ctx.detectChanges(false);
expect(renderLog.log).toEqual([]); expect(renderLog.log).toEqual([]);
})); }));
it('should support interpolation', fakeAsync(() => { it('should support interpolation', fakeAsync(() => {
const ctx = _bindSimpleProp('someProp="B{{a}}A"', TestData); const ctx = _bindSimpleProp('someProp="B{{a}}A"', TestData);
@ -537,27 +539,28 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(renderLog.log).toEqual(['someProp=Megatron']); expect(renderLog.log).toEqual(['someProp=Megatron']);
})); }));
it('should record unwrapped values via ngOnChanges', fakeAsync(() => { fixmeIvy('unknown').it(
const ctx = createCompFixture( 'should record unwrapped values via ngOnChanges', fakeAsync(() => {
'<div [testDirective]="\'aName\' | wrappedPipe" [a]="1" [b]="2 | wrappedPipe"></div>'); const ctx = createCompFixture(
const dir: TestDirective = queryDirs(ctx.debugElement, TestDirective)[0]; '<div [testDirective]="\'aName\' | wrappedPipe" [a]="1" [b]="2 | wrappedPipe"></div>');
ctx.detectChanges(false); const dir: TestDirective = queryDirs(ctx.debugElement, TestDirective)[0];
dir.changes = {}; ctx.detectChanges(false);
ctx.detectChanges(false); dir.changes = {};
ctx.detectChanges(false);
// Note: the binding for `b` did not change and has no ValueWrapper, // Note: the binding for `b` did not change and has no ValueWrapper,
// and should therefore stay unchanged. // and should therefore stay unchanged.
expect(dir.changes).toEqual({ expect(dir.changes).toEqual({
'name': new SimpleChange('aName', 'aName', false), 'name': new SimpleChange('aName', 'aName', false),
'b': new SimpleChange(2, 2, false) 'b': new SimpleChange(2, 2, false)
}); });
ctx.detectChanges(false); ctx.detectChanges(false);
expect(dir.changes).toEqual({ expect(dir.changes).toEqual({
'name': new SimpleChange('aName', 'aName', false), 'name': new SimpleChange('aName', 'aName', false),
'b': new SimpleChange(2, 2, false) 'b': new SimpleChange(2, 2, false)
}); });
})); }));
it('should call pure pipes only if the arguments change', fakeAsync(() => { it('should call pure pipes only if the arguments change', fakeAsync(() => {
const ctx = _bindSimpleValue('name | countingPipe', Person); const ctx = _bindSimpleValue('name | countingPipe', Person);
@ -588,29 +591,30 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
})); }));
it('should call pure pipes that are used multiple times only when the arguments change', fixmeIvy('unknown').it(
fakeAsync(() => { 'should call pure pipes that are used multiple times only when the arguments change',
const ctx = createCompFixture( fakeAsync(() => {
`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>` + const ctx = createCompFixture(
'<div *ngFor="let x of [1,2]" [someProp]="address.city | countingPipe"></div>', `<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>` +
Person); '<div *ngFor="let x of [1,2]" [someProp]="address.city | countingPipe"></div>',
ctx.componentInstance.name = 'a'; Person);
ctx.componentInstance.age = 10; ctx.componentInstance.name = 'a';
ctx.componentInstance.address = new Address('mtv'); ctx.componentInstance.age = 10;
ctx.detectChanges(false); ctx.componentInstance.address = new Address('mtv');
expect(renderLog.loggedValues).toEqual([ ctx.detectChanges(false);
'mtv state:0', 'mtv state:1', 'a state:2', '10 state:3' expect(renderLog.loggedValues).toEqual([
]); 'mtv state:0', 'mtv state:1', 'a state:2', '10 state:3'
ctx.detectChanges(false); ]);
expect(renderLog.loggedValues).toEqual([ ctx.detectChanges(false);
'mtv state:0', 'mtv state:1', 'a state:2', '10 state:3' expect(renderLog.loggedValues).toEqual([
]); 'mtv state:0', 'mtv state:1', 'a state:2', '10 state:3'
ctx.componentInstance.age = 11; ]);
ctx.detectChanges(false); ctx.componentInstance.age = 11;
expect(renderLog.loggedValues).toEqual([ ctx.detectChanges(false);
'mtv state:0', 'mtv state:1', 'a state:2', '10 state:3', '11 state:4' expect(renderLog.loggedValues).toEqual([
]); 'mtv state:0', 'mtv state:1', 'a state:2', '10 state:3', '11 state:4'
})); ]);
}));
it('should call impure pipes on each change detection run', fakeAsync(() => { it('should call impure pipes on each change detection run', fakeAsync(() => {
const ctx = _bindSimpleValue('name | countingImpurePipe', Person); const ctx = _bindSimpleValue('name | countingImpurePipe', Person);
@ -994,31 +998,32 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]); expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]);
})); }));
it('should not call ngAfterViewInit again if it throws', fakeAsync(() => { fixmeIvy('unknown').it(
const ctx = 'should not call ngAfterViewInit again if it throws', fakeAsync(() => {
createCompFixture('<div testDirective="dir" throwOn="ngAfterViewInit"></div>'); const ctx =
createCompFixture('<div testDirective="dir" throwOn="ngAfterViewInit"></div>');
let errored = false; let errored = false;
// First pass fails, but ngAfterViewInit should be called. // First pass fails, but ngAfterViewInit should be called.
try { try {
ctx.detectChanges(false); ctx.detectChanges(false);
} catch (e) { } catch (e) {
errored = true; errored = true;
} }
expect(errored).toBe(true); expect(errored).toBe(true);
expect(directiveLog.filter(['ngAfterViewInit'])).toEqual(['dir.ngAfterViewInit']); expect(directiveLog.filter(['ngAfterViewInit'])).toEqual(['dir.ngAfterViewInit']);
directiveLog.clear(); directiveLog.clear();
// Second change detection also fails, but this time ngAfterViewInit should not be // Second change detection also fails, but this time ngAfterViewInit should not be
// called. // called.
try { try {
ctx.detectChanges(false); ctx.detectChanges(false);
} catch (e) { } catch (e) {
throw new Error('Second detectChanges() should not have run detection.'); throw new Error('Second detectChanges() should not have run detection.');
} }
expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]); expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]);
})); }));
}); });
describe('ngAfterViewChecked', () => { describe('ngAfterViewChecked', () => {
@ -1073,111 +1078,119 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
}); });
describe('ngOnDestroy', () => { describe('ngOnDestroy', () => {
it('should be called on view destruction', fakeAsync(() => { fixmeIvy('unknown').it(
const ctx = createCompFixture('<div testDirective="dir"></div>'); 'should be called on view destruction', fakeAsync(() => {
ctx.detectChanges(false); const ctx = createCompFixture('<div testDirective="dir"></div>');
ctx.detectChanges(false);
ctx.destroy(); ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy'])).toEqual(['dir.ngOnDestroy']); expect(directiveLog.filter(['ngOnDestroy'])).toEqual(['dir.ngOnDestroy']);
})); }));
it('should be called after processing the content and view children', fakeAsync(() => { fixmeIvy('unknown').it(
TestBed.overrideComponent(AnotherComponent, { 'should be called after processing the content and view children', fakeAsync(() => {
set: new Component( TestBed.overrideComponent(AnotherComponent, {
{selector: 'other-cmp', template: '<div testDirective="viewChild"></div>'}) set: new Component(
}); {selector: 'other-cmp', template: '<div testDirective="viewChild"></div>'})
});
const ctx = createCompFixture( const ctx = createCompFixture(
'<div testDirective="parent"><div *ngFor="let x of [0,1]" testDirective="contentChild{{x}}"></div>' + '<div testDirective="parent"><div *ngFor="let x of [0,1]" testDirective="contentChild{{x}}"></div>' +
'<other-cmp></other-cmp></div>', '<other-cmp></other-cmp></div>',
TestComponent); TestComponent);
ctx.detectChanges(false); ctx.detectChanges(false);
ctx.destroy(); ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([ expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
'contentChild0.ngOnDestroy', 'contentChild1.ngOnDestroy', 'viewChild.ngOnDestroy', 'contentChild0.ngOnDestroy', 'contentChild1.ngOnDestroy', 'viewChild.ngOnDestroy',
'parent.ngOnDestroy' 'parent.ngOnDestroy'
]); ]);
})); }));
it('should be called in reverse order so the child is always notified before the parent', fixmeIvy('unknown').it(
fakeAsync(() => { 'should be called in reverse order so the child is always notified before the parent',
const ctx = createCompFixture( fakeAsync(() => {
'<div testDirective="parent"><div testDirective="child"></div></div><div testDirective="sibling"></div>'); const ctx = createCompFixture(
'<div testDirective="parent"><div testDirective="child"></div></div><div testDirective="sibling"></div>');
ctx.detectChanges(false); ctx.detectChanges(false);
ctx.destroy(); ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([ expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
'child.ngOnDestroy', 'parent.ngOnDestroy', 'sibling.ngOnDestroy' 'child.ngOnDestroy', 'parent.ngOnDestroy', 'sibling.ngOnDestroy'
]); ]);
})); }));
it('should deliver synchronous events to parent', fakeAsync(() => { fixmeIvy('unknown').it(
const ctx = createCompFixture('<div (destroy)="a=$event" onDestroyDirective></div>'); 'should deliver synchronous events to parent', fakeAsync(() => {
const ctx = createCompFixture('<div (destroy)="a=$event" onDestroyDirective></div>');
ctx.detectChanges(false); ctx.detectChanges(false);
ctx.destroy(); ctx.destroy();
expect(ctx.componentInstance.a).toEqual('destroyed'); expect(ctx.componentInstance.a).toEqual('destroyed');
})); }));
it('should call ngOnDestroy on pipes', fakeAsync(() => { fixmeIvy('unknown').it('should call ngOnDestroy on pipes', fakeAsync(() => {
const ctx = createCompFixture('{{true | pipeWithOnDestroy }}'); const ctx = createCompFixture('{{true | pipeWithOnDestroy }}');
ctx.detectChanges(false); ctx.detectChanges(false);
ctx.destroy(); ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([ expect(directiveLog.filter(['ngOnDestroy'])).toEqual([
'pipeWithOnDestroy.ngOnDestroy' 'pipeWithOnDestroy.ngOnDestroy'
]); ]);
})); }));
it('should call ngOnDestroy on an injectable class', fakeAsync(() => { fixmeIvy('unknown').it('should call ngOnDestroy on an injectable class', fakeAsync(() => {
TestBed.overrideDirective( TestBed.overrideDirective(
TestDirective, {set: {providers: [InjectableWithLifecycle]}}); TestDirective, {set: {providers: [InjectableWithLifecycle]}});
const ctx = createCompFixture('<div testDirective="dir"></div>', TestComponent); const ctx = createCompFixture(
'<div testDirective="dir"></div>', TestComponent);
ctx.debugElement.children[0].injector.get(InjectableWithLifecycle); ctx.debugElement.children[0].injector.get(InjectableWithLifecycle);
ctx.detectChanges(false); ctx.detectChanges(false);
ctx.destroy(); ctx.destroy();
// We don't care about the exact order in this test. // We don't care about the exact order in this test.
expect(directiveLog.filter(['ngOnDestroy']).sort()).toEqual([ expect(directiveLog.filter(['ngOnDestroy']).sort()).toEqual([
'dir.ngOnDestroy', 'injectable.ngOnDestroy' 'dir.ngOnDestroy', 'injectable.ngOnDestroy'
]); ]);
})); }));
}); });
}); });
describe('enforce no new changes', () => { describe('enforce no new changes', () => {
it('should throw when a record gets changed after it has been checked', fakeAsync(() => { fixmeIvy('unknown').it(
@Directive({selector: '[changed]'}) 'should throw when a record gets changed after it has been checked', fakeAsync(() => {
class ChangingDirective { @Directive({selector: '[changed]'})
@Input() changed: any; class ChangingDirective {
} @Input() changed: any;
}
TestBed.configureTestingModule({declarations: [ChangingDirective]}); TestBed.configureTestingModule({declarations: [ChangingDirective]});
const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData); const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData);
ctx.componentInstance.b = 1; ctx.componentInstance.b = 1;
expect(() => ctx.checkNoChanges()) expect(() => ctx.checkNoChanges())
.toThrowError(/Previous value: 'changed: undefined'\. Current value: 'changed: 1'/g); .toThrowError(
})); /Previous value: 'changed: undefined'\. Current value: 'changed: 1'/g);
}));
it('should warn when the view has been created in a cd hook', fakeAsync(() => { fixmeIvy('unknown').it(
const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData); 'should warn when the view has been created in a cd hook', fakeAsync(() => {
ctx.componentInstance.a = 1; const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData);
expect(() => ctx.detectChanges()) ctx.componentInstance.a = 1;
.toThrowError( expect(() => ctx.detectChanges())
/It seems like the view has been created after its parent and its children have been dirty checked/); .toThrowError(
})); /It seems like the view has been created after its parent and its children have been dirty checked/);
}));
it('should not throw when two arrays are structurally the same', fakeAsync(() => { it('should not throw when two arrays are structurally the same', fakeAsync(() => {
const ctx = _bindSimpleValue('a', TestData); const ctx = _bindSimpleValue('a', TestData);
@ -1187,27 +1200,27 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(() => ctx.checkNoChanges()).not.toThrow(); expect(() => ctx.checkNoChanges()).not.toThrow();
})); }));
it('should not break the next run', fakeAsync(() => { fixmeIvy('unknown').it('should not break the next run', fakeAsync(() => {
const ctx = _bindSimpleValue('a', TestData); const ctx = _bindSimpleValue('a', TestData);
ctx.componentInstance.a = 'value'; ctx.componentInstance.a = 'value';
expect(() => ctx.checkNoChanges()).toThrow(); expect(() => ctx.checkNoChanges()).toThrow();
ctx.detectChanges(); ctx.detectChanges();
expect(renderLog.loggedValues).toEqual(['value']); expect(renderLog.loggedValues).toEqual(['value']);
})); }));
}); });
describe('mode', () => { describe('mode', () => {
it('Detached', fakeAsync(() => { fixmeIvy('unknown').it('Detached', fakeAsync(() => {
const ctx = createCompFixture('<comp-with-ref></comp-with-ref>'); const ctx = createCompFixture('<comp-with-ref></comp-with-ref>');
const cmp: CompWithRef = queryDirs(ctx.debugElement, CompWithRef)[0]; const cmp: CompWithRef = queryDirs(ctx.debugElement, CompWithRef)[0];
cmp.value = 'hello'; cmp.value = 'hello';
cmp.changeDetectorRef.detach(); cmp.changeDetectorRef.detach();
ctx.detectChanges(); ctx.detectChanges();
expect(renderLog.log).toEqual([]); expect(renderLog.log).toEqual([]);
})); }));
it('Detached should disable OnPush', fakeAsync(() => { it('Detached should disable OnPush', fakeAsync(() => {
const ctx = createCompFixture('<push-cmp [value]="value"></push-cmp>'); const ctx = createCompFixture('<push-cmp [value]="value"></push-cmp>');
@ -1260,34 +1273,36 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
})); }));
it('Reattaches in the original cd mode', fakeAsync(() => { fixmeIvy('unknown').it('Reattaches in the original cd mode', fakeAsync(() => {
const ctx = createCompFixture('<push-cmp></push-cmp>'); const ctx = createCompFixture('<push-cmp></push-cmp>');
const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0]; const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0];
cmp.changeDetectorRef.detach(); cmp.changeDetectorRef.detach();
cmp.changeDetectorRef.reattach(); cmp.changeDetectorRef.reattach();
// renderCount should NOT be incremented with each CD as CD mode should be resetted to // renderCount should NOT be incremented with each CD as CD mode
// on-push // should be resetted to
ctx.detectChanges(); // on-push
expect(cmp.renderCount).toBeGreaterThan(0); ctx.detectChanges();
const count = cmp.renderCount; expect(cmp.renderCount).toBeGreaterThan(0);
const count = cmp.renderCount;
ctx.detectChanges(); ctx.detectChanges();
expect(cmp.renderCount).toBe(count); expect(cmp.renderCount).toBe(count);
})); }));
}); });
describe('multi directive order', () => { describe('multi directive order', () => {
it('should follow the DI order for the same element', fakeAsync(() => { fixmeIvy('unknown').it(
const ctx = 'should follow the DI order for the same element', fakeAsync(() => {
createCompFixture('<div orderCheck2="2" orderCheck0="0" orderCheck1="1"></div>'); const ctx =
createCompFixture('<div orderCheck2="2" orderCheck0="0" orderCheck1="1"></div>');
ctx.detectChanges(false); ctx.detectChanges(false);
ctx.destroy(); ctx.destroy();
expect(directiveLog.filter(['set'])).toEqual(['0.set', '1.set', '2.set']); expect(directiveLog.filter(['set'])).toEqual(['0.set', '1.set', '2.set']);
})); }));
}); });
describe('nested view recursion', () => { describe('nested view recursion', () => {
@ -1424,25 +1439,26 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(log).toEqual(['inner-start', 'main-tpl', 'outer-tpl']); expect(log).toEqual(['inner-start', 'main-tpl', 'outer-tpl']);
}); });
it('should dirty check projected views if the declaration place is dirty checked', () => { fixmeIvy('unknown').it(
ctx.detectChanges(false); 'should dirty check projected views if the declaration place is dirty checked', () => {
log = []; ctx.detectChanges(false);
innerComp.cdRef.detach(); log = [];
mainComp.cdRef.detectChanges(); innerComp.cdRef.detach();
mainComp.cdRef.detectChanges();
expect(log).toEqual(['main-start', 'outer-start', 'main-tpl', 'outer-tpl']); expect(log).toEqual(['main-start', 'outer-start', 'main-tpl', 'outer-tpl']);
log = []; log = [];
outerComp.cdRef.detectChanges(); outerComp.cdRef.detectChanges();
expect(log).toEqual(['outer-start', 'outer-tpl']); expect(log).toEqual(['outer-start', 'outer-tpl']);
log = []; log = [];
outerComp.cdRef.detach(); outerComp.cdRef.detach();
mainComp.cdRef.detectChanges(); mainComp.cdRef.detectChanges();
expect(log).toEqual(['main-start', 'main-tpl']); expect(log).toEqual(['main-start', 'main-tpl']);
}); });
}); });
}); });
@ -1516,7 +1532,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
childThrows: LifetimeMethods; childThrows: LifetimeMethods;
} }
describe('calling init', () => { fixmeIvy('unknown').describe('calling init', () => {
function initialize(options: Options) { function initialize(options: Options) {
@Component({selector: 'my-child', template: ''}) @Component({selector: 'my-child', template: ''})
class MyChild { class MyChild {

View File

@ -700,30 +700,31 @@ function declareTests(config?: {useJit: boolean}) {
}); });
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
it('should be checked when an async pipe requests a check', fakeAsync(() => { fixmeIvy('unknown').it(
TestBed.configureTestingModule( 'should be checked when an async pipe requests a check', fakeAsync(() => {
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); TestBed.configureTestingModule(
const template = '<push-cmp-with-async #cmp></push-cmp-with-async>'; {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
TestBed.overrideComponent(MyComp, {set: {template}}); const template = '<push-cmp-with-async #cmp></push-cmp-with-async>';
const fixture = TestBed.createComponent(MyComp); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
tick(); tick();
const cmp: PushCmpWithAsyncPipe = const cmp: PushCmpWithAsyncPipe =
fixture.debugElement.children[0].references !['cmp']; fixture.debugElement.children[0].references !['cmp'];
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
fixture.detectChanges(); fixture.detectChanges();
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
cmp.resolve(2); cmp.resolve(2);
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(2); expect(cmp.numberOfChecks).toEqual(2);
})); }));
} }
}); });
@ -1872,7 +1873,7 @@ function declareTests(config?: {useJit: boolean}) {
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
describe('svg', () => { describe('svg', () => {
it('should support svg elements', () => { fixmeIvy('unknown').it('should support svg elements', () => {
TestBed.configureTestingModule({declarations: [MyComp]}); TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<svg><use xlink:href="Port" /></svg>'; const template = '<svg><use xlink:href="Port" /></svg>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -1891,7 +1892,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink'); expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink');
}); });
it('should support foreignObjects with document fragments', () => { fixmeIvy('unknown').it('should support foreignObjects with document fragments', () => {
TestBed.configureTestingModule({declarations: [MyComp]}); TestBed.configureTestingModule({declarations: [MyComp]});
const template = const template =
'<svg><foreignObject><xhtml:div><p>Test</p></xhtml:div></foreignObject></svg>'; '<svg><foreignObject><xhtml:div><p>Test</p></xhtml:div></foreignObject></svg>';
@ -1913,7 +1914,7 @@ function declareTests(config?: {useJit: boolean}) {
describe('attributes', () => { describe('attributes', () => {
it('should support attributes with namespace', () => { fixmeIvy('unknown').it('should support attributes with namespace', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]}); TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
const template = '<svg:use xlink:href="#id" />'; const template = '<svg:use xlink:href="#id" />';
TestBed.overrideComponent(SomeCmp, {set: {template}}); TestBed.overrideComponent(SomeCmp, {set: {template}});
@ -1924,7 +1925,7 @@ function declareTests(config?: {useJit: boolean}) {
.toEqual('#id'); .toEqual('#id');
}); });
it('should support binding to attributes with namespace', () => { fixmeIvy('unknown').it('should support binding to attributes with namespace', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]}); TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
const template = '<svg:use [attr.xlink:href]="value" />'; const template = '<svg:use [attr.xlink:href]="value" />';
TestBed.overrideComponent(SomeCmp, {set: {template}}); TestBed.overrideComponent(SomeCmp, {set: {template}});

View File

@ -370,21 +370,22 @@ describe('projection', () => {
}); });
if (getDOM().supportsNativeShadowDOM()) { if (getDOM().supportsNativeShadowDOM()) {
it('should support native content projection and isolate styles per component', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]}); 'should support native content projection and isolate styles per component', () => {
TestBed.overrideComponent(MainComp, { TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]});
set: { TestBed.overrideComponent(MainComp, {
template: '<simple-native1><div>A</div></simple-native1>' + set: {
'<simple-native2><div>B</div></simple-native2>' template: '<simple-native1><div>A</div></simple-native1>' +
} '<simple-native2><div>B</div></simple-native2>'
}); }
const main = TestBed.createComponent(MainComp); });
const main = TestBed.createComponent(MainComp);
const childNodes = getDOM().childNodes(main.nativeElement); const childNodes = getDOM().childNodes(main.nativeElement);
expect(childNodes[0]).toHaveText('div {color: red}SIMPLE1(A)'); expect(childNodes[0]).toHaveText('div {color: red}SIMPLE1(A)');
expect(childNodes[1]).toHaveText('div {color: blue}SIMPLE2(B)'); expect(childNodes[1]).toHaveText('div {color: blue}SIMPLE2(B)');
main.destroy(); main.destroy();
}); });
} }
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {

View File

@ -428,7 +428,7 @@ function declareTestsUsingBootstrap() {
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
// This test needs a real DOM.... // This test needs a real DOM....
it('should keep change detecting if there was an error', (done) => { fixmeIvy('unknown').it('should keep change detecting if there was an error', (done) => {
@Component({ @Component({
selector: COMP_SELECTOR, selector: COMP_SELECTOR,
template: template:

View File

@ -10,6 +10,7 @@ import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, Compon
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing'; import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy} from '@angular/private/testing';
@Directive({selector: '[simpleDirective]'}) @Directive({selector: '[simpleDirective]'})
class SimpleDirective { class SimpleDirective {
@ -291,7 +292,7 @@ class TestComp {
expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2'); expect(el.children[0].injector.get('injectable2')).toEqual('injectable1-injectable2');
}); });
it('should instantiate viewProviders that have dependencies', () => { fixmeIvy('unknown').it('should instantiate viewProviders that have dependencies', () => {
TestBed.configureTestingModule({declarations: [SimpleComponent]}); TestBed.configureTestingModule({declarations: [SimpleComponent]});
const viewProviders = [ const viewProviders = [
{provide: 'injectable1', useValue: 'injectable1'}, { {provide: 'injectable1', useValue: 'injectable1'}, {
@ -428,7 +429,7 @@ class TestComp {
expect(ctx.debugElement.injector.get('eager2')).toBe('v2: v1'); expect(ctx.debugElement.injector.get('eager2')).toBe('v2: v1');
}); });
it('should inject providers that were declared after it', () => { fixmeIvy('unknown').it('should inject providers that were declared after it', () => {
@Component({ @Component({
template: '', template: '',
providers: [ providers: [
@ -464,7 +465,7 @@ class TestComp {
expect(comp.componentInstance.a).toBe('aValue'); expect(comp.componentInstance.a).toBe('aValue');
}); });
it('should support ngOnDestroy for lazy providers', () => { fixmeIvy('unknown').it('should support ngOnDestroy for lazy providers', () => {
let created = false; let created = false;
let destroyed = false; let destroyed = false;
@ -496,7 +497,7 @@ class TestComp {
expect(destroyed).toBe(true); expect(destroyed).toBe(true);
}); });
it('should instantiate view providers lazily', () => { fixmeIvy('unknown').it('should instantiate view providers lazily', () => {
let created = false; let created = false;
TestBed.configureTestingModule({declarations: [SimpleComponent]}); TestBed.configureTestingModule({declarations: [SimpleComponent]});
TestBed.overrideComponent( TestBed.overrideComponent(
@ -551,38 +552,45 @@ class TestComp {
.toEqual('parentService'); .toEqual('parentService');
}); });
it('should instantiate directives that depend on providers of a component', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); 'should instantiate directives that depend on providers of a component', () => {
TestBed.overrideComponent( TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]});
SimpleComponent, {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); TestBed.overrideComponent(
TestBed.overrideComponent(SimpleComponent, {set: {template: '<div needsService></div>'}}); SimpleComponent,
const el = createComponent('<div simpleComponent></div>'); {set: {providers: [{provide: 'service', useValue: 'hostService'}]}});
expect(el.children[0].children[0].injector.get(NeedsService).service) TestBed.overrideComponent(
.toEqual('hostService'); SimpleComponent, {set: {template: '<div needsService></div>'}});
}); const el = createComponent('<div simpleComponent></div>');
expect(el.children[0].children[0].injector.get(NeedsService).service)
.toEqual('hostService');
});
it('should instantiate directives that depend on view providers of a component', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); 'should instantiate directives that depend on view providers of a component', () => {
TestBed.overrideComponent( TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]});
SimpleComponent, {set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); TestBed.overrideComponent(
TestBed.overrideComponent(SimpleComponent, {set: {template: '<div needsService></div>'}}); SimpleComponent,
const el = createComponent('<div simpleComponent></div>'); {set: {providers: [{provide: 'service', useValue: 'hostService'}]}});
expect(el.children[0].children[0].injector.get(NeedsService).service) TestBed.overrideComponent(
.toEqual('hostService'); SimpleComponent, {set: {template: '<div needsService></div>'}});
}); const el = createComponent('<div simpleComponent></div>');
expect(el.children[0].children[0].injector.get(NeedsService).service)
.toEqual('hostService');
});
it('should instantiate directives in a root embedded view that depend on view providers of a component', fixmeIvy('unknown').it(
() => { 'should instantiate directives in a root embedded view that depend on view providers of a component',
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]}); () => {
TestBed.overrideComponent( TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsService]});
SimpleComponent, TestBed.overrideComponent(
{set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); SimpleComponent,
TestBed.overrideComponent( {set: {providers: [{provide: 'service', useValue: 'hostService'}]}});
SimpleComponent, {set: {template: '<div *ngIf="true" needsService></div>'}}); TestBed.overrideComponent(
const el = createComponent('<div simpleComponent></div>'); SimpleComponent, {set: {template: '<div *ngIf="true" needsService></div>'}});
expect(el.children[0].children[0].injector.get(NeedsService).service) const el = createComponent('<div simpleComponent></div>');
.toEqual('hostService'); expect(el.children[0].children[0].injector.get(NeedsService).service)
}); .toEqual('hostService');
});
it('should instantiate directives that depend on instances in the app injector', () => { it('should instantiate directives that depend on instances in the app injector', () => {
TestBed.configureTestingModule({declarations: [NeedsAppService]}); TestBed.configureTestingModule({declarations: [NeedsAppService]});
@ -590,54 +598,57 @@ class TestComp {
expect(el.children[0].injector.get(NeedsAppService).service).toEqual('appService'); expect(el.children[0].injector.get(NeedsAppService).service).toEqual('appService');
}); });
it('should not instantiate a directive with cyclic dependencies', () => { fixmeIvy('unknown').it('should not instantiate a directive with cyclic dependencies', () => {
TestBed.configureTestingModule({declarations: [CycleDirective]}); TestBed.configureTestingModule({declarations: [CycleDirective]});
expect(() => createComponent('<div cycleDirective></div>')) expect(() => createComponent('<div cycleDirective></div>'))
.toThrowError( .toThrowError(
/Template parse errors:\nCannot instantiate cyclic dependency! CycleDirective \("\[ERROR ->\]<div cycleDirective><\/div>"\): .*TestComp.html@0:0/); /Template parse errors:\nCannot instantiate cyclic dependency! CycleDirective \("\[ERROR ->\]<div cycleDirective><\/div>"\): .*TestComp.html@0:0/);
}); });
it('should not instantiate a directive in a view that has a host dependency on providers' + fixmeIvy('unknown').it(
' of the component', 'should not instantiate a directive in a view that has a host dependency on providers' +
() => { ' of the component',
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsServiceFromHost]}); () => {
TestBed.overrideComponent( TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsServiceFromHost]});
SimpleComponent, TestBed.overrideComponent(
{set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); SimpleComponent,
TestBed.overrideComponent( {set: {providers: [{provide: 'service', useValue: 'hostService'}]}});
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}}); TestBed.overrideComponent(
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}});
expect(() => createComponent('<div simpleComponent></div>')) expect(() => createComponent('<div simpleComponent></div>'))
.toThrowError( .toThrowError(
/Template parse errors:\nNo provider for service \("\[ERROR ->\]<div needsServiceFromHost><div>"\): .*SimpleComponent.html@0:0/); /Template parse errors:\nNo provider for service \("\[ERROR ->\]<div needsServiceFromHost><div>"\): .*SimpleComponent.html@0:0/);
}); });
it('should not instantiate a directive in a view that has a host dependency on providers' + fixmeIvy('unknown').it(
' of a decorator directive', 'should not instantiate a directive in a view that has a host dependency on providers' +
() => { ' of a decorator directive',
TestBed.configureTestingModule( () => {
{declarations: [SimpleComponent, SomeOtherDirective, NeedsServiceFromHost]}); TestBed.configureTestingModule(
TestBed.overrideComponent( {declarations: [SimpleComponent, SomeOtherDirective, NeedsServiceFromHost]});
SimpleComponent, TestBed.overrideComponent(
{set: {providers: [{provide: 'service', useValue: 'hostService'}]}}); SimpleComponent,
TestBed.overrideComponent( {set: {providers: [{provide: 'service', useValue: 'hostService'}]}});
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}}); TestBed.overrideComponent(
SimpleComponent, {set: {template: '<div needsServiceFromHost><div>'}});
expect(() => createComponent('<div simpleComponent someOtherDirective></div>')) expect(() => createComponent('<div simpleComponent someOtherDirective></div>'))
.toThrowError( .toThrowError(
/Template parse errors:\nNo provider for service \("\[ERROR ->\]<div needsServiceFromHost><div>"\): .*SimpleComponent.html@0:0/); /Template parse errors:\nNo provider for service \("\[ERROR ->\]<div needsServiceFromHost><div>"\): .*SimpleComponent.html@0:0/);
}); });
it('should not instantiate a directive in a view that has a self dependency on a parent directive', fixmeIvy('unknown').it(
() => { 'should not instantiate a directive in a view that has a self dependency on a parent directive',
TestBed.configureTestingModule( () => {
{declarations: [SimpleDirective, NeedsDirectiveFromSelf]}); TestBed.configureTestingModule(
expect( {declarations: [SimpleDirective, NeedsDirectiveFromSelf]});
() => expect(
createComponent('<div simpleDirective><div needsDirectiveFromSelf></div></div>')) () => createComponent(
.toThrowError( '<div simpleDirective><div needsDirectiveFromSelf></div></div>'))
/Template parse errors:\nNo provider for SimpleDirective \("<div simpleDirective>\[ERROR ->\]<div needsDirectiveFromSelf><\/div><\/div>"\): .*TestComp.html@0:21/); .toThrowError(
}); /Template parse errors:\nNo provider for SimpleDirective \("<div simpleDirective>\[ERROR ->\]<div needsDirectiveFromSelf><\/div><\/div>"\): .*TestComp.html@0:21/);
});
it('should instantiate directives that depend on other directives', fakeAsync(() => { it('should instantiate directives that depend on other directives', fakeAsync(() => {
TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsDirective]}); TestBed.configureTestingModule({declarations: [SimpleDirective, NeedsDirective]});
@ -662,48 +673,54 @@ class TestComp {
expect(d.dependency).toBeNull(); expect(d.dependency).toBeNull();
}); });
it('should instantiate directives that depends on the host component', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsComponentFromHost]}); 'should instantiate directives that depends on the host component', () => {
TestBed.overrideComponent( TestBed.configureTestingModule(
SimpleComponent, {set: {template: '<div needsComponentFromHost></div>'}}); {declarations: [SimpleComponent, NeedsComponentFromHost]});
const el = createComponent('<div simpleComponent></div>'); TestBed.overrideComponent(
const d = el.children[0].children[0].injector.get(NeedsComponentFromHost); SimpleComponent, {set: {template: '<div needsComponentFromHost></div>'}});
expect(d.dependency).toBeAnInstanceOf(SimpleComponent); const el = createComponent('<div simpleComponent></div>');
}); const d = el.children[0].children[0].injector.get(NeedsComponentFromHost);
expect(d.dependency).toBeAnInstanceOf(SimpleComponent);
});
it('should instantiate host views for components that have a @Host dependency ', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule({declarations: [NeedsHostAppService]}); 'should instantiate host views for components that have a @Host dependency ', () => {
const el = createComponent('', [], NeedsHostAppService); TestBed.configureTestingModule({declarations: [NeedsHostAppService]});
expect(el.componentInstance.service).toEqual('appService'); const el = createComponent('', [], NeedsHostAppService);
}); expect(el.componentInstance.service).toEqual('appService');
});
it('should not instantiate directives that depend on other directives on the host element', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule( 'should not instantiate directives that depend on other directives on the host element',
{declarations: [SimpleComponent, SimpleDirective, NeedsDirectiveFromHost]}); () => {
TestBed.overrideComponent( TestBed.configureTestingModule(
SimpleComponent, {set: {template: '<div needsDirectiveFromHost></div>'}}); {declarations: [SimpleComponent, SimpleDirective, NeedsDirectiveFromHost]});
expect(() => createComponent('<div simpleComponent simpleDirective></div>')) TestBed.overrideComponent(
.toThrowError( SimpleComponent, {set: {template: '<div needsDirectiveFromHost></div>'}});
/Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]<div needsDirectiveFromHost><\/div>"\): .*SimpleComponent.html@0:0/); expect(() => createComponent('<div simpleComponent simpleDirective></div>'))
}); .toThrowError(
/Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]<div needsDirectiveFromHost><\/div>"\): .*SimpleComponent.html@0:0/);
});
it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector', fixmeIvy('unknown').it(
() => { 'should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',
@Component({template: ''}) () => {
class MyComp { @Component({template: ''})
constructor(public vc: ViewContainerRef) {} class MyComp {
} constructor(public vc: ViewContainerRef) {}
}
const compFixture = TestBed const compFixture = TestBed
.configureTestingModule({ .configureTestingModule({
declarations: [MyComp], declarations: [MyComp],
providers: [{provide: 'someToken', useValue: 'someValue'}] providers: [{provide: 'someToken', useValue: 'someValue'}]
}) })
.createComponent(MyComp); .createComponent(MyComp);
expect(compFixture.componentInstance.vc.parentInjector.get('someToken')) expect(compFixture.componentInstance.vc.parentInjector.get('someToken'))
.toBe('someValue'); .toBe('someValue');
}); });
}); });
describe('static attributes', () => { describe('static attributes', () => {
@ -734,85 +751,97 @@ class TestComp {
.toBe(el.children[0].nativeElement); .toBe(el.children[0].nativeElement);
}); });
it('should inject ChangeDetectorRef of the component\'s view into the component', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]}); 'should inject ChangeDetectorRef of the component\'s view into the component', () => {
const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>'); TestBed.configureTestingModule({declarations: [PushComponentNeedsChangeDetectorRef]});
cf.detectChanges(); const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>');
const compEl = cf.debugElement.children[0]; cf.detectChanges();
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); const compEl = cf.debugElement.children[0];
comp.counter = 1; const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
cf.detectChanges(); comp.counter = 1;
expect(compEl.nativeElement).toHaveText('0'); cf.detectChanges();
comp.changeDetectorRef.markForCheck(); expect(compEl.nativeElement).toHaveText('0');
cf.detectChanges(); comp.changeDetectorRef.markForCheck();
expect(compEl.nativeElement).toHaveText('1'); cf.detectChanges();
}); expect(compEl.nativeElement).toHaveText('1');
});
it('should inject ChangeDetectorRef of the containing component into directives', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule( 'should inject ChangeDetectorRef of the containing component into directives', () => {
{declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]}); TestBed.configureTestingModule({
TestBed.overrideComponent(PushComponentNeedsChangeDetectorRef, { declarations:
set: { [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
template: });
'{{counter}}<div directiveNeedsChangeDetectorRef></div><div *ngIf="true" directiveNeedsChangeDetectorRef></div>' TestBed.overrideComponent(PushComponentNeedsChangeDetectorRef, {
} set: {
}); template:
const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>'); '{{counter}}<div directiveNeedsChangeDetectorRef></div><div *ngIf="true" directiveNeedsChangeDetectorRef></div>'
cf.detectChanges(); }
const compEl = cf.debugElement.children[0]; });
const comp: PushComponentNeedsChangeDetectorRef = const cf = createComponentFixture('<div componentNeedsChangeDetectorRef></div>');
compEl.injector.get(PushComponentNeedsChangeDetectorRef); cf.detectChanges();
comp.counter = 1; const compEl = cf.debugElement.children[0];
cf.detectChanges(); const comp: PushComponentNeedsChangeDetectorRef =
expect(compEl.nativeElement).toHaveText('0'); compEl.injector.get(PushComponentNeedsChangeDetectorRef);
expect(compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef) comp.counter = 1;
.toEqual(comp.changeDetectorRef); cf.detectChanges();
expect(compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef) expect(compEl.nativeElement).toHaveText('0');
.toEqual(comp.changeDetectorRef); expect(
comp.changeDetectorRef.markForCheck(); compEl.children[0].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
cf.detectChanges(); .toEqual(comp.changeDetectorRef);
expect(compEl.nativeElement).toHaveText('1'); expect(
}); compEl.children[1].injector.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef)
.toEqual(comp.changeDetectorRef);
comp.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
});
it('should inject ChangeDetectorRef of a same element component into a directive', () => { fixmeIvy('unknown').it(
TestBed.configureTestingModule( 'should inject ChangeDetectorRef of a same element component into a directive', () => {
{declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]}); TestBed.configureTestingModule({
const cf = createComponentFixture( declarations:
'<div componentNeedsChangeDetectorRef directiveNeedsChangeDetectorRef></div>'); [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
cf.detectChanges(); });
const compEl = cf.debugElement.children[0]; const cf = createComponentFixture(
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); '<div componentNeedsChangeDetectorRef directiveNeedsChangeDetectorRef></div>');
const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef); cf.detectChanges();
comp.counter = 1; const compEl = cf.debugElement.children[0];
cf.detectChanges(); const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
expect(compEl.nativeElement).toHaveText('0'); const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef);
dir.changeDetectorRef.markForCheck(); comp.counter = 1;
cf.detectChanges(); cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1'); expect(compEl.nativeElement).toHaveText('0');
}); dir.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
});
it(`should not inject ChangeDetectorRef of a parent element's component into a directive`, () => { fixmeIvy('unknown').it(
TestBed `should not inject ChangeDetectorRef of a parent element's component into a directive`,
.configureTestingModule({ () => {
declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef] TestBed
}) .configureTestingModule({
.overrideComponent( declarations:
PushComponentNeedsChangeDetectorRef, [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
{set: {template: '<ng-content></ng-content>{{counter}}'}}); })
const cf = createComponentFixture( .overrideComponent(
'<div componentNeedsChangeDetectorRef><div directiveNeedsChangeDetectorRef></div></div>'); PushComponentNeedsChangeDetectorRef,
cf.detectChanges(); {set: {template: '<ng-content></ng-content>{{counter}}'}});
const compEl = cf.debugElement.children[0]; const cf = createComponentFixture(
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef); '<div componentNeedsChangeDetectorRef><div directiveNeedsChangeDetectorRef></div></div>');
const dirEl = compEl.children[0]; cf.detectChanges();
const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef); const compEl = cf.debugElement.children[0];
comp.counter = 1; const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
cf.detectChanges(); const dirEl = compEl.children[0];
expect(compEl.nativeElement).toHaveText('0'); const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef);
dir.changeDetectorRef.markForCheck(); comp.counter = 1;
cf.detectChanges(); cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0'); expect(compEl.nativeElement).toHaveText('0');
}); dir.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
});
it('should inject ViewContainerRef', () => { it('should inject ViewContainerRef', () => {
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]}); TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]});
@ -822,7 +851,7 @@ class TestComp {
.toBe(el.children[0].nativeElement); .toBe(el.children[0].nativeElement);
}); });
it('should inject ViewContainerRef', () => { fixmeIvy('unknown').it('should inject ViewContainerRef', () => {
@Component({template: ''}) @Component({template: ''})
class TestComp { class TestComp {
constructor(public vcr: ViewContainerRef) {} constructor(public vcr: ViewContainerRef) {}
@ -847,7 +876,7 @@ class TestComp {
expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue'); expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue');
}); });
it('should inject TemplateRef', () => { fixmeIvy('unknown').it('should inject TemplateRef', () => {
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]}); TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]});
const el = const el =
createComponent('<ng-template needsViewContainerRef needsTemplateRef></ng-template>'); createComponent('<ng-template needsViewContainerRef needsTemplateRef></ng-template>');
@ -855,7 +884,7 @@ class TestComp {
.toEqual(el.childNodes[0].injector.get(NeedsViewContainerRef).viewContainer.element); .toEqual(el.childNodes[0].injector.get(NeedsViewContainerRef).viewContainer.element);
}); });
it('should throw if there is no TemplateRef', () => { fixmeIvy('unknown').it('should throw if there is no TemplateRef', () => {
TestBed.configureTestingModule({declarations: [NeedsTemplateRef]}); TestBed.configureTestingModule({declarations: [NeedsTemplateRef]});
expect(() => createComponent('<div needsTemplateRef></div>')) expect(() => createComponent('<div needsTemplateRef></div>'))
.toThrowError(/No provider for TemplateRef!/); .toThrowError(/No provider for TemplateRef!/);
@ -870,7 +899,7 @@ class TestComp {
}); });
describe('pipes', () => { describe('pipes', () => {
it('should instantiate pipes that have dependencies', () => { fixmeIvy('unknown').it('should instantiate pipes that have dependencies', () => {
TestBed.configureTestingModule({declarations: [SimpleDirective, PipeNeedsService]}); TestBed.configureTestingModule({declarations: [SimpleDirective, PipeNeedsService]});
const el = createComponent( const el = createComponent(
@ -879,7 +908,7 @@ class TestComp {
expect(el.children[0].injector.get(SimpleDirective).value.service).toEqual('pipeService'); expect(el.children[0].injector.get(SimpleDirective).value.service).toEqual('pipeService');
}); });
it('should overwrite pipes with later entry in the pipes array', () => { fixmeIvy('unknown').it('should overwrite pipes with later entry in the pipes array', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [SimpleDirective, DuplicatePipe1, DuplicatePipe2]}); {declarations: [SimpleDirective, DuplicatePipe1, DuplicatePipe2]});
const el = createComponent('<div [simpleDirective]="true | duplicatePipe"></div>'); const el = createComponent('<div [simpleDirective]="true | duplicatePipe"></div>');
@ -898,7 +927,7 @@ class TestComp {
expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef); expect(el.children[0].injector.get(SimpleDirective).value.changeDetectorRef).toEqual(cdRef);
}); });
it('should cache pure pipes', () => { fixmeIvy('unknown').it('should cache pure pipes', () => {
TestBed.configureTestingModule({declarations: [SimpleDirective, PurePipe]}); TestBed.configureTestingModule({declarations: [SimpleDirective, PurePipe]});
const el = createComponent( const el = createComponent(
'<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>' + '<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>' +

View File

@ -9,6 +9,7 @@
import {SecurityContext} from '@angular/core'; import {SecurityContext} from '@angular/core';
import {ArgumentType, BindingFlags, NodeCheckFn, NodeFlags, Services, ViewData, ViewFlags, ViewState, asElementData, directiveDef, elementDef, rootRenderNodes} from '@angular/core/src/view/index'; import {ArgumentType, BindingFlags, NodeCheckFn, NodeFlags, Services, ViewData, ViewFlags, ViewState, asElementData, directiveDef, elementDef, rootRenderNodes} from '@angular/core/src/view/index';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {fixmeIvy} from '@angular/private/testing';
import {callMostRecentEventListenerHandler, compViewDef, createAndGetRootNodes, createRootView, isBrowser, recordNodeToRemove} from './helper'; import {callMostRecentEventListenerHandler, compViewDef, createAndGetRootNodes, createRootView, isBrowser, recordNodeToRemove} from './helper';
@ -199,62 +200,64 @@ const addEventListener = '__zone_symbol__addEventListener' as 'addEventListener'
}); });
if (isBrowser()) { if (isBrowser()) {
it('should support OnPush components', () => { fixmeIvy('FW-665: Discovery util fails with "Unable to find context associated with ..."')
let compInputValue: any; .it('should support OnPush components', () => {
class AComp { let compInputValue: any;
a: any; class AComp {
} a: any;
}
const update = jasmine.createSpy('updater'); const update = jasmine.createSpy('updater');
const addListenerSpy = spyOn(HTMLElement.prototype, addEventListener).and.callThrough(); const addListenerSpy =
spyOn(HTMLElement.prototype, addEventListener).and.callThrough();
const {view} = createAndGetRootNodes(compViewDef( const {view} = createAndGetRootNodes(compViewDef(
[ [
elementDef( elementDef(
0, NodeFlags.None, null, null, 1, 'div', null, null, null, null, 0, NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => { () => {
return compViewDef( return compViewDef(
[ [
elementDef( elementDef(
0, NodeFlags.None, null, null, 0, 'span', null, null, 0, NodeFlags.None, null, null, 0, 'span', null, null,
[[null !, 'click']]), [[null !, 'click']]),
], ],
update, null, ViewFlags.OnPush); update, null, ViewFlags.OnPush);
}), }),
directiveDef(1, NodeFlags.Component, null, 0, AComp, [], {a: [0, 'a']}), directiveDef(1, NodeFlags.Component, null, 0, AComp, [], {a: [0, 'a']}),
], ],
(check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); })); (check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); }));
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
// auto detach // auto detach
update.calls.reset(); update.calls.reset();
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled(); expect(update).not.toHaveBeenCalled();
// auto attach on input changes // auto attach on input changes
update.calls.reset(); update.calls.reset();
compInputValue = 'v1'; compInputValue = 'v1';
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
expect(update).toHaveBeenCalled(); expect(update).toHaveBeenCalled();
// auto detach // auto detach
update.calls.reset(); update.calls.reset();
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled(); expect(update).not.toHaveBeenCalled();
// auto attach on events // auto attach on events
callMostRecentEventListenerHandler(addListenerSpy, 'SomeEvent'); callMostRecentEventListenerHandler(addListenerSpy, 'SomeEvent');
update.calls.reset(); update.calls.reset();
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
expect(update).toHaveBeenCalled(); expect(update).toHaveBeenCalled();
// auto detach // auto detach
update.calls.reset(); update.calls.reset();
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled(); expect(update).not.toHaveBeenCalled();
}); });
} }
it('should not stop dirty checking views that threw errors in change detection', () => { it('should not stop dirty checking views that threw errors in change detection', () => {

View File

@ -11,6 +11,7 @@ import {getDebugContext} from '@angular/core/src/errors';
import {BindingFlags, NodeFlags, Services, ViewData, ViewDefinition, asElementData, elementDef} from '@angular/core/src/view/index'; import {BindingFlags, NodeFlags, Services, ViewData, ViewDefinition, asElementData, elementDef} from '@angular/core/src/view/index';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {fixmeIvy} from '@angular/private/testing';
import {ARG_TYPE_VALUES, callMostRecentEventListenerHandler, checkNodeInlineOrDynamic, compViewDef, createAndGetRootNodes, isBrowser, recordNodeToRemove} from './helper'; import {ARG_TYPE_VALUES, callMostRecentEventListenerHandler, checkNodeInlineOrDynamic, compViewDef, createAndGetRootNodes, isBrowser, recordNodeToRemove} from './helper';
@ -184,26 +185,27 @@ const removeEventListener = '__zone_symbol__removeEventListener' as 'removeEvent
return result; return result;
} }
it('should listen to DOM events', () => { fixmeIvy('FW-665: Discovery util fails with "Unable to find context associated with ..."')
const handleEventSpy = jasmine.createSpy('handleEvent'); .it('should listen to DOM events', () => {
const removeListenerSpy = const handleEventSpy = jasmine.createSpy('handleEvent');
spyOn(HTMLElement.prototype, removeEventListener).and.callThrough(); const removeListenerSpy =
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( spyOn(HTMLElement.prototype, removeEventListener).and.callThrough();
0, NodeFlags.None, null, null, 0, 'button', null, null, [[null !, 'click']], const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
handleEventSpy)])); 0, NodeFlags.None, null, null, 0, 'button', null, null, [[null !, 'click']],
handleEventSpy)]));
rootNodes[0].click(); rootNodes[0].click();
expect(handleEventSpy).toHaveBeenCalled(); expect(handleEventSpy).toHaveBeenCalled();
let handleEventArgs = handleEventSpy.calls.mostRecent().args; let handleEventArgs = handleEventSpy.calls.mostRecent().args;
expect(handleEventArgs[0]).toBe(view); expect(handleEventArgs[0]).toBe(view);
expect(handleEventArgs[1]).toBe('click'); expect(handleEventArgs[1]).toBe('click');
expect(handleEventArgs[2]).toBeTruthy(); expect(handleEventArgs[2]).toBeTruthy();
Services.destroyView(view); Services.destroyView(view);
expect(removeListenerSpy).toHaveBeenCalled(); expect(removeListenerSpy).toHaveBeenCalled();
}); });
it('should listen to window events', () => { it('should listen to window events', () => {
const handleEventSpy = jasmine.createSpy('handleEvent'); const handleEventSpy = jasmine.createSpy('handleEvent');
@ -251,49 +253,52 @@ const removeEventListener = '__zone_symbol__removeEventListener' as 'removeEvent
expect(removeListenerSpy).toHaveBeenCalled(); expect(removeListenerSpy).toHaveBeenCalled();
}); });
it('should preventDefault only if the handler returns false', () => { fixmeIvy('FW-665: Discovery util fails with "Unable to find context associated with ..."')
let eventHandlerResult: any; .it('should preventDefault only if the handler returns false', () => {
let preventDefaultSpy: jasmine.Spy = undefined !; let eventHandlerResult: any;
let preventDefaultSpy: jasmine.Spy = undefined !;
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
0, NodeFlags.None, null, null, 0, 'button', null, null, [[null !, 'click']], 0, NodeFlags.None, null, null, 0, 'button', null, null, [[null !, 'click']],
(view, eventName, event) => { (view, eventName, event) => {
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough(); preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
return eventHandlerResult; return eventHandlerResult;
})])); })]));
eventHandlerResult = undefined; eventHandlerResult = undefined;
rootNodes[0].click(); rootNodes[0].click();
expect(preventDefaultSpy).not.toHaveBeenCalled(); expect(preventDefaultSpy).not.toHaveBeenCalled();
eventHandlerResult = true; eventHandlerResult = true;
rootNodes[0].click(); rootNodes[0].click();
expect(preventDefaultSpy).not.toHaveBeenCalled(); expect(preventDefaultSpy).not.toHaveBeenCalled();
eventHandlerResult = 'someString'; eventHandlerResult = 'someString';
rootNodes[0].click(); rootNodes[0].click();
expect(preventDefaultSpy).not.toHaveBeenCalled(); expect(preventDefaultSpy).not.toHaveBeenCalled();
eventHandlerResult = false; eventHandlerResult = false;
rootNodes[0].click(); rootNodes[0].click();
expect(preventDefaultSpy).toHaveBeenCalled(); expect(preventDefaultSpy).toHaveBeenCalled();
}); });
it('should report debug info on event errors', () => { fixmeIvy('FW-665: Discovery util fails with "Unable to find context associated with ..."')
const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError'); .it('should report debug info on event errors', () => {
const addListenerSpy = spyOn(HTMLElement.prototype, addEventListener).and.callThrough(); const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError');
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( const addListenerSpy =
0, NodeFlags.None, null, null, 0, 'button', null, null, [[null !, 'click']], spyOn(HTMLElement.prototype, addEventListener).and.callThrough();
() => { throw new Error('Test'); })])); const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
0, NodeFlags.None, null, null, 0, 'button', null, null, [[null !, 'click']],
() => { throw new Error('Test'); })]));
callMostRecentEventListenerHandler(addListenerSpy, 'SomeEvent'); callMostRecentEventListenerHandler(addListenerSpy, 'SomeEvent');
const err = handleErrorSpy.calls.mostRecent().args[0]; const err = handleErrorSpy.calls.mostRecent().args[0];
expect(err).toBeTruthy(); expect(err).toBeTruthy();
expect(err.message).toBe('Test'); expect(err.message).toBe('Test');
const debugCtx = getDebugContext(err); const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view); expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(0); expect(debugCtx.nodeIndex).toBe(0);
}); });
}); });
} }
}); });