fix(navcontroller): lazy loading is queued

This commit is contained in:
Manuel Mtz-Almeida
2017-04-09 12:10:57 +02:00
parent 5776f76ac9
commit f88823a30f
17 changed files with 649 additions and 571 deletions

View File

@ -85,6 +85,7 @@ function karmaTest(watch: boolean, done: Function) {
let karmaConfig = { let karmaConfig = {
configFile: join(SCRIPTS_ROOT, 'karma/karma.conf.js'), configFile: join(SCRIPTS_ROOT, 'karma/karma.conf.js'),
singleRun: true,
}; };
if (watch) { if (watch) {
@ -96,6 +97,9 @@ function karmaTest(watch: boolean, done: Function) {
args: ['--grep', argv.testGrep] args: ['--grep', argv.testGrep]
}; };
} }
if (typeof argv.debug !== 'undefined') {
karmaConfig.singleRun = false;
}
new karma.Server(karmaConfig, done).start(); new karma.Server(karmaConfig, done).start();
} }

View File

@ -4,7 +4,7 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
// disable console debugs/errors/warns from printing out // disable console debugs/errors/warns from printing out
console.debug = () => {}; console.debug = () => {};
console.error = () => {}; // console.error = () => {};
console.warn = () => {}; console.warn = () => {};
__karma__.loaded = function () {}; __karma__.loaded = function () {};

View File

@ -103,6 +103,13 @@ export class Animation {
return 0; return 0;
} }
/**
* Returns if the animation is a root one.
*/
isRoot(): boolean {
return !this.parent;
}
/** /**
* Set the duration for this animation. * Set the duration for this animation.
*/ */

View File

@ -167,8 +167,9 @@ describe('App', () => {
expect(plt.exitApp).not.toHaveBeenCalled(); expect(plt.exitApp).not.toHaveBeenCalled();
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });
it('should pop the second view in the root nav', () => { it('should pop the second view in the root nav', () => {

View File

@ -42,7 +42,7 @@
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Toggle Can Leave</ion-label> <ion-label>Toggle Can Leave</ion-label>
<ion-toggle (click)="canLeave = !canLeave"></ion-toggle> <ion-toggle [(ngModel)]="canLeave"></ion-toggle>
</ion-item> </ion-item>
<button ion-item (click)="viewDismiss()">View Dismiss</button> <button ion-item (click)="viewDismiss()">View Dismiss</button>
<button ion-item (click)="quickPush()">New push during transition</button> <button ion-item (click)="quickPush()">New push during transition</button>

View File

@ -34,6 +34,7 @@ describe('Nav', () => {
expect(nav.setPages).toHaveBeenCalledWith(knownViews, null, null); expect(nav.setPages).toHaveBeenCalledWith(knownViews, null, null);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });
@ -56,6 +57,7 @@ describe('Nav', () => {
expect(nav.setPages).toHaveBeenCalledWith(knownViews, null, null); expect(nav.setPages).toHaveBeenCalledWith(knownViews, null, null);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });
@ -72,7 +74,8 @@ describe('Nav', () => {
promise.then(() => { promise.then(() => {
expect(nav.push).toHaveBeenCalled(); expect(nav.push).toHaveBeenCalled();
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });

View File

@ -292,7 +292,7 @@ export class Tab extends NavControllerBase {
/** /**
* @hidden * @hidden
*/ */
load(opts: NavOptions, done?: Function) { load(opts: NavOptions, done?: () => void) {
if (!this._loaded && this.root) { if (!this._loaded && this.root) {
this.setElementClass('show-tab', true); this.setElementClass('show-tab', true);
this.push(this.root, this.rootParams, opts, done); this.push(this.root, this.rootParams, opts, done);
@ -305,7 +305,7 @@ export class Tab extends NavControllerBase {
this._dom.read(() => { this._dom.read(() => {
this.resize(); this.resize();
}); });
done(true); done();
} }
} }

View File

@ -162,7 +162,6 @@ describe('Tabs', () => {
it('should get the tab', () => { it('should get the tab', () => {
var tabs = mockTabs(); var tabs = mockTabs();
var tab0 = mockTab(tabs); var tab0 = mockTab(tabs);
tab0.setRoot(<any>{});
var tab1 = mockTab(tabs); var tab1 = mockTab(tabs);
expect(tabs.getIndex(tab0)).toEqual(0); expect(tabs.getIndex(tab0)).toEqual(0);

View File

@ -3,8 +3,8 @@ import { ComponentRef, Input, ComponentFactoryResolver, ElementRef, EventEmitter
import { AnimationOptions } from '../animations/animation'; import { AnimationOptions } from '../animations/animation';
import { App } from '../components/app/app'; import { App } from '../components/app/app';
import { Config } from '../config/config'; import { Config } from '../config/config';
import { convertToView, convertToViews, NavOptions, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX, import { convertToViews, NavOptions, NavResult, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX,
TransitionResolveFn, TransitionInstruction, STATE_NEW, STATE_INITIALIZED, STATE_ATTACHED, STATE_DESTROYED } from './nav-util'; TransitionInstruction, STATE_NEW, STATE_INITIALIZED, STATE_ATTACHED, STATE_DESTROYED } from './nav-util';
import { setZIndex } from './nav-util'; import { setZIndex } from './nav-util';
import { DeepLinker } from './deep-linker'; import { DeepLinker } from './deep-linker';
import { DomController } from '../platform/dom-controller'; import { DomController } from '../platform/dom-controller';
@ -80,40 +80,31 @@ export class NavControllerBase extends Ion implements NavController {
this.id = 'n' + (++ctrlIds); this.id = 'n' + (++ctrlIds);
} }
push(page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> { push(page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return convertToView(this._linker, page, params).then(viewController => { return this._queueTrns({
return this._queueTrns({ insertStart: -1,
insertStart: -1, insertViews: [{ page: page, params: params }],
insertViews: [viewController], opts: opts,
opts: opts, }, done);
}, done);
}).catch((err: Error) => {
console.error('Failed to navigate: ', err.message);
throw err;
});
} }
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> { insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return convertToView(this._linker, page, params).then(viewController => { return this._queueTrns({
return this._queueTrns({ insertStart: insertIndex,
insertStart: insertIndex, insertViews: [{ page: page, params: params }],
insertViews: [viewController], opts: opts,
opts: opts, }, done);
}, done);
});
} }
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: Function): Promise<any> { insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: () => void): Promise<any> {
return convertToViews(this._linker, insertPages).then(viewControllers => { return this._queueTrns({
return this._queueTrns({ insertStart: insertIndex,
insertStart: insertIndex, insertViews: insertPages,
insertViews: viewControllers, opts: opts,
opts: opts, }, done);
}, done);
});
} }
pop(opts?: NavOptions, done?: Function): Promise<any> { pop(opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({ return this._queueTrns({
removeStart: -1, removeStart: -1,
removeCount: 1, removeCount: 1,
@ -121,7 +112,7 @@ export class NavControllerBase extends Ion implements NavController {
}, done); }, done);
} }
popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: Function): Promise<any> { popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: () => void): Promise<any> {
let config: TransitionInstruction = { let config: TransitionInstruction = {
removeStart: -1, removeStart: -1,
removeCount: -1, removeCount: -1,
@ -136,7 +127,7 @@ export class NavControllerBase extends Ion implements NavController {
return this._queueTrns(config, done); return this._queueTrns(config, done);
} }
popToRoot(opts?: NavOptions, done?: Function): Promise<any> { popToRoot(opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({ return this._queueTrns({
removeStart: 1, removeStart: 1,
removeCount: -1, removeCount: -1,
@ -152,7 +143,7 @@ export class NavControllerBase extends Ion implements NavController {
return Promise.all(promises); return Promise.all(promises);
} }
remove(startIndex: number, removeCount: number = 1, opts?: NavOptions, done?: Function): Promise<any> { remove(startIndex: number, removeCount: number = 1, opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({ return this._queueTrns({
removeStart: startIndex, removeStart: startIndex,
removeCount: removeCount, removeCount: removeCount,
@ -160,7 +151,7 @@ export class NavControllerBase extends Ion implements NavController {
}, done); }, done);
} }
removeView(viewController: ViewController, opts?: NavOptions, done?: Function): Promise<any> { removeView(viewController: ViewController, opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({ return this._queueTrns({
removeView: viewController, removeView: viewController,
removeStart: 0, removeStart: 0,
@ -169,19 +160,12 @@ export class NavControllerBase extends Ion implements NavController {
}, done); }, done);
} }
setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> { setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return convertToView(this._linker, pageOrViewCtrl, params).then((viewController) => { return this.setPages([{ page: pageOrViewCtrl, params: params }], opts, done);
return this._setPages([viewController], opts, done);
});
} }
setPages(pages: any[], opts?: NavOptions, done?: Function): Promise<any> {
return convertToViews(this._linker, pages).then(viewControllers => {
return this._setPages(viewControllers, opts, done);
});
}
_setPages(viewControllers: ViewController[], opts?: NavOptions, done?: Function): Promise<any> { setPages(viewControllers: any[], opts?: NavOptions, done?: () => void): Promise<any> {
if (isBlank(opts)) { if (isBlank(opts)) {
opts = {}; opts = {};
} }
@ -208,81 +192,68 @@ export class NavControllerBase extends Ion implements NavController {
// 7. _transitionStart(): called once the transition actually starts, it initializes the Animation underneath. // 7. _transitionStart(): called once the transition actually starts, it initializes the Animation underneath.
// 8. _transitionFinish(): called once the transition finishes // 8. _transitionFinish(): called once the transition finishes
// 9. _cleanup(): syncs the navigation internal state with the DOM. For example it removes the pages from the DOM or hides/show them. // 9. _cleanup(): syncs the navigation internal state with the DOM. For example it removes the pages from the DOM or hides/show them.
_queueTrns(ti: TransitionInstruction, done: Function): Promise<any> { _queueTrns(ti: TransitionInstruction, done: () => void): Promise<boolean> {
let promise: Promise<any>; const promise = new Promise<boolean>((resolve, reject) => {
let resolve: Function = done; ti.resolve = resolve;
let reject: Function = done; ti.reject = reject;
});
ti.done = done;
if (done === undefined) { // Normalize empty
// only create a promise if a done callback wasn't provided if (ti.insertViews && ti.insertViews.length === 0) {
// done can be a null, which avoids any functions ti.insertViews = undefined;
promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
}
// ti.resolve() is called when the navigation transition is finished successfully
ti.resolve = (hasCompleted: boolean, isAsync: boolean, enteringName: string, leavingName: string, direction: string) => {
this._trnsId = null;
this._init = true;
resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction);
// let's see if there's another to kick off
this.setTransitioning(false);
this._swipeBackCheck();
this._nextTrns();
};
// ti.reject() is called when the navigation transition fails. ie. it is rejected at some point.
ti.reject = (rejectReason: any, transition: Transition) => {
this._trnsId = null;
this._queue.length = 0;
// walk through the transition views so they are destroyed
while (transition) {
var enteringView = transition.enteringView;
if (enteringView && (enteringView._state === STATE_ATTACHED)) {
this._destroyView(enteringView);
}
if (transition.isRoot()) {
this._trnsCtrl.destroy(transition.trnsId);
break;
}
transition = transition.parent;
}
reject && reject(false, false, rejectReason);
// let's see if there's another to kick off
this.setTransitioning(false);
this._swipeBackCheck();
this._nextTrns();
};
if (ti.insertViews) {
// ensure we've got good views to insert
ti.insertViews = ti.insertViews.filter(v => v !== null);
if (ti.insertViews.length === 0) {
ti.reject('invalid views to insert');
return promise;
}
} else if (isPresent(ti.removeStart) && this._views.length === 0 && !this._isPortal) {
ti.reject('no views in the stack to be removed');
return promise;
} }
// Enqueue transition instruction
this._queue.push(ti); this._queue.push(ti);
// if there isn't a transition already happening // if there isn't a transition already happening
// then this will kick off this transition // then this will kick off this transition
this._nextTrns(); this._nextTrns();
// promise is undefined if a done callbacks was provided
return promise; return promise;
} }
_success(result: NavResult, ti: TransitionInstruction) {
this._init = true;
this._trnsId = null;
// let's see if there's another to kick off
this.setTransitioning(false);
this._swipeBackCheck();
this._nextTrns();
if (ti.done) {
ti.done(
result.hasCompleted,
result.requiresTransition,
result.enteringName,
result.leavingName,
result.direction
);
}
ti.resolve(result.hasCompleted);
}
_failed(rejectReason: any, ti: TransitionInstruction) {
this._trnsId = null;
this._queue.length = 0;
// let's see if there's another to kick off
this.setTransitioning(false);
this._swipeBackCheck();
this._nextTrns();
if (ti.done) {
ti.done(false, false, rejectReason);
}
if (ti.reject) {
ti.reject(rejectReason);
} else {
ti.resolve(false);
}
}
_nextTrns(): boolean { _nextTrns(): boolean {
// this is the framework's bread 'n butta function // this is the framework's bread 'n butta function
// only one transition is allowed at any given time // only one transition is allowed at any given time
@ -292,68 +263,53 @@ export class NavControllerBase extends Ion implements NavController {
// there is no transition happening right now // there is no transition happening right now
// get the next instruction // get the next instruction
const ti = this._nextTI(); const ti = this._queue.shift();
if (!ti) { if (!ti) {
return false; return false;
} }
// ensure any of the inserted view are used
const insertViews = ti.insertViews;
if (insertViews) {
for (var i = 0; i < insertViews.length; i++) {
var nav = insertViews[i]._nav;
if (nav && nav !== this || insertViews[i]._state === STATE_DESTROYED) {
ti.reject('leavingView and enteringView are null. stack is already empty');
return false;
}
}
}
// get entering and leaving views
const leavingView = this.getActive();
const enteringView = this._getEnteringView(ti, leavingView);
if (!leavingView && !enteringView) {
ti.reject('leavingView and enteringView are null. stack is already empty');
return false;
}
// set that this nav is actively transitioning // set that this nav is actively transitioning
this.setTransitioning(true); let enteringView: ViewController;
let leavingView: ViewController;
// Initialize enteringView this._startTI(ti)
if (enteringView && enteringView._state === STATE_NEW) { .then(() => this._loadLazyLoading(ti))
// render the entering view, and all child navs and views .then(() => {
// ******** DOM WRITE **************** leavingView = this.getActive();
this._viewInit(enteringView); enteringView = this._getEnteringView(ti, leavingView);
}
// Only test canLeave/canEnter if there is transition if (!leavingView && !enteringView) {
const requiresTransition = ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView; throw 'no views in the stack to be removed';
if (requiresTransition) { }
// views have been initialized, now let's test
// to see if the transition is even allowed or not if (enteringView && enteringView._state === STATE_NEW) {
return this._viewTest(enteringView, leavingView, ti); this._viewInit(enteringView);
} else { }
return this._postViewInit(enteringView, leavingView, ti);
} // Needs transition?
ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView;
})
.then(() => this._viewTest(enteringView, leavingView, ti))
.then(() => this._postViewInit(enteringView, leavingView, ti))
.then(() => this._transition(enteringView, leavingView, ti))
.then((result) => this._success(result, ti))
.catch((rejectReason) => this._failed(rejectReason, ti));
return true;
} }
_nextTI(): TransitionInstruction { _startTI(ti: TransitionInstruction): Promise<void> {
const ti = this._queue.shift();
if (!ti) {
return null;
}
const viewsLength = this._views.length; const viewsLength = this._views.length;
if (isPresent(ti.removeView)) { if (isPresent(ti.removeView)) {
assert(isPresent(ti.removeStart), 'removeView needs removeStart'); assert(isPresent(ti.removeStart), 'removeView needs removeStart');
assert(isPresent(ti.removeCount), 'removeView needs removeCount'); assert(isPresent(ti.removeCount), 'removeView needs removeCount');
var index = this._views.indexOf(ti.removeView); const index = this.indexOf(ti.removeView);
if (index >= 0) { if (index < 0) {
ti.removeStart += index; return Promise.reject('removeView was not found');
} }
ti.removeStart += index;
} }
if (isPresent(ti.removeStart)) { if (isPresent(ti.removeStart)) {
if (ti.removeStart < 0) { if (ti.removeStart < 0) {
@ -373,7 +329,35 @@ export class NavControllerBase extends Ion implements NavController {
} }
ti.enteringRequiresTransition = (ti.insertStart === viewsLength); ti.enteringRequiresTransition = (ti.insertStart === viewsLength);
} }
return ti; this.setTransitioning(true);
return Promise.resolve();
}
_loadLazyLoading(ti: TransitionInstruction): Promise<void> {
const insertViews = ti.insertViews;
if (insertViews) {
assert(insertViews.length > 0, 'length can not be zero');
return convertToViews(this._linker, insertViews).then((viewControllers) => {
assert(insertViews.length === viewControllers.length, 'lengths does not match');
// Check all the inserted view are correct
for (var i = 0; i < viewControllers.length; i++) {
var view = viewControllers[i];
if (!view) {
throw 'invalid views to insert';
}
var nav = view._nav;
if (nav && nav !== this) {
throw 'inserted view was already inserted';
}
if (viewControllers[i]._state === STATE_DESTROYED) {
throw 'inserted view was already destroyed';
}
}
ti.insertViews = viewControllers;
});
}
return Promise.resolve();
} }
_getEnteringView(ti: TransitionInstruction, leavingView: ViewController): ViewController { _getEnteringView(ti: TransitionInstruction, leavingView: ViewController): ViewController {
@ -435,10 +419,10 @@ export class NavControllerBase extends Ion implements NavController {
console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`, console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`,
this, this.getNativeElement()); this, this.getNativeElement());
ti.reject('navigation stack needs at least one root page'); throw 'navigation stack needs at least one root page';
return false;
} }
// At this point the transition can not be rejected, any throw should be an error
// there are views to insert // there are views to insert
if (insertViews) { if (insertViews) {
// manually set the new view's id if an id was passed in the options // manually set the new view's id if an id was passed in the options
@ -479,27 +463,15 @@ export class NavControllerBase extends Ion implements NavController {
} }
} }
if (!ti.requiresTransition) {
// transition is not required, so we are already done!
// they're inserting/removing the views somewhere in the middle or
// beginning, so visually nothing needs to animate/transition
// resolve immediately because there's no animation that's happening
ti.resolve(true, false);
return true;
}
// set which animation it should use if it wasn't set yet // set which animation it should use if it wasn't set yet
if (!opts.animation) { if (ti.requiresTransition && !opts.animation) {
if (isPresent(ti.removeStart)) { if (isPresent(ti.removeStart)) {
opts.animation = (leavingView || enteringView).getTransitionName(opts.direction); opts.animation = (leavingView || enteringView).getTransitionName(opts.direction);
} else { } else {
opts.animation = (enteringView || leavingView).getTransitionName(opts.direction); opts.animation = (enteringView || leavingView).getTransitionName(opts.direction);
} }
} }
ti.opts = opts;
// huzzah! let us transition these views
this._transitionInit(enteringView, leavingView, opts, ti.resolve);
return true;
} }
/** /**
@ -509,6 +481,7 @@ export class NavControllerBase extends Ion implements NavController {
assert(enteringView, 'enteringView must be non null'); assert(enteringView, 'enteringView must be non null');
assert(enteringView._state === STATE_NEW, 'enteringView state must be NEW'); assert(enteringView._state === STATE_NEW, 'enteringView state must be NEW');
// render the entering view, and all child navs and views
// entering view has not been initialized yet // entering view has not been initialized yet
const componentProviders = ReflectiveInjector.resolve([ const componentProviders = ReflectiveInjector.resolve([
{ provide: NavController, useValue: this }, { provide: NavController, useValue: this },
@ -551,52 +524,52 @@ export class NavControllerBase extends Ion implements NavController {
this._zone.run(this._didLoad.bind(this, view)); this._zone.run(this._didLoad.bind(this, view));
} }
_viewTest(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): boolean { _viewTest(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): Promise<void> {
// Only test canLeave/canEnter if there is transition
if (!ti.requiresTransition) {
return Promise.resolve();
}
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
if (leavingView) { if (leavingView) {
var leavingTestResult = leavingView._lifecycleTest('Leave'); promises.push(leavingView._lifecycleTest('Leave'));
if (leavingTestResult === false) {
// synchronous reject
ti.reject((leavingTestResult !== false ? leavingTestResult : `ionViewCanLeave rejected`));
return false;
} else if (leavingTestResult instanceof Promise) {
// async promise
promises.push(leavingTestResult);
}
} }
if (enteringView) { if (enteringView) {
var enteringTestResult = enteringView._lifecycleTest('Enter'); promises.push(enteringView._lifecycleTest('Enter'));
}
if (enteringTestResult === false) { if (promises.length === 0) {
// synchronous reject return Promise.resolve();
ti.reject((enteringTestResult !== false ? enteringTestResult : `ionViewCanEnter rejected`)); }
return false;
} else if (enteringTestResult instanceof Promise) { // darn, async promises, gotta wait for them to resolve
// async promise return Promise.all(promises).then((values: any[]) => {
promises.push(enteringTestResult); if (values.some(result => result === false)) {
throw 'canEnter/Leave returned false';
} }
} }).catch((reason) => {
// Do not
if (promises.length) { ti.reject = null;
// darn, async promises, gotta wait for them to resolve throw reason;
Promise.all(promises).then((values: any[]) => { });
if (values.some(result => result === false)) {
ti.reject(`ionViewCanEnter rejected`);
} else {
this._postViewInit(enteringView, leavingView, ti);
}
}).catch(ti.reject);
return true;
} else {
// synchronous and all tests passed! let's move on already
return this._postViewInit(enteringView, leavingView, ti);
}
} }
_transitionInit(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn) { _transition(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): Promise<NavResult> {
if (!ti.requiresTransition) {
// transition is not required, so we are already done!
// they're inserting/removing the views somewhere in the middle or
// beginning, so visually nothing needs to animate/transition
// resolve immediately because there's no animation that's happening
return Promise.resolve({
hasCompleted: true,
requiresTransition: false
});
}
const opts = ti.opts;
// figure out if this transition is the root one or a // figure out if this transition is the root one or a
// child of a parent nav that has the root transition // child of a parent nav that has the root transition
this._trnsId = this._trnsCtrl.getRootTrnsId(this); this._trnsId = this._trnsCtrl.getRootTrnsId(this);
@ -630,11 +603,8 @@ export class NavControllerBase extends Ion implements NavController {
} }
// transition start has to be registered before attaching the view to the DOM! // transition start has to be registered before attaching the view to the DOM!
transition.registerStart(() => { const promise = new Promise<void>(resolve => transition.registerStart(resolve)).then(() => {
this._transitionStart(transition, enteringView, leavingView, opts, resolve); return this._transitionStart(transition, enteringView, leavingView, opts);
if (transition.parent) {
transition.parent.start();
}
}); });
if (enteringView && (enteringView._state === STATE_INITIALIZED)) { if (enteringView && (enteringView._state === STATE_INITIALIZED)) {
@ -645,13 +615,15 @@ export class NavControllerBase extends Ion implements NavController {
this._viewAttachToDOM(enteringView, enteringView._cmp, this._viewport); this._viewAttachToDOM(enteringView, enteringView._cmp, this._viewport);
} }
if (!transition.hasChildren) { if (!transition.hasChildren) {
// lowest level transition, so kick it off and let it bubble up to start all of them // lowest level transition, so kick it off and let it bubble up to start all of them
transition.start(); transition.start();
} }
return promise;
} }
_transitionStart(transition: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn) { _transitionStart(transition: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions): Promise<NavResult> {
assert(this.isTransitioning(), 'isTransitioning() has to be true'); assert(this.isTransitioning(), 'isTransitioning() has to be true');
this._trnsId = null; this._trnsId = null;
@ -689,15 +661,14 @@ export class NavControllerBase extends Ion implements NavController {
// that will fire off the willEnter/Leave lifecycle events at the right time // that will fire off the willEnter/Leave lifecycle events at the right time
transition.beforeAddRead(this._viewsWillLifecycles.bind(this, enteringView, leavingView)); transition.beforeAddRead(this._viewsWillLifecycles.bind(this, enteringView, leavingView));
// create a callback for when the animation is done
transition.onFinish(() => {
// transition animation has ended
this._zone.run(this._transitionFinish.bind(this, transition, opts, resolve));
});
// get the set duration of this transition // get the set duration of this transition
const duration = transition.getDuration(); const duration = transition.getDuration();
// create a callback for when the animation is done
const promise = new Promise(resolve => {
transition.onFinish(resolve);
});
if (transition.isRoot()) { if (transition.isRoot()) {
// this is the top most, or only active transition, so disable the app // this is the top most, or only active transition, so disable the app
// add XXms to the duration the app is disabled when the keyboard is open // add XXms to the duration the app is disabled when the keyboard is open
@ -723,9 +694,14 @@ export class NavControllerBase extends Ion implements NavController {
transition.play(); transition.play();
} }
} }
return promise.then(() => this._zone.run(() => {
return this._transitionFinish(transition, opts);
}));
} }
_transitionFinish(transition: Transition, opts: NavOptions, resolve: TransitionResolveFn) { _transitionFinish(transition: Transition, opts: NavOptions): NavResult {
const hasCompleted = transition.hasCompleted; const hasCompleted = transition.hasCompleted;
const enteringView = transition.enteringView; const enteringView = transition.enteringView;
const leavingView = transition.leavingView; const leavingView = transition.leavingView;
@ -774,8 +750,13 @@ export class NavControllerBase extends Ion implements NavController {
} }
} }
// congrats, we did it! return {
resolve(hasCompleted, true, enteringName, leavingName, opts.direction); hasCompleted: hasCompleted,
requiresTransition: true,
enteringName: enteringName,
leavingName: leavingName,
direction: opts.direction
};
} }
_viewsWillLifecycles(enteringView: ViewController, leavingView: ViewController) { _viewsWillLifecycles(enteringView: ViewController, leavingView: ViewController) {

View File

@ -8,7 +8,7 @@ import { NavControllerBase } from './nav-controller-base';
import { Transition } from '../transitions/transition'; import { Transition } from '../transitions/transition';
export function getComponent(linker: DeepLinker, nameOrPageOrView: any, params?: any) { export function getComponent(linker: DeepLinker, nameOrPageOrView: any, params?: any): Promise<ViewController> {
if (typeof nameOrPageOrView === 'function') { if (typeof nameOrPageOrView === 'function') {
return Promise.resolve( return Promise.resolve(
new ViewController(nameOrPageOrView, params) new ViewController(nameOrPageOrView, params)
@ -24,7 +24,7 @@ export function getComponent(linker: DeepLinker, nameOrPageOrView: any, params?:
return Promise.resolve(null); return Promise.resolve(null);
} }
export function convertToView(linker: DeepLinker, nameOrPageOrView: any, params: any) { export function convertToView(linker: DeepLinker, nameOrPageOrView: any, params: any): Promise<ViewController> {
if (nameOrPageOrView) { if (nameOrPageOrView) {
if (isViewController(nameOrPageOrView)) { if (isViewController(nameOrPageOrView)) {
// is already a ViewController // is already a ViewController
@ -34,11 +34,10 @@ export function convertToView(linker: DeepLinker, nameOrPageOrView: any, params:
return getComponent(linker, nameOrPageOrView, params); return getComponent(linker, nameOrPageOrView, params);
} }
console.error(`invalid page component: ${nameOrPageOrView}`);
return Promise.resolve(null); return Promise.resolve(null);
} }
export function convertToViews(linker: DeepLinker, pages: any[]) { export function convertToViews(linker: DeepLinker, pages: any[]): Promise<ViewController[]> {
const views: Promise<ViewController>[] = []; const views: Promise<ViewController>[] = [];
if (isArray(pages)) { if (isArray(pages)) {
for (var i = 0; i < pages.length; i++) { for (var i = 0; i < pages.length; i++) {
@ -147,6 +146,14 @@ export interface NavLink {
defaultHistory?: any[]; defaultHistory?: any[];
} }
export interface NavResult {
hasCompleted: boolean;
requiresTransition: boolean;
enteringName?: string;
leavingName?: string;
direction?: string;
}
export interface NavSegment { export interface NavSegment {
id: string; id: string;
name: string; name: string;
@ -188,12 +195,13 @@ export interface TransitionRejectFn {
export interface TransitionInstruction { export interface TransitionInstruction {
opts: NavOptions; opts: NavOptions;
insertStart?: number; insertStart?: number;
insertViews?: ViewController[]; insertViews?: any[];
removeView?: ViewController; removeView?: ViewController;
removeStart?: number; removeStart?: number;
removeCount?: number; removeCount?: number;
resolve?: TransitionResolveFn; resolve?: (hasCompleted: boolean) => void;
reject?: TransitionRejectFn; reject?: (rejectReason: string) => void;
done?: Function;
leavingRequiresTransition?: boolean; leavingRequiresTransition?: boolean;
enteringRequiresTransition?: boolean; enteringRequiresTransition?: boolean;
requiresTransition?: boolean; requiresTransition?: boolean;

View File

@ -284,6 +284,7 @@ describe('DeepLinker', () => {
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });

View File

@ -60,7 +60,7 @@ describe('NavController', () => {
expect(nav.getByIndex(2).component).toEqual(MockView3); expect(nav.getByIndex(2).component).toEqual(MockView3);
expect(nav.getByIndex(3).component).toEqual(MockView4); expect(nav.getByIndex(3).component).toEqual(MockView4);
// Pop 1 // Pop 1
nav.pop({ animate: false }, pop1Done); return nav.pop({ animate: false }, pop1Done);
}).then(() => { }).then(() => {
expect(pop1Done).toHaveBeenCalledWith( expect(pop1Done).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'MockView3', 'MockView4', DIRECTION_BACK hasCompleted, requiresTransition, 'MockView3', 'MockView4', DIRECTION_BACK
@ -88,9 +88,10 @@ describe('NavController', () => {
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
}); });
describe('push', () => { describe('push', () => {
@ -108,9 +109,10 @@ describe('NavController', () => {
expect(nav.isTransitioning()).toEqual(false); expect(nav.isTransitioning()).toEqual(false);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should push a component as the second view at the end', (done: Function) => { it('should push a component as the second view at the end', (done: Function) => {
mockViews(nav, [mockView(MockView1)]); mockViews(nav, [mockView(MockView1)]);
@ -128,9 +130,10 @@ describe('NavController', () => {
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should push a ViewController as the second view and fire lifecycles', (done: Function) => { it('should push a ViewController as the second view and fire lifecycles', (done: Function) => {
let view1 = mockView(); let view1 = mockView();
@ -169,20 +172,26 @@ describe('NavController', () => {
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
}); });
describe('insert', () => { describe('insert', () => {
it('should not modify the view id', () => { it('should not modify the view id', (done) => {
let view = mockView(MockView4); let view = mockView(MockView4);
view.id = 'custom_id'; view.id = 'custom_id';
nav.insert(0, view); nav.insert(0, view).then(() => {
expect(view.id).toEqual('custom_id');
done();
}).catch(err => {
fail(err);
done();
});
expect(view.id).toEqual('custom_id'); expect(view.id).toEqual('custom_id');
}); }, 10000);
it('should insert at the begining with no async transition', (done: Function) => { it('should insert at the begining with no async transition', (done: Function) => {
@ -213,9 +222,10 @@ describe('NavController', () => {
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should insert at the end when given -1', (done: Function) => { it('should insert at the end when given -1', (done: Function) => {
let opts: NavOptions = {}; let opts: NavOptions = {};
@ -231,9 +241,10 @@ describe('NavController', () => {
expect(nav.last().component).toEqual(MockView2); expect(nav.last().component).toEqual(MockView2);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should insert at the end when given a number greater than actual length', (done: Function) => { it('should insert at the end when given a number greater than actual length', (done: Function) => {
mockViews(nav, [mockView(MockView1)]); mockViews(nav, [mockView(MockView1)]);
@ -249,25 +260,28 @@ describe('NavController', () => {
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should not insert if null view', (done: Function) => { it('should not insert if null view', (done: Function) => {
mockViews(nav, [mockView(MockView1)]); mockViews(nav, [mockView(MockView1)]);
nav.insert(-1, null, null, null, trnsDone).then(() => { nav.insert(-1, null, null, null, trnsDone).then(() => {
fail('it should not succeed');
done();
}).catch((err: Error) => {
let hasCompleted = false; let hasCompleted = false;
let requiresTransition = false; let requiresTransition = false;
let rejectReason = 'invalid views to insert'; let rejectReason = 'invalid views to insert';
expect(err).toEqual(rejectReason);
expect(trnsDone).toHaveBeenCalledWith(hasCompleted, requiresTransition, rejectReason); expect(trnsDone).toHaveBeenCalledWith(hasCompleted, requiresTransition, rejectReason);
expect(nav.length()).toEqual(1); expect(nav.length()).toEqual(1);
expect(nav.last().component).toEqual(MockView1); expect(nav.last().component).toEqual(MockView1);
done(); done();
}).catch((err: Error) => {
done(err);
}); });
}); }, 10000);
it('should not insert any view in the stack if canLeave returns false', (done: Function) => { it('should not insert any view in the stack if canLeave returns false', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
@ -292,12 +306,11 @@ describe('NavController', () => {
}).then(() => { }).then(() => {
expect(nav.length()).toEqual(3); expect(nav.length()).toEqual(3);
done(); done();
}).catch((err: Error) => { }).catch(err => fail(err));
done(err);
});
});
it('should not remove any view from the stack if canLeave returns false', () => { }, 10000);
it('should not remove any view from the stack if canLeave returns false', (done) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
mockViews(nav, [view1, view2]); mockViews(nav, [view1, view2]);
@ -310,15 +323,17 @@ describe('NavController', () => {
return (count === 3); return (count === 3);
}; };
nav.pop(); nav.pop().then(() => {
expect(nav.length()).toEqual(2); expect(nav.length()).toEqual(2);
return nav.pop();
nav.pop(); }).then(() => {
expect(nav.length()).toEqual(2); expect(nav.length()).toEqual(2);
return nav.pop();
nav.pop(); }).then(() => {
expect(nav.length()).toEqual(1); expect(nav.length()).toEqual(1);
}); done();
}).catch(err => fail(err));
}, 10000);
}); });
@ -356,104 +371,126 @@ describe('NavController', () => {
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
}); });
describe('pop', () => { describe('pop', () => {
it('should not pop when no views in the stack', () => { it('should not pop when no views in the stack', (done) => {
nav.pop(null, trnsDone); nav.pop(null, trnsDone).then(() => {
fail('it should not succeed');
done();
}).catch((err) => {
let hasCompleted = false;
let requiresTransition = false;
let rejectReason = 'no views in the stack to be removed';
expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, rejectReason
);
expect(err).toEqual(rejectReason);
expect(nav.length()).toEqual(0);
expect(nav.isTransitioning()).toEqual(false);
done();
});
}, 10000);
let hasCompleted = false; it('should remove the last view and fire lifecycles', (done: Function) => {
let requiresTransition = false;
expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'no views in the stack to be removed'
);
expect(nav.length()).toEqual(0);
expect(nav.isTransitioning()).toEqual(false);
});
it('should remove the last view and fire lifecycles', () => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
mockViews(nav, [view1, view2]); mockViews(nav, [view1, view2]);
let instance1 = spyOnLifecycles(view1); let instance1 = spyOnLifecycles(view1);
let instance2 = spyOnLifecycles(view2); let instance2 = spyOnLifecycles(view2);
nav.pop(null, trnsDone); nav.pop(null, trnsDone).then(() => {
expect(instance1.ionViewDidLoad).toHaveBeenCalled(); expect(instance1.ionViewDidLoad).toHaveBeenCalled();
expect(instance1.ionViewCanEnter).toHaveBeenCalled(); expect(instance1.ionViewCanEnter).toHaveBeenCalled();
expect(instance1.ionViewWillEnter).toHaveBeenCalled(); expect(instance1.ionViewWillEnter).toHaveBeenCalled();
expect(instance1.ionViewDidEnter).toHaveBeenCalled(); expect(instance1.ionViewDidEnter).toHaveBeenCalled();
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance2.ionViewCanLeave).toHaveBeenCalled(); expect(instance2.ionViewCanLeave).toHaveBeenCalled();
expect(instance2.ionViewWillLeave).toHaveBeenCalled(); expect(instance2.ionViewWillLeave).toHaveBeenCalled();
expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewDidLeave).toHaveBeenCalled();
expect(instance2.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled();
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = true; let requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'MockView1', 'MockView2', DIRECTION_BACK hasCompleted, requiresTransition, 'MockView1', 'MockView2', DIRECTION_BACK
); );
expect(nav.length()).toEqual(1); expect(nav.length()).toEqual(1);
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.isTransitioning()).toEqual(false); expect(nav.isTransitioning()).toEqual(false);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
}); });
describe('popTo', () => { describe('popTo', () => {
it('should pop to a view', () => { it('should pop to a view', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
let view3 = mockView(MockView3); let view3 = mockView(MockView3);
mockViews(nav, [view1, view2, view3]); mockViews(nav, [view1, view2, view3]);
nav.popTo(view2, null, trnsDone); nav.popTo(view2, null, trnsDone).then(() => {
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = true; let requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'MockView2', 'MockView3', DIRECTION_BACK hasCompleted, requiresTransition, 'MockView2', 'MockView3', DIRECTION_BACK
); );
expect(nav.length()).toEqual(2); expect(nav.length()).toEqual(2);
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.getByIndex(1).component).toEqual(MockView2); expect(nav.getByIndex(1).component).toEqual(MockView2);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
it('should pop to using an index number', () => { it('should pop to using an index number', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
let view3 = mockView(MockView3); let view3 = mockView(MockView3);
let view4 = mockView(MockView4); let view4 = mockView(MockView4);
mockViews(nav, [view1, view2, view3, view4]); mockViews(nav, [view1, view2, view3, view4]);
nav.popTo(1, null, trnsDone); nav.popTo(1, null, trnsDone).then(() => {
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = true; let requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'MockView2', 'MockView4', DIRECTION_BACK hasCompleted, requiresTransition, 'MockView2', 'MockView4', DIRECTION_BACK
); );
expect(nav.length()).toEqual(2); expect(nav.length()).toEqual(2);
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.getByIndex(1).component).toEqual(MockView2); expect(nav.getByIndex(1).component).toEqual(MockView2);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
it('should pop to first using an index number', () => { it('should pop to first using an index number', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
let view3 = mockView(MockView3); let view3 = mockView(MockView3);
@ -465,58 +502,63 @@ describe('NavController', () => {
let instance3 = spyOnLifecycles(view3); let instance3 = spyOnLifecycles(view3);
let instance4 = spyOnLifecycles(view4); let instance4 = spyOnLifecycles(view4);
nav.popTo(0, null, trnsDone); nav.popTo(0, null, trnsDone).then(() => {
expect(instance1.ionViewDidLoad).toHaveBeenCalled(); expect(instance1.ionViewDidLoad).toHaveBeenCalled();
expect(instance1.ionViewCanEnter).toHaveBeenCalled(); expect(instance1.ionViewCanEnter).toHaveBeenCalled();
expect(instance1.ionViewWillEnter).toHaveBeenCalled(); expect(instance1.ionViewWillEnter).toHaveBeenCalled();
expect(instance1.ionViewDidEnter).toHaveBeenCalled(); expect(instance1.ionViewDidEnter).toHaveBeenCalled();
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance2.ionViewWillLeave).toHaveBeenCalled(); expect(instance2.ionViewWillLeave).toHaveBeenCalled();
expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewDidLeave).toHaveBeenCalled();
expect(instance2.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled();
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance3.ionViewWillLeave).toHaveBeenCalled(); expect(instance3.ionViewWillLeave).toHaveBeenCalled();
expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewDidLeave).toHaveBeenCalled();
expect(instance3.ionViewWillUnload).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled();
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance4.ionViewCanLeave).toHaveBeenCalled(); expect(instance4.ionViewCanLeave).toHaveBeenCalled();
expect(instance4.ionViewWillLeave).toHaveBeenCalled(); expect(instance4.ionViewWillLeave).toHaveBeenCalled();
expect(instance4.ionViewDidLeave).toHaveBeenCalled(); expect(instance4.ionViewDidLeave).toHaveBeenCalled();
expect(instance4.ionViewWillUnload).toHaveBeenCalled(); expect(instance4.ionViewWillUnload).toHaveBeenCalled();
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = true; let requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'MockView1', 'MockView4', DIRECTION_BACK hasCompleted, requiresTransition, 'MockView1', 'MockView4', DIRECTION_BACK
); );
expect(nav.length()).toEqual(1); expect(nav.length()).toEqual(1);
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
}); });
describe('popToRoot', () => { describe('popToRoot', () => {
it('should pop to the first view', () => { it('should pop to the first view', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
let view3 = mockView(MockView3); let view3 = mockView(MockView3);
@ -528,58 +570,63 @@ describe('NavController', () => {
let instance3 = spyOnLifecycles(view3); let instance3 = spyOnLifecycles(view3);
let instance4 = spyOnLifecycles(view4); let instance4 = spyOnLifecycles(view4);
nav.popToRoot(null, trnsDone); nav.popToRoot(null, trnsDone).then(() => {
expect(instance1.ionViewDidLoad).toHaveBeenCalled(); expect(instance1.ionViewDidLoad).toHaveBeenCalled();
expect(instance1.ionViewCanEnter).toHaveBeenCalled(); expect(instance1.ionViewCanEnter).toHaveBeenCalled();
expect(instance1.ionViewWillEnter).toHaveBeenCalled(); expect(instance1.ionViewWillEnter).toHaveBeenCalled();
expect(instance1.ionViewDidEnter).toHaveBeenCalled(); expect(instance1.ionViewDidEnter).toHaveBeenCalled();
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance2.ionViewWillLeave).toHaveBeenCalled(); expect(instance2.ionViewWillLeave).toHaveBeenCalled();
expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewDidLeave).toHaveBeenCalled();
expect(instance2.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled();
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance3.ionViewWillLeave).toHaveBeenCalled(); expect(instance3.ionViewWillLeave).toHaveBeenCalled();
expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewDidLeave).toHaveBeenCalled();
expect(instance3.ionViewWillUnload).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled();
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance4.ionViewCanLeave).toHaveBeenCalled(); expect(instance4.ionViewCanLeave).toHaveBeenCalled();
expect(instance4.ionViewWillLeave).toHaveBeenCalled(); expect(instance4.ionViewWillLeave).toHaveBeenCalled();
expect(instance4.ionViewDidLeave).toHaveBeenCalled(); expect(instance4.ionViewDidLeave).toHaveBeenCalled();
expect(instance4.ionViewWillUnload).toHaveBeenCalled(); expect(instance4.ionViewWillUnload).toHaveBeenCalled();
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = true; let requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'MockView1', 'MockView4', DIRECTION_BACK hasCompleted, requiresTransition, 'MockView1', 'MockView4', DIRECTION_BACK
); );
expect(nav.length()).toEqual(1); expect(nav.length()).toEqual(1);
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
}); });
describe('remove', () => { describe('remove', () => {
it('should remove the first three views in the beginning, no last view transition', () => { it('should remove the first three views in the beginning, no last view transition', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
let view3 = mockView(MockView3); let view3 = mockView(MockView3);
@ -591,54 +638,59 @@ describe('NavController', () => {
let instance3 = spyOnLifecycles(view3); let instance3 = spyOnLifecycles(view3);
let instance4 = spyOnLifecycles(view4); let instance4 = spyOnLifecycles(view4);
nav.remove(0, 3, null, trnsDone); nav.remove(0, 3, null, trnsDone).then(() => {
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillLeave).toHaveBeenCalled(); expect(instance1.ionViewWillLeave).toHaveBeenCalled();
expect(instance1.ionViewDidLeave).toHaveBeenCalled(); expect(instance1.ionViewDidLeave).toHaveBeenCalled();
expect(instance1.ionViewWillUnload).toHaveBeenCalled(); expect(instance1.ionViewWillUnload).toHaveBeenCalled();
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance2.ionViewWillLeave).toHaveBeenCalled(); expect(instance2.ionViewWillLeave).toHaveBeenCalled();
expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewDidLeave).toHaveBeenCalled();
expect(instance2.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled();
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance3.ionViewWillLeave).toHaveBeenCalled(); expect(instance3.ionViewWillLeave).toHaveBeenCalled();
expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewDidLeave).toHaveBeenCalled();
expect(instance3.ionViewWillUnload).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled();
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance4.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance4.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance4.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance4.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance4.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance4.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance4.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance4.ionViewWillUnload).not.toHaveBeenCalled();
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = false; let requiresTransition = false;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, undefined, undefined, undefined hasCompleted, requiresTransition, undefined, undefined, undefined
); );
expect(nav.length()).toEqual(1); expect(nav.length()).toEqual(1);
expect(nav.getByIndex(0).component).toEqual(MockView4); expect(nav.getByIndex(0).component).toEqual(MockView4);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
it('should remove two views in the middle', () => { it('should remove two views in the middle', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
let view3 = mockView(MockView3); let view3 = mockView(MockView3);
@ -652,65 +704,70 @@ describe('NavController', () => {
let instance4 = spyOnLifecycles(view4); let instance4 = spyOnLifecycles(view4);
let instance5 = spyOnLifecycles(view5); let instance5 = spyOnLifecycles(view5);
nav.remove(2, 2, null, trnsDone); nav.remove(2, 2, null, trnsDone).then(() => {
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance2.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance2.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance2.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance2.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance2.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance2.ionViewWillUnload).not.toHaveBeenCalled();
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance3.ionViewWillLeave).toHaveBeenCalled(); expect(instance3.ionViewWillLeave).toHaveBeenCalled();
expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewDidLeave).toHaveBeenCalled();
expect(instance3.ionViewWillUnload).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled();
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance4.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance4.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance4.ionViewWillLeave).toHaveBeenCalled(); expect(instance4.ionViewWillLeave).toHaveBeenCalled();
expect(instance4.ionViewDidLeave).toHaveBeenCalled(); expect(instance4.ionViewDidLeave).toHaveBeenCalled();
expect(instance4.ionViewWillUnload).toHaveBeenCalled(); expect(instance4.ionViewWillUnload).toHaveBeenCalled();
expect(instance5.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance5.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance5.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance5.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance5.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance5.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance5.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance5.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance5.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance5.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance5.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance5.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance5.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance5.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance5.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance5.ionViewWillUnload).not.toHaveBeenCalled();
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = false; let requiresTransition = false;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, undefined, undefined, undefined hasCompleted, requiresTransition, undefined, undefined, undefined
); );
expect(nav.length()).toEqual(3); expect(nav.length()).toEqual(3);
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.getByIndex(1).component).toEqual(MockView2); expect(nav.getByIndex(1).component).toEqual(MockView2);
expect(nav.getByIndex(2).component).toEqual(MockView5); expect(nav.getByIndex(2).component).toEqual(MockView5);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
it('should remove the last two views at the end', () => { it('should remove the last two views at the end', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
let view2 = mockView(MockView2); let view2 = mockView(MockView2);
let view3 = mockView(MockView3); let view3 = mockView(MockView3);
@ -722,53 +779,58 @@ describe('NavController', () => {
let instance3 = spyOnLifecycles(view3); let instance3 = spyOnLifecycles(view3);
let instance4 = spyOnLifecycles(view4); let instance4 = spyOnLifecycles(view4);
nav.remove(2, 2, null, trnsDone); nav.remove(2, 2, null, trnsDone).then(() => {
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
expect(instance2.ionViewDidLoad).toHaveBeenCalled(); expect(instance2.ionViewDidLoad).toHaveBeenCalled();
expect(instance2.ionViewCanEnter).toHaveBeenCalled(); expect(instance2.ionViewCanEnter).toHaveBeenCalled();
expect(instance2.ionViewWillEnter).toHaveBeenCalled(); expect(instance2.ionViewWillEnter).toHaveBeenCalled();
expect(instance2.ionViewDidEnter).toHaveBeenCalled(); expect(instance2.ionViewDidEnter).toHaveBeenCalled();
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance2.ionViewWillLeave).not.toHaveBeenCalled(); expect(instance2.ionViewWillLeave).not.toHaveBeenCalled();
expect(instance2.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance2.ionViewDidLeave).not.toHaveBeenCalled();
expect(instance2.ionViewWillUnload).not.toHaveBeenCalled(); expect(instance2.ionViewWillUnload).not.toHaveBeenCalled();
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled(); expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
expect(instance3.ionViewWillLeave).toHaveBeenCalled(); expect(instance3.ionViewWillLeave).toHaveBeenCalled();
expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewDidLeave).toHaveBeenCalled();
expect(instance3.ionViewWillUnload).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled();
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
expect(instance4.ionViewCanLeave).toHaveBeenCalled(); expect(instance4.ionViewCanLeave).toHaveBeenCalled();
expect(instance4.ionViewWillLeave).toHaveBeenCalled(); expect(instance4.ionViewWillLeave).toHaveBeenCalled();
expect(instance4.ionViewDidLeave).toHaveBeenCalled(); expect(instance4.ionViewDidLeave).toHaveBeenCalled();
expect(instance4.ionViewWillUnload).toHaveBeenCalled(); expect(instance4.ionViewWillUnload).toHaveBeenCalled();
let hasCompleted = true; let hasCompleted = true;
let requiresTransition = true; let requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
hasCompleted, requiresTransition, 'MockView2', 'MockView4', DIRECTION_BACK hasCompleted, requiresTransition, 'MockView2', 'MockView4', DIRECTION_BACK
); );
expect(nav.length()).toEqual(2); expect(nav.length()).toEqual(2);
expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.getByIndex(1).component).toEqual(MockView2); expect(nav.getByIndex(1).component).toEqual(MockView2);
}); done();
}).catch((err: Error) => {
fail(err);
done(err);
});
}, 10000);
}); });
@ -821,9 +883,10 @@ describe('NavController', () => {
expect(nav.getByIndex(0).component).toEqual(MockView3); expect(nav.getByIndex(0).component).toEqual(MockView3);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should set a ViewController as the root when its the middle view, with transition', (done: Function) => { it('should set a ViewController as the root when its the middle view, with transition', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
@ -873,9 +936,10 @@ describe('NavController', () => {
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should set a ViewController as the root when its the first view, with transition', (done: Function) => { it('should set a ViewController as the root when its the first view, with transition', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
@ -925,9 +989,10 @@ describe('NavController', () => {
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
it('should set a page component as the root, with transition', (done: Function) => { it('should set a page component as the root, with transition', (done: Function) => {
let view1 = mockView(MockView1); let view1 = mockView(MockView1);
@ -953,9 +1018,10 @@ describe('NavController', () => {
expect(nav.getByIndex(0).component).toEqual(MockView4); expect(nav.getByIndex(0).component).toEqual(MockView4);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
}); });
describe('setPages', () => { describe('setPages', () => {
@ -982,9 +1048,10 @@ describe('NavController', () => {
expect(nav.getByIndex(1).component).toEqual(MockView5); expect(nav.getByIndex(1).component).toEqual(MockView5);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); }, 10000);
}); });

View File

@ -16,6 +16,7 @@ describe('Overlay Proxy', () => {
expect(instance.overlay.dismiss).toHaveBeenCalled(); expect(instance.overlay.dismiss).toHaveBeenCalled();
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });
@ -69,6 +70,7 @@ describe('Overlay Proxy', () => {
expect(knownOverlay.onWillDismiss).toHaveBeenCalledWith(handler); expect(knownOverlay.onWillDismiss).toHaveBeenCalledWith(handler);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });
@ -91,6 +93,7 @@ describe('Overlay Proxy', () => {
expect(deepLinker.getComponentFromName).not.toHaveBeenCalled(); expect(deepLinker.getComponentFromName).not.toHaveBeenCalled();
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });
@ -112,6 +115,7 @@ describe('Overlay Proxy', () => {
expect(deepLinker.getComponentFromName).toHaveBeenCalledWith(componentName); expect(deepLinker.getComponentFromName).toHaveBeenCalledWith(componentName);
done(); done();
}).catch((err: Error) => { }).catch((err: Error) => {
fail(err);
done(err); done(err);
}); });
}); });

View File

@ -541,26 +541,24 @@ export class ViewController {
/** /**
* @hidden * @hidden
*/ */
_lifecycleTest(lifecycle: string): boolean | Promise<any> { _lifecycleTest(lifecycle: string): Promise<any> {
const instance = this.instance; const instance = this.instance;
const methodName = 'ionViewCan' + lifecycle; const methodName = 'ionViewCan' + lifecycle;
if (instance && instance[methodName]) { if (instance && instance[methodName]) {
try { try {
var result = instance[methodName](); var result = instance[methodName]();
if (result === false) { if (result instanceof Promise) {
return false;
} else if (result instanceof Promise) {
return result; return result;
} else { } else {
return true; // Any value but explitic false, should be true
return Promise.resolve(result !== false);
} }
} catch (e) { } catch (e) {
console.error(`${this.name} ${methodName} error: ${e.message}`); return Promise.reject(`${this.name} ${methodName} error: ${e.message}`);
return false;
} }
} }
return true; return Promise.resolve(true);
} }
_lifecycle(lifecycle: string) { _lifecycle(lifecycle: string) {

View File

@ -20,12 +20,12 @@ export class TransitionController {
constructor(public plt: Platform, private _config: Config) {} constructor(public plt: Platform, private _config: Config) {}
getRootTrnsId(nav: NavControllerBase): number { getRootTrnsId(nav: NavControllerBase): number {
let parent = <NavControllerBase>nav.parent; nav = <NavControllerBase>nav.parent;
while (parent) { while (nav) {
if (isPresent(parent._trnsId)) { if (isPresent(nav._trnsId)) {
return parent._trnsId; return nav._trnsId;
} }
parent = parent.parent; nav = nav.parent;
} }
return null; return null;
} }

View File

@ -24,7 +24,12 @@ export class Transition extends Animation {
parent: Transition; parent: Transition;
trnsId: number; trnsId: number;
constructor(plt: Platform, public enteringView: ViewController, public leavingView: ViewController, opts: AnimationOptions) { constructor(
plt: Platform,
public enteringView: ViewController,
public leavingView: ViewController,
opts: AnimationOptions
) {
super(plt, null, opts); super(plt, null, opts);
} }
@ -34,13 +39,12 @@ export class Transition extends Animation {
this._trnsStart = trnsStart; this._trnsStart = trnsStart;
} }
isRoot(): boolean {
return !this.parent;
}
start() { start() {
this._trnsStart && this._trnsStart(); this._trnsStart && this._trnsStart();
this._trnsStart = null; this._trnsStart = null;
// bubble up start
this.parent && this.parent.start();
} }
destroy() { destroy() {

View File

@ -19,7 +19,8 @@ describe('module-loader', () => {
promise.then((response) => { promise.then((response) => {
expect(ngModuleLoader.load).toHaveBeenCalledWith(pathPrefix, exportSuffix); expect(ngModuleLoader.load).toHaveBeenCalledWith(pathPrefix, exportSuffix);
}).catch((err: Error) => { }).catch((err: Error) => {
done(err); fail(err);
done(err);
}); });
}); });