test(ivy): add onDestroy acceptance tests (#30445)

Ports render3 onDestroy tests over to be acceptance tests. Removes old render3 tests if possible.

Note the onlyInIvy test for one area where Ivy has a different behavior than ViewEngine

PR Close #30445
This commit is contained in:
Ben Lesh
2019-05-13 22:52:52 -07:00
committed by Jason Aden
parent 257e9646d0
commit 222dde129d
2 changed files with 565 additions and 568 deletions

View File

@ -116,573 +116,6 @@ describe('lifecycles', () => {
});
});
describe('onDestroy', () => {
let events: string[];
beforeEach(() => { events = []; });
let Comp = createOnDestroyComponent('comp', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
ΔprojectionDef();
Δprojection(0);
}
}, 1);
let Parent = createOnDestroyComponent('parent', getParentTemplate('comp'), 1, 1, [Comp]);
function createOnDestroyComponent(
name: string, template: ComponentTemplate<any>, consts: number = 0, vars: number = 0,
directives: any[] = []) {
return class Component {
val: string = '';
ngOnDestroy() { events.push(`${name}${this.val}`); }
static ngComponentDef = ΔdefineComponent({
type: Component,
selectors: [[name]],
factory: () => new Component(),
consts: consts,
vars: vars,
inputs: {val: 'val'},
template: template,
directives: directives
});
};
}
let Grandparent = createOnDestroyComponent('grandparent', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δelement(0, 'parent');
}
}, 1, 0, [Parent]);
const ProjectedComp = createOnDestroyComponent('projected', (rf: RenderFlags, ctx: any) => {});
class Directive {
ngOnDestroy() { events.push('dir'); }
static ngDirectiveDef = ΔdefineDirective(
{type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive()});
}
const defs = [Comp, Parent, Grandparent, ProjectedComp, Directive];
it('should call destroy when view is removed', () => {
/**
* % if (!skip) {
* <comp></comp>
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (!ctx.skip) {
let rf1 = ΔembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp');
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.skip = true;
fixture.update();
expect(events).toEqual(['comp']);
});
it('should call destroy when multiple views are removed', () => {
/**
* % if (!skip) {
* <comp [val]="1"></comp>
* <comp [val]="2"></comp>
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (!ctx.skip) {
let rf1 = ΔembeddedViewStart(0, 2, 2);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp');
Δelement(1, 'comp');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind('1'));
Δselect(1);
ΔelementProperty(1, 'val', Δbind('2'));
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.skip = true;
fixture.update();
expect(events).toEqual(['comp1', 'comp2']);
});
it('should be called in child components before parent components', () => {
/**
* % if (!skip) {
* <parent></parent>
* % }
*
* parent template: <comp></comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (!ctx.skip) {
let rf1 = ΔembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'parent');
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.skip = true;
fixture.update();
expect(events).toEqual(['comp', 'parent']);
});
it('should be called bottom up with children nested 2 levels deep', () => {
/**
* % if (!skip) {
* <grandparent></grandparent>
* % }
*
* grandparent template: <parent></parent>
* parent template: <comp></comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (!ctx.skip) {
let rf1 = ΔembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'grandparent');
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.skip = true;
fixture.update();
expect(events).toEqual(['comp', 'parent', 'grandparent']);
});
it('should be called in projected components before their hosts', () => {
/**
* % if (!skip) {
* <comp [val]="1">
* <projected [val]="1"></projected>
* </comp>
* <comp [val]="2">
* <projected [val]="2"></projected>
* </comp>
* }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (!ctx.skip) {
let rf1 = ΔembeddedViewStart(0, 4, 0);
if (rf1 & RenderFlags.Create) {
ΔelementStart(0, 'comp');
{ Δelement(1, 'projected'); }
ΔelementEnd();
ΔelementStart(2, 'comp');
{ Δelement(3, 'projected'); }
ΔelementEnd();
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val', 1);
Δselect(1);
ΔelementProperty(1, 'val', 1);
Δselect(2);
ΔelementProperty(2, 'val', 2);
Δselect(3);
ΔelementProperty(3, 'val', 2);
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.skip = true;
fixture.update();
expect(events).toEqual(['projected1', 'comp1', 'projected2', 'comp2']);
});
it('should be called in consistent order if views are removed and re-added', () => {
/**
* % if (condition) {
* <comp [val]="1"></comp>
* % if (condition2) {
* <comp [val]="2"></comp>
* % }
* <comp [val]="3"></comp>
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (ctx.condition) {
let rf1 = ΔembeddedViewStart(0, 3, 2);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp');
Δcontainer(1);
Δelement(2, 'comp');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind('1'));
Δselect(2);
ΔelementProperty(2, 'val', Δbind('3'));
ΔcontainerRefreshStart(1);
{
if (ctx.condition2) {
let rf2 = ΔembeddedViewStart(0, 1, 1);
if (rf2 & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf2 & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind('2'));
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.condition = true;
fixture.component.condition2 = true;
fixture.update();
// remove all views
fixture.component.condition = false;
fixture.update();
/**
* Current angular will process in this same order (root is the top-level removed view):
*
* root.child (comp1 view) onDestroy: null
* root.child.next (container) -> embeddedView
* embeddedView.child (comp2 view) onDestroy: null
* embeddedView onDestroy: [comp2]
* root.child.next.next (comp3 view) onDestroy: null
* root onDestroy: [comp1, comp3]
*/
expect(events).toEqual(['comp2', 'comp1', 'comp3']);
events = [];
// remove inner view
fixture.component.condition = true;
fixture.component.condition2 = false;
fixture.update();
// remove outer view
fixture.component.condition = false;
fixture.update();
expect(events).toEqual(['comp1', 'comp3']);
events = [];
// restore both views
fixture.component.condition = true;
fixture.component.condition2 = true;
fixture.update();
// remove both views
fixture.component.condition = false;
fixture.update();
expect(events).toEqual(['comp2', 'comp1', 'comp3']);
});
it('should be called in every iteration of a destroyed for loop', () => {
/**
* % if (condition) {
* <comp [val]="1"></comp>
* % for (let i = 2; i < len; i++) {
* <comp [val]="i"></comp>
* % }
* <comp [val]="5"></comp>
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (ctx.condition) {
let rf1 = ΔembeddedViewStart(0, 3, 2);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp');
Δcontainer(1);
Δelement(2, 'comp');
}
if (rf1 & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind('1'));
Δselect(2);
ΔelementProperty(2, 'val', Δbind('5'));
ΔcontainerRefreshStart(1);
{
for (let j = 2; j < ctx.len; j++) {
let rf2 = ΔembeddedViewStart(0, 1, 1);
if (rf2 & RenderFlags.Create) {
Δelement(0, 'comp');
}
if (rf2 & RenderFlags.Update) {
ΔelementProperty(0, 'val', Δbind(j));
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.condition = true;
fixture.component.len = 5;
fixture.update();
fixture.component.condition = false;
fixture.update();
/**
* Current angular will process in this same order (root is the top-level removed view):
*
* root.child (comp1 view) onDestroy: null
* root.child.next (container) -> embeddedView (children[0].data)
* embeddedView.child (comp2 view) onDestroy: null
* embeddedView onDestroy: [comp2]
* embeddedView.next.child (comp3 view) onDestroy: null
* embeddedView.next onDestroy: [comp3]
* embeddedView.next.next.child (comp4 view) onDestroy: null
* embeddedView.next.next onDestroy: [comp4]
* embeddedView.next.next -> container -> root
* root onDestroy: [comp1, comp5]
*/
expect(events).toEqual(['comp2', 'comp3', 'comp4', 'comp1', 'comp5']);
events = [];
fixture.component.condition = true;
fixture.component.len = 4;
fixture.update();
fixture.component.condition = false;
fixture.update();
expect(events).toEqual(['comp2', 'comp3', 'comp1', 'comp5']);
events = [];
fixture.component.condition = true;
fixture.component.len = 5;
fixture.update();
fixture.component.condition = false;
fixture.update();
expect(events).toEqual(['comp2', 'comp3', 'comp4', 'comp1', 'comp5']);
});
it('should call destroy properly if view also has listeners', () => {
/**
* % if (condition) {
* <button (click)="onClick()">Click me</button>
* <comp></comp>
* <button (click)="onClick()">Click me</button>
* % }
*/
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (ctx.condition) {
let rf1 = ΔembeddedViewStart(0, 5, 0);
if (rf1 & RenderFlags.Create) {
ΔelementStart(0, 'button');
{
Δlistener('click', ctx.onClick.bind(ctx));
Δtext(1, 'Click me');
}
ΔelementEnd();
Δelement(2, 'comp');
ΔelementStart(3, 'button');
{
Δlistener('click', ctx.onClick.bind(ctx));
Δtext(4, 'Click me');
}
ΔelementEnd();
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}
class App {
counter = 0;
condition = true;
onClick() { this.counter++; }
}
const ctx: {counter: number} = new App();
renderToHtml(Template, ctx, 1, 0, defs);
const buttons = containerEl.querySelectorAll('button') !;
buttons[0].click();
expect(ctx.counter).toEqual(1);
buttons[1].click();
expect(ctx.counter).toEqual(2);
renderToHtml(Template, {condition: false}, 1, 0, defs);
buttons[0].click();
buttons[1].click();
expect(events).toEqual(['comp']);
expect(ctx.counter).toEqual(2);
});
it('should be called on directives after component', () => {
/**
* % if (condition) {
* <comp></comp>
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (ctx.condition) {
let rf1 = ΔembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'comp', ['dir', '']);
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.condition = true;
fixture.update();
expect(events).toEqual([]);
fixture.component.condition = false;
fixture.update();
expect(events).toEqual(['comp', 'dir']);
});
it('should be called on directives on an element', () => {
/**
* % if (condition) {
* <div directive></div>
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
Δcontainer(0);
}
if (rf & RenderFlags.Update) {
ΔcontainerRefreshStart(0);
{
if (ctx.condition) {
let rf1 = ΔembeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
Δelement(0, 'div', ['dir', '']);
}
ΔembeddedViewEnd();
}
}
ΔcontainerRefreshEnd();
}
}, 1, 0, defs);
const fixture = new ComponentFixture(App);
fixture.component.condition = true;
fixture.update();
expect(events).toEqual([]);
fixture.component.condition = false;
fixture.update();
expect(events).toEqual(['dir']);
});
});
describe('onChanges', () => {
let events: ({type: string, name: string, [key: string]: any})[];