fix(ivy): ensure animation @trigger ordering is correctly delivered to the renderer (#28165)

In Ivy when elements are created a series of static attribute names are provided
over to the construction instruction of that element. Static attribute names
include non-binding attribues (like `<div selected>`) as well as animation bindings
that do not have a RHS value (like `<div @foo>`). Because of this distinction,
value-less animation triggers are rendered first before value-full animation
bindings are and this improper ordering has caused various existing tests to fail.
This patch ensures that animation bindings are evaluated in the order that they
exist within the HTML template code (or host binding code).

PR Close #28165
This commit is contained in:
Matias Niemelä
2019-01-15 13:46:15 -08:00
committed by Alex Rickabaugh
parent 0d6913f037
commit e172e97e13
4 changed files with 141 additions and 154 deletions

View File

@ -1569,64 +1569,61 @@ const DEFAULT_COMPONENT_ID = '1';
}
});
fixmeIvy(
'FW-932: Animation @triggers are not reported to the renderer in Ivy as they are in VE')
.it('should animate removals of nodes to the `void` state for each animation trigger, but treat all auto styles as pre styles',
() => {
@Component({
selector: 'ani-cmp',
template: `
it('should animate removals of nodes to the `void` state for each animation trigger, but treat all auto styles as pre styles',
() => {
@Component({
selector: 'ani-cmp',
template: `
<div *ngIf="exp" class="ng-if" [@trig1]="exp2" @trig2></div>
`,
animations: [
trigger('trig1', [transition(
'state => void', [animate(1000, style({opacity: 0}))])]),
trigger(
'trig2', [transition(':leave', [animate(1000, style({width: '0px'}))])])
]
})
class Cmp {
public exp = true;
public exp2 = 'state';
}
animations: [
trigger(
'trig1', [transition('state => void', [animate(1000, style({opacity: 0}))])]),
trigger('trig2', [transition(':leave', [animate(1000, style({width: '0px'}))])])
]
})
class Cmp {
public exp = true;
public exp2 = 'state';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;
fixture.detectChanges();
engine.flush();
resetLog();
const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;
fixture.detectChanges();
engine.flush();
resetLog();
const element = getDOM().querySelector(fixture.nativeElement, '.ng-if');
assertHasParent(element, true);
const element = getDOM().querySelector(fixture.nativeElement, '.ng-if');
assertHasParent(element, true);
cmp.exp = false;
fixture.detectChanges();
engine.flush();
cmp.exp = false;
fixture.detectChanges();
engine.flush();
assertHasParent(element, true);
assertHasParent(element, true);
expect(getLog().length).toEqual(2);
expect(getLog().length).toEqual(2);
const player2 = getLog().pop() !;
const player1 = getLog().pop() !;
const player2 = getLog().pop() !;
const player1 = getLog().pop() !;
expect(player2.keyframes).toEqual([
{width: PRE_STYLE, offset: 0},
{width: '0px', offset: 1},
]);
expect(player2.keyframes).toEqual([
{width: PRE_STYLE, offset: 0},
{width: '0px', offset: 1},
]);
expect(player1.keyframes).toEqual([
{opacity: PRE_STYLE, offset: 0}, {opacity: '0', offset: 1}
]);
expect(player1.keyframes).toEqual([
{opacity: PRE_STYLE, offset: 0}, {opacity: '0', offset: 1}
]);
player2.finish();
player1.finish();
assertHasParent(element, false);
});
player2.finish();
player1.finish();
assertHasParent(element, false);
});
it('should properly cancel all existing animations when a removal occurs', () => {
@Component({
@ -3372,43 +3369,42 @@ const DEFAULT_COMPONENT_ID = '1';
expect(getLog().length).toEqual(1);
});
fixmeIvy('FW-951 - Attribute-only synthetic properties are treated differently in Ivy')
.it('should treat the property as true when the expression is missing', () => {
@Component({
selector: 'parent-cmp',
animations: [
trigger(
'myAnimation',
[
transition(
'* => go',
[
style({opacity: 0}),
animate(500, style({opacity: 1})),
]),
]),
],
template: `
it('should treat the property as true when the expression is missing', () => {
@Component({
selector: 'parent-cmp',
animations: [
trigger(
'myAnimation',
[
transition(
'* => go',
[
style({opacity: 0}),
animate(500, style({opacity: 1})),
]),
]),
],
template: `
<div @.disabled>
<div [@myAnimation]="exp"></div>
</div>
`
})
class Cmp {
exp = '';
}
})
class Cmp {
exp = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
resetLog();
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
resetLog();
cmp.exp = 'go';
fixture.detectChanges();
expect(getLog().length).toEqual(0);
});
cmp.exp = 'go';
fixture.detectChanges();
expect(getLog().length).toEqual(0);
});
it('should respect parent/sub animations when the respective area in the DOM is disabled',
fakeAsync(() => {