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 = {
configFile: join(SCRIPTS_ROOT, 'karma/karma.conf.js'),
singleRun: true,
};
if (watch) {
@ -96,6 +97,9 @@ function karmaTest(watch: boolean, done: Function) {
args: ['--grep', argv.testGrep]
};
}
if (typeof argv.debug !== 'undefined') {
karmaConfig.singleRun = false;
}
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
console.debug = () => {};
console.error = () => {};
// console.error = () => {};
console.warn = () => {};
__karma__.loaded = function () {};

View File

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

View File

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

View File

@ -42,7 +42,7 @@
</ion-item>
<ion-item>
<ion-label>Toggle Can Leave</ion-label>
<ion-toggle (click)="canLeave = !canLeave"></ion-toggle>
<ion-toggle [(ngModel)]="canLeave"></ion-toggle>
</ion-item>
<button ion-item (click)="viewDismiss()">View Dismiss</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);
done();
}).catch((err: Error) => {
fail(err);
done(err);
});
});
@ -56,6 +57,7 @@ describe('Nav', () => {
expect(nav.setPages).toHaveBeenCalledWith(knownViews, null, null);
done();
}).catch((err: Error) => {
fail(err);
done(err);
});
});
@ -72,7 +74,8 @@ describe('Nav', () => {
promise.then(() => {
expect(nav.push).toHaveBeenCalled();
done();
}).catch((err: Error) => {
}).catch((err: Error) => {
fail(err);
done(err);
});
});

View File

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

View File

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

View File

@ -3,8 +3,8 @@ import { ComponentRef, Input, ComponentFactoryResolver, ElementRef, EventEmitter
import { AnimationOptions } from '../animations/animation';
import { App } from '../components/app/app';
import { Config } from '../config/config';
import { convertToView, convertToViews, NavOptions, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX,
TransitionResolveFn, TransitionInstruction, STATE_NEW, STATE_INITIALIZED, STATE_ATTACHED, STATE_DESTROYED } from './nav-util';
import { convertToViews, NavOptions, NavResult, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX,
TransitionInstruction, STATE_NEW, STATE_INITIALIZED, STATE_ATTACHED, STATE_DESTROYED } from './nav-util';
import { setZIndex } from './nav-util';
import { DeepLinker } from './deep-linker';
import { DomController } from '../platform/dom-controller';
@ -80,40 +80,31 @@ export class NavControllerBase extends Ion implements NavController {
this.id = 'n' + (++ctrlIds);
}
push(page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> {
return convertToView(this._linker, page, params).then(viewController => {
return this._queueTrns({
insertStart: -1,
insertViews: [viewController],
opts: opts,
}, done);
}).catch((err: Error) => {
console.error('Failed to navigate: ', err.message);
throw err;
});
push(page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({
insertStart: -1,
insertViews: [{ page: page, params: params }],
opts: opts,
}, done);
}
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> {
return convertToView(this._linker, page, params).then(viewController => {
return this._queueTrns({
insertStart: insertIndex,
insertViews: [viewController],
opts: opts,
}, done);
});
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({
insertStart: insertIndex,
insertViews: [{ page: page, params: params }],
opts: opts,
}, done);
}
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: Function): Promise<any> {
return convertToViews(this._linker, insertPages).then(viewControllers => {
return this._queueTrns({
insertStart: insertIndex,
insertViews: viewControllers,
opts: opts,
}, done);
});
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({
insertStart: insertIndex,
insertViews: insertPages,
opts: opts,
}, done);
}
pop(opts?: NavOptions, done?: Function): Promise<any> {
pop(opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({
removeStart: -1,
removeCount: 1,
@ -121,7 +112,7 @@ export class NavControllerBase extends Ion implements NavController {
}, done);
}
popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: Function): Promise<any> {
popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: () => void): Promise<any> {
let config: TransitionInstruction = {
removeStart: -1,
removeCount: -1,
@ -136,7 +127,7 @@ export class NavControllerBase extends Ion implements NavController {
return this._queueTrns(config, done);
}
popToRoot(opts?: NavOptions, done?: Function): Promise<any> {
popToRoot(opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({
removeStart: 1,
removeCount: -1,
@ -152,7 +143,7 @@ export class NavControllerBase extends Ion implements NavController {
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({
removeStart: startIndex,
removeCount: removeCount,
@ -160,7 +151,7 @@ export class NavControllerBase extends Ion implements NavController {
}, done);
}
removeView(viewController: ViewController, opts?: NavOptions, done?: Function): Promise<any> {
removeView(viewController: ViewController, opts?: NavOptions, done?: () => void): Promise<any> {
return this._queueTrns({
removeView: viewController,
removeStart: 0,
@ -169,19 +160,12 @@ export class NavControllerBase extends Ion implements NavController {
}, done);
}
setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> {
return convertToView(this._linker, pageOrViewCtrl, params).then((viewController) => {
return this._setPages([viewController], opts, done);
});
setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return this.setPages([{ page: pageOrViewCtrl, params: params }], 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)) {
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.
// 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.
_queueTrns(ti: TransitionInstruction, done: Function): Promise<any> {
let promise: Promise<any>;
let resolve: Function = done;
let reject: Function = done;
_queueTrns(ti: TransitionInstruction, done: () => void): Promise<boolean> {
const promise = new Promise<boolean>((resolve, reject) => {
ti.resolve = resolve;
ti.reject = reject;
});
ti.done = done;
if (done === undefined) {
// only create a promise if a done callback wasn't provided
// done can be a null, which avoids any functions
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;
// Normalize empty
if (ti.insertViews && ti.insertViews.length === 0) {
ti.insertViews = undefined;
}
// Enqueue transition instruction
this._queue.push(ti);
// if there isn't a transition already happening
// then this will kick off this transition
this._nextTrns();
// promise is undefined if a done callbacks was provided
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 {
// this is the framework's bread 'n butta function
// 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
// get the next instruction
const ti = this._nextTI();
const ti = this._queue.shift();
if (!ti) {
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
this.setTransitioning(true);
let enteringView: ViewController;
let leavingView: ViewController;
// Initialize enteringView
if (enteringView && enteringView._state === STATE_NEW) {
// render the entering view, and all child navs and views
// ******** DOM WRITE ****************
this._viewInit(enteringView);
}
this._startTI(ti)
.then(() => this._loadLazyLoading(ti))
.then(() => {
leavingView = this.getActive();
enteringView = this._getEnteringView(ti, leavingView);
// Only test canLeave/canEnter if there is transition
const requiresTransition = ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView;
if (requiresTransition) {
// views have been initialized, now let's test
// to see if the transition is even allowed or not
return this._viewTest(enteringView, leavingView, ti);
} else {
return this._postViewInit(enteringView, leavingView, ti);
}
if (!leavingView && !enteringView) {
throw 'no views in the stack to be removed';
}
if (enteringView && enteringView._state === STATE_NEW) {
this._viewInit(enteringView);
}
// 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 {
const ti = this._queue.shift();
if (!ti) {
return null;
}
_startTI(ti: TransitionInstruction): Promise<void> {
const viewsLength = this._views.length;
if (isPresent(ti.removeView)) {
assert(isPresent(ti.removeStart), 'removeView needs removeStart');
assert(isPresent(ti.removeCount), 'removeView needs removeCount');
var index = this._views.indexOf(ti.removeView);
if (index >= 0) {
ti.removeStart += index;
const index = this.indexOf(ti.removeView);
if (index < 0) {
return Promise.reject('removeView was not found');
}
ti.removeStart += index;
}
if (isPresent(ti.removeStart)) {
if (ti.removeStart < 0) {
@ -373,7 +329,35 @@ export class NavControllerBase extends Ion implements NavController {
}
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 {
@ -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.`,
this, this.getNativeElement());
ti.reject('navigation stack needs at least one root page');
return false;
throw 'navigation stack needs at least one root page';
}
// At this point the transition can not be rejected, any throw should be an error
// there are views to insert
if (insertViews) {
// 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
if (!opts.animation) {
if (ti.requiresTransition && !opts.animation) {
if (isPresent(ti.removeStart)) {
opts.animation = (leavingView || enteringView).getTransitionName(opts.direction);
} else {
opts.animation = (enteringView || leavingView).getTransitionName(opts.direction);
}
}
// huzzah! let us transition these views
this._transitionInit(enteringView, leavingView, opts, ti.resolve);
return true;
ti.opts = opts;
}
/**
@ -509,6 +481,7 @@ export class NavControllerBase extends Ion implements NavController {
assert(enteringView, 'enteringView must be non null');
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
const componentProviders = ReflectiveInjector.resolve([
{ provide: NavController, useValue: this },
@ -551,52 +524,52 @@ export class NavControllerBase extends Ion implements NavController {
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>[] = [];
if (leavingView) {
var leavingTestResult = 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);
}
promises.push(leavingView._lifecycleTest('Leave'));
}
if (enteringView) {
var enteringTestResult = enteringView._lifecycleTest('Enter');
promises.push(enteringView._lifecycleTest('Enter'));
}
if (enteringTestResult === false) {
// synchronous reject
ti.reject((enteringTestResult !== false ? enteringTestResult : `ionViewCanEnter rejected`));
return false;
} else if (enteringTestResult instanceof Promise) {
// async promise
promises.push(enteringTestResult);
if (promises.length === 0) {
return Promise.resolve();
}
// darn, async promises, gotta wait for them to resolve
return Promise.all(promises).then((values: any[]) => {
if (values.some(result => result === false)) {
throw 'canEnter/Leave returned false';
}
}
if (promises.length) {
// darn, async promises, gotta wait for them to resolve
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);
}
}).catch((reason) => {
// Do not
ti.reject = null;
throw reason;
});
}
_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
// child of a parent nav that has the root transition
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.registerStart(() => {
this._transitionStart(transition, enteringView, leavingView, opts, resolve);
if (transition.parent) {
transition.parent.start();
}
const promise = new Promise<void>(resolve => transition.registerStart(resolve)).then(() => {
return this._transitionStart(transition, enteringView, leavingView, opts);
});
if (enteringView && (enteringView._state === STATE_INITIALIZED)) {
@ -645,13 +615,15 @@ export class NavControllerBase extends Ion implements NavController {
this._viewAttachToDOM(enteringView, enteringView._cmp, this._viewport);
}
if (!transition.hasChildren) {
// lowest level transition, so kick it off and let it bubble up to start all of them
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');
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
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
const duration = transition.getDuration();
// create a callback for when the animation is done
const promise = new Promise(resolve => {
transition.onFinish(resolve);
});
if (transition.isRoot()) {
// 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
@ -723,9 +694,14 @@ export class NavControllerBase extends Ion implements NavController {
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 enteringView = transition.enteringView;
const leavingView = transition.leavingView;
@ -774,8 +750,13 @@ export class NavControllerBase extends Ion implements NavController {
}
}
// congrats, we did it!
resolve(hasCompleted, true, enteringName, leavingName, opts.direction);
return {
hasCompleted: hasCompleted,
requiresTransition: true,
enteringName: enteringName,
leavingName: leavingName,
direction: opts.direction
};
}
_viewsWillLifecycles(enteringView: ViewController, leavingView: ViewController) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,12 @@ export class Transition extends Animation {
parent: Transition;
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);
}
@ -34,13 +39,12 @@ export class Transition extends Animation {
this._trnsStart = trnsStart;
}
isRoot(): boolean {
return !this.parent;
}
start() {
this._trnsStart && this._trnsStart();
this._trnsStart = null;
// bubble up start
this.parent && this.parent.start();
}
destroy() {

View File

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