fix(ivy): don’t publish animation bindings as attributes (#27805)
Some of the animation tests have been failing because animation gets triggered multiple times. The reason for this is that the compiler was generating static attribute bindings in addition to dynamic bindings. This created multiple writes to the animation render which failed the tests. PR Close #27805
This commit is contained in:
parent
880b4aabdb
commit
1f1e77b641
|
@ -120,4 +120,70 @@ describe('r3_view_compiler', () => {
|
|||
expectEmit(result.source, bV_call, 'Incorrect bV call');
|
||||
});
|
||||
});
|
||||
|
||||
describe('animations', () => {
|
||||
it('should keep @attr but suppress [@attr]', () => {
|
||||
const files: MockDirectory = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<div @attrOnly [@myAnimation]="exp"></div>'
|
||||
})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyApp]})
|
||||
export class MyModule {}`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ["@attrOnly", ""];
|
||||
// ...
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵelement(0, "div", _c0);
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect initialization attributes');
|
||||
});
|
||||
|
||||
it('should dedup multiple [@event] listeners', () => {
|
||||
const files: MockDirectory = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<div (@mySelector.start)="false" (@mySelector.done)="false" [@mySelector]="0"></div>'
|
||||
})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyApp]})
|
||||
export class MyModule {}`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = [1, "mySelector"];
|
||||
// ...
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵelementStart(0, "div", _c0);
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect initialization attributes');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -214,7 +214,6 @@ describe('compiler compliance: styling', () => {
|
|||
};
|
||||
|
||||
const template = `
|
||||
const $e0_attrs$ = ["@foo", ""];
|
||||
const $e1_attrs$ = ["@bar", ""];
|
||||
const $e2_attrs$ = ["@baz", ""];
|
||||
…
|
||||
|
@ -224,7 +223,7 @@ describe('compiler compliance: styling', () => {
|
|||
vars: 1,
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelement(0, "div", $e0_attrs$);
|
||||
$r3$.ɵelement(0, "div");
|
||||
$r3$.ɵelement(1, "div", $e1_attrs$);
|
||||
$r3$.ɵelement(2, "div", $e2_attrs$);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import {flatten, sanitizeIdentifier} from '../../compile_metadata';
|
|||
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||
import {ConstantPool} from '../../constant_pool';
|
||||
import * as core from '../../core';
|
||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, ParsedEventType, PropertyRead} from '../../expression_parser/ast';
|
||||
import {AST, ASTWithSource, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, ParsedEventType, PropertyRead} from '../../expression_parser/ast';
|
||||
import {Lexer} from '../../expression_parser/lexer';
|
||||
import {Parser} from '../../expression_parser/parser';
|
||||
import * as i18n from '../../i18n/i18n_ast';
|
||||
|
@ -967,6 +967,29 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
styles?: StylingBuilder): o.Expression[] {
|
||||
const attrExprs: o.Expression[] = [];
|
||||
const nonSyntheticInputs: t.BoundAttribute[] = [];
|
||||
const alreadySeen = new Set<string>();
|
||||
|
||||
function isASTWithSource(ast: AST): ast is ASTWithSource {
|
||||
return ast instanceof ASTWithSource;
|
||||
}
|
||||
|
||||
function isLiteralPrimitive(ast: AST): ast is LiteralPrimitive {
|
||||
return ast instanceof LiteralPrimitive;
|
||||
}
|
||||
|
||||
function addAttrExpr(key: string | number, value?: o.Expression): void {
|
||||
if (typeof key === 'string') {
|
||||
if (!alreadySeen.has(key)) {
|
||||
attrExprs.push(o.literal(key));
|
||||
if (value !== undefined) {
|
||||
attrExprs.push(value);
|
||||
}
|
||||
alreadySeen.add(key);
|
||||
}
|
||||
} else {
|
||||
attrExprs.push(o.literal(key));
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.length) {
|
||||
const EMPTY_STRING_EXPR = asLiteral('');
|
||||
|
@ -976,7 +999,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
// may be supported differently in future versions of angular. However,
|
||||
// @triggers should always just be treated as regular attributes (it's up
|
||||
// to the renderer to detect and use them in a special way).
|
||||
attrExprs.push(asLiteral(prepareSyntheticAttributeName(input.name)), EMPTY_STRING_EXPR);
|
||||
const valueExp = input.value;
|
||||
if (isASTWithSource(valueExp)) {
|
||||
const literal = valueExp.ast;
|
||||
if (isLiteralPrimitive(literal) && literal.value === undefined) {
|
||||
addAttrExpr(prepareSyntheticAttributeName(input.name), EMPTY_STRING_EXPR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nonSyntheticInputs.push(input);
|
||||
}
|
||||
|
@ -991,9 +1020,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
}
|
||||
|
||||
if (nonSyntheticInputs.length || outputs.length) {
|
||||
attrExprs.push(o.literal(core.AttributeMarker.SelectOnly));
|
||||
nonSyntheticInputs.forEach((i: t.BoundAttribute) => attrExprs.push(asLiteral(i.name)));
|
||||
outputs.forEach((o: t.BoundEvent) => attrExprs.push(asLiteral(o.name)));
|
||||
addAttrExpr(core.AttributeMarker.SelectOnly);
|
||||
nonSyntheticInputs.forEach((i: t.BoundAttribute) => addAttrExpr(i.name));
|
||||
outputs.forEach((o: t.BoundEvent) => addAttrExpr(o.name));
|
||||
}
|
||||
|
||||
return attrExprs;
|
||||
|
|
|
@ -394,8 +394,9 @@ function renderComponentOrTemplate<T>(
|
|||
hostView: LView, context: T, templateFn?: ComponentTemplate<T>) {
|
||||
const rendererFactory = hostView[RENDERER_FACTORY];
|
||||
const oldView = enterView(hostView, hostView[HOST_NODE]);
|
||||
const normalExecutionPath = !getCheckNoChangesMode();
|
||||
try {
|
||||
if (rendererFactory.begin) {
|
||||
if (normalExecutionPath && rendererFactory.begin) {
|
||||
rendererFactory.begin();
|
||||
}
|
||||
|
||||
|
@ -414,7 +415,7 @@ function renderComponentOrTemplate<T>(
|
|||
templateFn && templateFn(RenderFlags.Update, context !);
|
||||
refreshDescendantViews(hostView);
|
||||
} finally {
|
||||
if (rendererFactory.end) {
|
||||
if (normalExecutionPath && rendererFactory.end) {
|
||||
rendererFactory.end();
|
||||
}
|
||||
leaveView(oldView);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -853,12 +853,11 @@ import {HostListener} from '../../src/metadata/directives';
|
|||
});
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should cleanup :enter and :leave artifacts from nodes when any animation sequences fail to be built',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
template: `
|
||||
it('should cleanup :enter and :leave artifacts from nodes when any animation sequences fail to be built',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="items.length" class="parent" #container>
|
||||
<div *ngFor="let item of items" class="child">
|
||||
{{ item }}
|
||||
|
@ -866,56 +865,56 @@ import {HostListener} from '../../src/metadata/directives';
|
|||
<div *ngIf="items.length == 0" class="child">Leave!</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition('* => 0', []),
|
||||
transition(
|
||||
'* => *',
|
||||
[
|
||||
query(
|
||||
'.child:enter',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate(1000, style({opacity: 1})),
|
||||
]),
|
||||
query(
|
||||
'.incorrect-child:leave',
|
||||
[
|
||||
animate(1000, style({opacity: 0})),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
@ViewChild('container') public container: any;
|
||||
public items: any[] = [];
|
||||
}
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition('* => 0', []),
|
||||
transition(
|
||||
'* => *',
|
||||
[
|
||||
query(
|
||||
'.child:enter',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate(1000, style({opacity: 1})),
|
||||
]),
|
||||
query(
|
||||
'.incorrect-child:leave',
|
||||
[
|
||||
animate(1000, style({opacity: 0})),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
@ViewChild('container') public container: any;
|
||||
public items: any[] = [];
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.items = [];
|
||||
fixture.detectChanges();
|
||||
cmp.items = [];
|
||||
fixture.detectChanges();
|
||||
|
||||
cmp.items = [0, 1, 2, 3, 4];
|
||||
cmp.items = [0, 1, 2, 3, 4];
|
||||
|
||||
expect(() => { fixture.detectChanges(); }).toThrow();
|
||||
expect(() => { fixture.detectChanges(); }).toThrow();
|
||||
|
||||
const children = cmp.container.nativeElement.querySelectorAll('.child');
|
||||
expect(children.length).toEqual(5);
|
||||
const children = cmp.container.nativeElement.querySelectorAll('.child');
|
||||
expect(children.length).toEqual(5);
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
expect(child.classList.contains(ENTER_CLASSNAME)).toBe(false);
|
||||
expect(child.classList.contains(LEAVE_CLASSNAME)).toBe(false);
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
expect(child.classList.contains(ENTER_CLASSNAME)).toBe(false);
|
||||
expect(child.classList.contains(LEAVE_CLASSNAME)).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should find elements that have been removed via :leave', () => {
|
||||
@Component({
|
||||
|
@ -2520,9 +2519,8 @@ import {HostListener} from '../../src/metadata/directives';
|
|||
expect(p4.element.classList.contains('d'));
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should collect multiple root levels of :enter and :leave nodes', () => {
|
||||
@Component({
|
||||
it('should collect multiple root levels of :enter and :leave nodes', () => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
animations: [
|
||||
trigger('pageAnimation', [
|
||||
|
@ -2557,88 +2555,88 @@ import {HostListener} from '../../src/metadata/directives';
|
|||
`
|
||||
})
|
||||
class Cmp {
|
||||
get title() {
|
||||
if (this.page1) {
|
||||
return 'hello from page1';
|
||||
}
|
||||
return 'greetings from page2';
|
||||
}
|
||||
|
||||
page1 = false;
|
||||
page2 = false;
|
||||
loading = false;
|
||||
|
||||
get status() {
|
||||
if (this.loading) return 'loading';
|
||||
if (this.page1) return 'page1';
|
||||
if (this.page2) return 'page2';
|
||||
return '';
|
||||
}
|
||||
get title() {
|
||||
if (this.page1) {
|
||||
return 'hello from page1';
|
||||
}
|
||||
return 'greetings from page2';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
page1 = false;
|
||||
page2 = false;
|
||||
loading = false;
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.loading = true;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
get status() {
|
||||
if (this.loading) return 'loading';
|
||||
if (this.page1) return 'page1';
|
||||
if (this.page2) return 'page2';
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
let players = getLog();
|
||||
resetLog();
|
||||
cancelAllPlayers(players);
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
cmp.page1 = true;
|
||||
cmp.loading = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.loading = true;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
let p1: MockAnimationPlayer;
|
||||
let p2: MockAnimationPlayer;
|
||||
let p3: MockAnimationPlayer;
|
||||
let players = getLog();
|
||||
resetLog();
|
||||
cancelAllPlayers(players);
|
||||
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(3);
|
||||
[p1, p2, p3] = players;
|
||||
cmp.page1 = true;
|
||||
cmp.loading = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
expect(p1.element.classList.contains('loading')).toBe(true);
|
||||
expect(p2.element.classList.contains('title')).toBe(true);
|
||||
expect(p3.element.classList.contains('page1')).toBe(true);
|
||||
let p1: MockAnimationPlayer;
|
||||
let p2: MockAnimationPlayer;
|
||||
let p3: MockAnimationPlayer;
|
||||
|
||||
resetLog();
|
||||
cancelAllPlayers(players);
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(3);
|
||||
[p1, p2, p3] = players;
|
||||
|
||||
cmp.page1 = false;
|
||||
cmp.loading = true;
|
||||
fixture.detectChanges();
|
||||
expect(p1.element.classList.contains('loading')).toBe(true);
|
||||
expect(p2.element.classList.contains('title')).toBe(true);
|
||||
expect(p3.element.classList.contains('page1')).toBe(true);
|
||||
|
||||
players = getLog();
|
||||
cancelAllPlayers(players);
|
||||
resetLog();
|
||||
cancelAllPlayers(players);
|
||||
|
||||
expect(players.length).toEqual(3);
|
||||
[p1, p2, p3] = players;
|
||||
cmp.page1 = false;
|
||||
cmp.loading = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(p1.element.classList.contains('title')).toBe(true);
|
||||
expect(p2.element.classList.contains('page1')).toBe(true);
|
||||
expect(p3.element.classList.contains('loading')).toBe(true);
|
||||
players = getLog();
|
||||
cancelAllPlayers(players);
|
||||
|
||||
resetLog();
|
||||
cancelAllPlayers(players);
|
||||
expect(players.length).toEqual(3);
|
||||
[p1, p2, p3] = players;
|
||||
|
||||
cmp.page2 = true;
|
||||
cmp.loading = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
expect(p1.element.classList.contains('title')).toBe(true);
|
||||
expect(p2.element.classList.contains('page1')).toBe(true);
|
||||
expect(p3.element.classList.contains('loading')).toBe(true);
|
||||
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(3);
|
||||
[p1, p2, p3] = players;
|
||||
resetLog();
|
||||
cancelAllPlayers(players);
|
||||
|
||||
expect(p1.element.classList.contains('loading')).toBe(true);
|
||||
expect(p2.element.classList.contains('title')).toBe(true);
|
||||
expect(p3.element.classList.contains('page2')).toBe(true);
|
||||
});
|
||||
cmp.page2 = true;
|
||||
cmp.loading = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(3);
|
||||
[p1, p2, p3] = players;
|
||||
|
||||
expect(p1.element.classList.contains('loading')).toBe(true);
|
||||
expect(p2.element.classList.contains('title')).toBe(true);
|
||||
expect(p3.element.classList.contains('page2')).toBe(true);
|
||||
});
|
||||
|
||||
it('should emulate leave animation callbacks for all sub elements that have leave triggers within the component',
|
||||
fakeAsync(() => {
|
||||
|
|
|
@ -34,354 +34,349 @@ import {RouterTestingModule} from '@angular/router/testing';
|
|||
});
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should query the old and new routes via :leave and :enter', fakeAsync(() => {
|
||||
@Component({
|
||||
animations: [
|
||||
trigger(
|
||||
'routerAnimations',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query(':leave', animateChild()),
|
||||
query(':enter', animateChild()),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
it('should query the old and new routes via :leave and :enter', fakeAsync(() => {
|
||||
@Component({
|
||||
animations: [
|
||||
trigger(
|
||||
'routerAnimations',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query(':leave', animateChild()),
|
||||
query(':enter', animateChild()),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
<div [@routerAnimations]="prepareRouteAnimation(r)">
|
||||
<router-outlet #r="outlet"></router-outlet>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class ContainerCmp {
|
||||
constructor(public router: Router) {}
|
||||
})
|
||||
class ContainerCmp {
|
||||
constructor(public router: Router) {}
|
||||
|
||||
prepareRouteAnimation(r: RouterOutlet) {
|
||||
const animation = r.activatedRouteData['animation'];
|
||||
const value = animation ? animation['value'] : null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
prepareRouteAnimation(r: RouterOutlet) {
|
||||
const animation = r.activatedRouteData['animation'];
|
||||
const value = animation ? animation['value'] : null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page1',
|
||||
template: `page1`,
|
||||
animations: [
|
||||
trigger(
|
||||
'page1Animation',
|
||||
[
|
||||
transition(
|
||||
':leave',
|
||||
[
|
||||
style({width: '200px'}),
|
||||
animate(1000, style({width: '0px'})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Page1Cmp {
|
||||
@HostBinding('@page1Animation') public doAnimate = true;
|
||||
}
|
||||
@Component({
|
||||
selector: 'page1',
|
||||
template: `page1`,
|
||||
animations: [
|
||||
trigger(
|
||||
'page1Animation',
|
||||
[
|
||||
transition(
|
||||
':leave',
|
||||
[
|
||||
style({width: '200px'}),
|
||||
animate(1000, style({width: '0px'})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Page1Cmp {
|
||||
@HostBinding('@page1Animation') public doAnimate = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page2',
|
||||
template: `page2`,
|
||||
animations: [
|
||||
trigger(
|
||||
'page2Animation',
|
||||
[
|
||||
transition(
|
||||
':enter',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate(1000, style({opacity: 1})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Page2Cmp {
|
||||
@HostBinding('@page2Animation') public doAnimate = true;
|
||||
}
|
||||
@Component({
|
||||
selector: 'page2',
|
||||
template: `page2`,
|
||||
animations: [
|
||||
trigger(
|
||||
'page2Animation',
|
||||
[
|
||||
transition(
|
||||
':enter',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate(1000, style({opacity: 1})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Page2Cmp {
|
||||
@HostBinding('@page2Animation') public doAnimate = true;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
const player = engine.players[0] !;
|
||||
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
|
||||
const players = groupPlayer.players as MockAnimationPlayer[];
|
||||
const player = engine.players[0] !;
|
||||
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
|
||||
const players = groupPlayer.players as MockAnimationPlayer[];
|
||||
|
||||
expect(players.length).toEqual(2);
|
||||
const [p1, p2] = players;
|
||||
expect(players.length).toEqual(2);
|
||||
const [p1, p2] = players;
|
||||
|
||||
expect(p1.duration).toEqual(1000);
|
||||
expect(p1.keyframes).toEqual([
|
||||
{offset: 0, width: '200px'},
|
||||
{offset: 1, width: '0px'},
|
||||
]);
|
||||
expect(p1.duration).toEqual(1000);
|
||||
expect(p1.keyframes).toEqual([
|
||||
{offset: 0, width: '200px'},
|
||||
{offset: 1, width: '0px'},
|
||||
]);
|
||||
|
||||
expect(p2.duration).toEqual(2000);
|
||||
expect(p2.keyframes).toEqual([
|
||||
{offset: 0, opacity: '0'},
|
||||
{offset: .5, opacity: '0'},
|
||||
{offset: 1, opacity: '1'},
|
||||
]);
|
||||
}));
|
||||
expect(p2.duration).toEqual(2000);
|
||||
expect(p2.keyframes).toEqual([
|
||||
{offset: 0, opacity: '0'},
|
||||
{offset: .5, opacity: '0'},
|
||||
{offset: 1, opacity: '1'},
|
||||
]);
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should allow inner enter animations to be emulated within a routed item', fakeAsync(() => {
|
||||
@Component({
|
||||
animations: [
|
||||
trigger(
|
||||
'routerAnimations',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query(':enter', animateChild()),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
it('should allow inner enter animations to be emulated within a routed item', fakeAsync(() => {
|
||||
@Component({
|
||||
animations: [
|
||||
trigger(
|
||||
'routerAnimations',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query(':enter', animateChild()),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
<div [@routerAnimations]="prepareRouteAnimation(r)">
|
||||
<router-outlet #r="outlet"></router-outlet>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class ContainerCmp {
|
||||
constructor(public router: Router) {}
|
||||
})
|
||||
class ContainerCmp {
|
||||
constructor(public router: Router) {}
|
||||
|
||||
prepareRouteAnimation(r: RouterOutlet) {
|
||||
const animation = r.activatedRouteData['animation'];
|
||||
const value = animation ? animation['value'] : null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
prepareRouteAnimation(r: RouterOutlet) {
|
||||
const animation = r.activatedRouteData['animation'];
|
||||
const value = animation ? animation['value'] : null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'page1', template: `page1`, animations: []})
|
||||
class Page1Cmp {
|
||||
}
|
||||
@Component({selector: 'page1', template: `page1`, animations: []})
|
||||
class Page1Cmp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page2',
|
||||
template: `
|
||||
@Component({
|
||||
selector: 'page2',
|
||||
template: `
|
||||
<h1>Page 2</h1>
|
||||
<div *ngIf="exp" class="if-one" @ifAnimation></div>
|
||||
<div *ngIf="exp" class="if-two" @ifAnimation></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'page2Animation',
|
||||
[
|
||||
transition(
|
||||
':enter',
|
||||
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
|
||||
]),
|
||||
trigger(
|
||||
'ifAnimation',
|
||||
[transition(
|
||||
':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])])
|
||||
]
|
||||
})
|
||||
class Page2Cmp {
|
||||
@HostBinding('@page2Animation') public doAnimate = true;
|
||||
animations: [
|
||||
trigger(
|
||||
'page2Animation',
|
||||
[
|
||||
transition(
|
||||
':enter',
|
||||
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
|
||||
]),
|
||||
trigger(
|
||||
'ifAnimation',
|
||||
[transition(
|
||||
':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])])
|
||||
]
|
||||
})
|
||||
class Page2Cmp {
|
||||
@HostBinding('@page2Animation') public doAnimate = true;
|
||||
|
||||
public exp = true;
|
||||
}
|
||||
public exp = true;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
const player = engine.players[0] !;
|
||||
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
|
||||
const players = groupPlayer.players as MockAnimationPlayer[];
|
||||
const player = engine.players[0] !;
|
||||
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
|
||||
const players = groupPlayer.players as MockAnimationPlayer[];
|
||||
|
||||
expect(players.length).toEqual(2);
|
||||
const [p1, p2] = players;
|
||||
expect(players.length).toEqual(2);
|
||||
const [p1, p2] = players;
|
||||
|
||||
expect(p1.keyframes).toEqual([
|
||||
{offset: 0, opacity: '0'},
|
||||
{offset: 1, opacity: '1'},
|
||||
]);
|
||||
expect(p1.keyframes).toEqual([
|
||||
{offset: 0, opacity: '0'},
|
||||
{offset: 1, opacity: '1'},
|
||||
]);
|
||||
|
||||
expect(p2.keyframes).toEqual([
|
||||
{offset: 0, opacity: '0'},
|
||||
{offset: .5, opacity: '0'},
|
||||
{offset: 1, opacity: '1'},
|
||||
]);
|
||||
}));
|
||||
expect(p2.keyframes).toEqual([
|
||||
{offset: 0, opacity: '0'},
|
||||
{offset: .5, opacity: '0'},
|
||||
{offset: 1, opacity: '1'},
|
||||
]);
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should allow inner leave animations to be emulated within a routed item', fakeAsync(() => {
|
||||
@Component({
|
||||
animations: [
|
||||
trigger(
|
||||
'routerAnimations',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query(':leave', animateChild()),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
it('should allow inner leave animations to be emulated within a routed item', fakeAsync(() => {
|
||||
@Component({
|
||||
animations: [
|
||||
trigger(
|
||||
'routerAnimations',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query(':leave', animateChild()),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
<div [@routerAnimations]="prepareRouteAnimation(r)">
|
||||
<router-outlet #r="outlet"></router-outlet>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class ContainerCmp {
|
||||
constructor(public router: Router) {}
|
||||
})
|
||||
class ContainerCmp {
|
||||
constructor(public router: Router) {}
|
||||
|
||||
prepareRouteAnimation(r: RouterOutlet) {
|
||||
const animation = r.activatedRouteData['animation'];
|
||||
const value = animation ? animation['value'] : null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
prepareRouteAnimation(r: RouterOutlet) {
|
||||
const animation = r.activatedRouteData['animation'];
|
||||
const value = animation ? animation['value'] : null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page1',
|
||||
template: `
|
||||
@Component({
|
||||
selector: 'page1',
|
||||
template: `
|
||||
<h1>Page 1</h1>
|
||||
<div *ngIf="exp" class="if-one" @ifAnimation></div>
|
||||
<div *ngIf="exp" class="if-two" @ifAnimation></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'page1Animation',
|
||||
[
|
||||
transition(
|
||||
':leave',
|
||||
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
|
||||
]),
|
||||
trigger(
|
||||
'ifAnimation',
|
||||
[transition(
|
||||
':leave', [style({opacity: 1}), animate(1000, style({opacity: 0}))])]),
|
||||
]
|
||||
})
|
||||
class Page1Cmp {
|
||||
@HostBinding('@page1Animation') public doAnimate = true;
|
||||
animations: [
|
||||
trigger(
|
||||
'page1Animation',
|
||||
[
|
||||
transition(
|
||||
':leave',
|
||||
[query('.if-one', animateChild()), query('.if-two', animateChild())]),
|
||||
]),
|
||||
trigger(
|
||||
'ifAnimation',
|
||||
[transition(':leave', [style({opacity: 1}), animate(1000, style({opacity: 0}))])]),
|
||||
]
|
||||
})
|
||||
class Page1Cmp {
|
||||
@HostBinding('@page1Animation') public doAnimate = true;
|
||||
|
||||
public exp = true;
|
||||
}
|
||||
public exp = true;
|
||||
}
|
||||
|
||||
@Component({selector: 'page2', template: `page2`, animations: []})
|
||||
class Page2Cmp {
|
||||
}
|
||||
@Component({selector: 'page2', template: `page2`, animations: []})
|
||||
class Page2Cmp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
const player = engine.players[0] !;
|
||||
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
|
||||
const players = groupPlayer.players as MockAnimationPlayer[];
|
||||
const player = engine.players[0] !;
|
||||
const groupPlayer = player.getRealPlayer() as AnimationGroupPlayer;
|
||||
const players = groupPlayer.players as MockAnimationPlayer[];
|
||||
|
||||
expect(players.length).toEqual(2);
|
||||
const [p1, p2] = players;
|
||||
expect(players.length).toEqual(2);
|
||||
const [p1, p2] = players;
|
||||
|
||||
expect(p1.keyframes).toEqual([
|
||||
{offset: 0, opacity: '1'},
|
||||
{offset: 1, opacity: '0'},
|
||||
]);
|
||||
expect(p1.keyframes).toEqual([
|
||||
{offset: 0, opacity: '1'},
|
||||
{offset: 1, opacity: '0'},
|
||||
]);
|
||||
|
||||
expect(p2.keyframes).toEqual([
|
||||
{offset: 0, opacity: '1'},
|
||||
{offset: .5, opacity: '1'},
|
||||
{offset: 1, opacity: '0'},
|
||||
]);
|
||||
}));
|
||||
expect(p2.keyframes).toEqual([
|
||||
{offset: 0, opacity: '1'},
|
||||
{offset: .5, opacity: '1'},
|
||||
{offset: 1, opacity: '0'},
|
||||
]);
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should properly collect :enter / :leave router nodes even when another non-router *template component is within the trigger boundaries',
|
||||
fakeAsync(() => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
animations: [
|
||||
trigger(
|
||||
'pageAnimation',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query('.router-container :leave', animate('1s', style({opacity: 0}))),
|
||||
query('.router-container :enter', animate('1s', style({opacity: 1}))),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
it('should properly collect :enter / :leave router nodes even when another non-router *template component is within the trigger boundaries',
|
||||
fakeAsync(() => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
animations: [
|
||||
trigger(
|
||||
'pageAnimation',
|
||||
[
|
||||
transition(
|
||||
'page1 => page2',
|
||||
[
|
||||
query('.router-container :leave', animate('1s', style({opacity: 0}))),
|
||||
query('.router-container :enter', animate('1s', style({opacity: 1}))),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
<div [@pageAnimation]="prepRoute(outlet)">
|
||||
<header>
|
||||
<div class="inner">
|
||||
|
@ -394,139 +389,136 @@ import {RouterTestingModule} from '@angular/router/testing';
|
|||
</section>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class ContainerCmp {
|
||||
loading = false;
|
||||
})
|
||||
class ContainerCmp {
|
||||
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`})
|
||||
class Page1Cmp {
|
||||
}
|
||||
@Component({selector: 'page1', template: `page1`})
|
||||
class Page1Cmp {
|
||||
}
|
||||
|
||||
@Component({selector: 'page2', template: `page2`})
|
||||
class Page2Cmp {
|
||||
}
|
||||
@Component({selector: 'page2', template: `page2`})
|
||||
class Page2Cmp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Page1Cmp, Page2Cmp, ContainerCmp],
|
||||
imports: [RouterTestingModule.withRoutes([
|
||||
{path: 'page1', component: Page1Cmp, data: makeAnimationData('page1')},
|
||||
{path: 'page2', component: Page2Cmp, data: makeAnimationData('page2')}
|
||||
])]
|
||||
});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.router.initialNavigation();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
cmp.loading = true;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page1');
|
||||
tick();
|
||||
cmp.loading = true;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
cmp.loading = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.router.navigateByUrl('/page2');
|
||||
tick();
|
||||
cmp.loading = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
const players = engine.players;
|
||||
expect(players.length).toEqual(1);
|
||||
const [p1] = players;
|
||||
const players = engine.players;
|
||||
expect(players.length).toEqual(1);
|
||||
const [p1] = players;
|
||||
|
||||
const innerPlayers = p1.getRealPlayer().players;
|
||||
expect(innerPlayers.length).toEqual(2);
|
||||
const innerPlayers = p1.getRealPlayer().players;
|
||||
expect(innerPlayers.length).toEqual(2);
|
||||
|
||||
const [ip1, ip2] = innerPlayers;
|
||||
expect(ip1.element.innerText).toEqual('page1');
|
||||
expect(ip2.element.innerText).toEqual('page2');
|
||||
}));
|
||||
const [ip1, ip2] = innerPlayers;
|
||||
expect(ip1.element.innerText).toEqual('page1');
|
||||
expect(ip2.element.innerText).toEqual('page2');
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should allow a recursive set of :leave animations to occur for nested routes',
|
||||
fakeAsync(() => {
|
||||
@Component(
|
||||
{selector: 'ani-cmp', template: '<router-outlet name="recur"></router-outlet>'})
|
||||
class ContainerCmp {
|
||||
constructor(private _router: Router) {}
|
||||
log: string[] = [];
|
||||
it('should allow a recursive set of :leave animations to occur for nested routes',
|
||||
fakeAsync(() => {
|
||||
@Component({selector: 'ani-cmp', template: '<router-outlet name="recur"></router-outlet>'})
|
||||
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({
|
||||
selector: 'recur-page',
|
||||
template: 'Depth: {{ depth }} \n <router-outlet></router-outlet>',
|
||||
animations: [
|
||||
trigger(
|
||||
'pageAnimations',
|
||||
[
|
||||
transition(
|
||||
':leave', [group([
|
||||
sequence([style({opacity: 1}), animate('1s', style({opacity: 0}))]),
|
||||
query('@*', animateChild(), {optional: true})
|
||||
])]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class RecurPageCmp {
|
||||
@HostBinding('@pageAnimations') public animatePage = true;
|
||||
@Component({
|
||||
selector: 'recur-page',
|
||||
template: 'Depth: {{ depth }} \n <router-outlet></router-outlet>',
|
||||
animations: [
|
||||
trigger(
|
||||
'pageAnimations',
|
||||
[
|
||||
transition(':leave', [group([
|
||||
sequence([style({opacity: 1}), animate('1s', style({opacity: 0}))]),
|
||||
query('@*', animateChild(), {optional: 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) {
|
||||
this.route.data.subscribe(data => {
|
||||
this.container.log.push(`DEPTH ${data.depth}`);
|
||||
this.depth = data.depth;
|
||||
});
|
||||
}
|
||||
}
|
||||
constructor(private container: ContainerCmp, private route: ActivatedRoute) {
|
||||
this.route.data.subscribe(data => {
|
||||
this.container.log.push(`DEPTH ${data.depth}`);
|
||||
this.depth = data.depth;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ContainerCmp, RecurPageCmp],
|
||||
imports: [RouterTestingModule.withRoutes([{
|
||||
path: 'recur',
|
||||
component: RecurPageCmp,
|
||||
outlet: 'recur',
|
||||
data: {depth: 0},
|
||||
children: [{path: 'nested', component: RecurPageCmp, data: {depth: 1}}]
|
||||
}])]
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ContainerCmp, RecurPageCmp],
|
||||
imports: [RouterTestingModule.withRoutes([{
|
||||
path: 'recur',
|
||||
component: RecurPageCmp,
|
||||
outlet: 'recur',
|
||||
data: {depth: 0},
|
||||
children: [{path: 'nested', component: RecurPageCmp, data: {depth: 1}}]
|
||||
}])]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.enter();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
const fixture = TestBed.createComponent(ContainerCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.enter();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(cmp.log).toEqual([
|
||||
'DEPTH 0',
|
||||
'DEPTH 1',
|
||||
]);
|
||||
expect(cmp.log).toEqual([
|
||||
'DEPTH 0',
|
||||
'DEPTH 1',
|
||||
]);
|
||||
|
||||
cmp.leave();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
cmp.leave();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(2);
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(2);
|
||||
|
||||
const [p1, p2] = players;
|
||||
expect(p1.element.getAttribute('data-depth')).toEqual('0');
|
||||
expect(p2.element.getAttribute('data-depth')).toEqual('1');
|
||||
}));
|
||||
const [p1, p2] = players;
|
||||
expect(p1.element.getAttribute('data-depth')).toEqual('0');
|
||||
expect(p2.element.getAttribute('data-depth')).toEqual('1');
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -242,10 +242,9 @@ import {fixmeIvy} from '@angular/private/testing';
|
|||
]);
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should treat * styles as ! for queried items that are collected in a container that is being removed',
|
||||
() => {
|
||||
@Component({
|
||||
it('should treat * styles as ! for queried items that are collected in a container that is being removed',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
styles: [`
|
||||
.list .outer {
|
||||
|
@ -286,58 +285,58 @@ import {fixmeIvy} from '@angular/private/testing';
|
|||
]
|
||||
})
|
||||
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 fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.empty();
|
||||
fixture.detectChanges();
|
||||
let player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
player.finish();
|
||||
cmp.empty();
|
||||
fixture.detectChanges();
|
||||
let player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
player.finish();
|
||||
|
||||
cmp.full();
|
||||
fixture.detectChanges();
|
||||
cmp.full();
|
||||
fixture.detectChanges();
|
||||
|
||||
player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
let queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
|
||||
expect(queriedPlayers.length).toEqual(5);
|
||||
player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
let queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
|
||||
expect(queriedPlayers.length).toEqual(5);
|
||||
|
||||
let i = 0;
|
||||
for (i = 0; i < queriedPlayers.length; i++) {
|
||||
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{height: '0px', offset: 0},
|
||||
{height: '50px', offset: 1},
|
||||
]);
|
||||
player.finish();
|
||||
}
|
||||
let i = 0;
|
||||
for (i = 0; i < queriedPlayers.length; i++) {
|
||||
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{height: '0px', offset: 0},
|
||||
{height: '50px', offset: 1},
|
||||
]);
|
||||
player.finish();
|
||||
}
|
||||
|
||||
cmp.empty();
|
||||
fixture.detectChanges();
|
||||
cmp.empty();
|
||||
fixture.detectChanges();
|
||||
|
||||
player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
|
||||
expect(queriedPlayers.length).toEqual(5);
|
||||
player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
|
||||
expect(queriedPlayers.length).toEqual(5);
|
||||
|
||||
for (i = 0; i < queriedPlayers.length; i++) {
|
||||
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{height: '50px', offset: 0},
|
||||
{height: '0px', offset: 1},
|
||||
]);
|
||||
}
|
||||
});
|
||||
for (i = 0; i < queriedPlayers.length; i++) {
|
||||
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{height: '50px', offset: 0},
|
||||
{height: '0px', offset: 1},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should compute intermediate styles properly when an animation is cancelled', () => {
|
||||
@Component({
|
||||
|
|
|
@ -279,36 +279,35 @@ import {el} from '../../testing/src/browser_util';
|
|||
});
|
||||
});
|
||||
|
||||
fixmeIvy(`FW-802: Animation 'start' and 'end' hooks are invoked twice`)
|
||||
.it('should provide hooks at the start and end of change detection', () => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
it('should provide hooks at the start and end of change detection', () => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger('myAnimation', [])]
|
||||
})
|
||||
class Cmp {
|
||||
public exp: any;
|
||||
}
|
||||
animations: [trigger('myAnimation', [])]
|
||||
})
|
||||
class Cmp {
|
||||
public exp: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const renderer = TestBed.get(RendererFactory2) as ExtendedAnimationRendererFactory;
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
const renderer = TestBed.get(RendererFactory2) as ExtendedAnimationRendererFactory;
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
});
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
|
|
Loading…
Reference in New Issue