+
+ +
diff --git a/src/components/modal/test/modal.spec.ts b/src/components/modal/test/modal.spec.ts new file mode 100644 index 00000000000..a7fa498d341 --- /dev/null +++ b/src/components/modal/test/modal.spec.ts @@ -0,0 +1,100 @@ +import {Modal, ModalCmp, Page, NavController, ViewController} from '../../../../src'; + +export function run() { + describe('Modal', () => { + + describe('create', () => { + + it('should have the correct properties on modal view controller instance', () => { + let modalViewController = Modal.create(ComponentToPresent); + expect(modalViewController.modalViewType).toEqual("ComponentToPresent"); + expect(modalViewController.componentType).toEqual(ModalCmp); + expect(modalViewController.viewType).toEqual("modal"); + expect(modalViewController.isOverlay).toEqual(true); + expect(modalViewController instanceof ViewController).toEqual(true); + }); + }); + + describe('loaded', () => { + it('should call done after loading component and call original ngAfterViewInit method', (done) => { + // arrange + let modal = new Modal({}, {}); + let mockInstance = { + ngAfterViewInit: () => {}, + loadComponent: () => {} + }; + let mockComponentRef = { + instance: "someData" + }; + modal.instance = mockInstance; + + let ngAfterViewInitSpy = spyOn(mockInstance, "ngAfterViewInit"); + spyOn(mockInstance, "loadComponent").and.returnValue(Promise.resolve(mockComponentRef)); + + let doneCallback = () => { + // assert + expect(ngAfterViewInitSpy).toHaveBeenCalled(); + expect(modal.instance).toEqual("someData"); + done(); + }; + + // act + modal.loaded(doneCallback); + // (angular calls ngAfterViewInit, we're not testing angular so manually call it) + mockInstance.ngAfterViewInit(); + + }, 5000); + }); + }); + + describe('ModalCmp', () => { + + it('should return a componentRef object after loading component', (done) => { + // arrange + let mockLoader = { + loadNextToLocation: () => {} + }; + let mockNavParams = { + data: { + componentType: "myComponentType" + } + }; + let mockComponentRef = {}; + + spyOn(mockLoader, "loadNextToLocation").and.returnValue(Promise.resolve(mockComponentRef)); + let modalCmp = new ModalCmp(null, mockLoader, mockNavParams, null); + modalCmp.viewport = "mockViewport"; + + // act + modalCmp.loadComponent().then(loadedComponentRef => { + // assert + expect(loadedComponentRef).toEqual(mockComponentRef); + expect(mockLoader.loadNextToLocation).toHaveBeenCalledWith(mockNavParams.data.componentType, modalCmp.viewport); + done(); + }); + }, 5000); + }); +} + +const STATE_ACTIVE = 'active'; +const STATE_INACTIVE = 'inactive'; +const STATE_INIT_ENTER = 'init_enter'; +const STATE_INIT_LEAVE = 'init_leave'; +const STATE_TRANS_ENTER = 'trans_enter'; +const STATE_TRANS_LEAVE = 'trans_leave'; +const STATE_REMOVE = 'remove'; +const STATE_REMOVE_AFTER_TRANS = 'remove_after_trans'; +const STATE_FORCE_ACTIVE = 'force_active'; + + +let componentToPresentSpy = { + _ionicProjectContent: () => {}, +}; + +@Page({ + template: `` +}) +class ComponentToPresent{ + constructor(){ + } +} diff --git a/src/components/nav/nav-controller.ts b/src/components/nav/nav-controller.ts index 3d2ac2d12ae..629d13e650d 100644 --- a/src/components/nav/nav-controller.ts +++ b/src/components/nav/nav-controller.ts @@ -505,7 +505,8 @@ export class NavController extends Ion { enteringView.setLeavingOpts({ keyboardClose: false, direction: 'back', - animation: enteringView.getTransitionName('back') + animation: enteringView.getTransitionName('back'), + ev: opts.ev }); // start the transition @@ -818,10 +819,10 @@ export class NavController extends Ion { if (!parentNav['_tabs']) { // Tabs can be a parent, but it is not a collection of views // only we're looking for an actual NavController w/ stack of views - leavingView.willLeave(); + leavingView.fireWillLeave(); return parentNav.pop(opts).then((rtnVal: boolean) => { - leavingView.didLeave(); + leavingView.fireDidLeave(); return rtnVal; }); } @@ -918,7 +919,7 @@ export class NavController extends Ion { // set that it is the init leaving view // the first view to be removed, it should init leave view.state = STATE_INIT_LEAVE; - view.willUnload(); + view.fireWillUnload(); // from the index of the leaving view, go backwards and // find the first view that is inactive so it can be the entering @@ -951,8 +952,8 @@ export class NavController extends Ion { // remove views that have been set to be removed, but not // apart of any transitions that will eventually happen this._views.filter(v => v.state === STATE_REMOVE).forEach(view => { - view.willLeave(); - view.didLeave(); + view.fireWillLeave(); + view.fireDidLeave(); this._views.splice(this.indexOf(view), 1); view.destroy(); }); @@ -986,7 +987,7 @@ export class NavController extends Ion { if (!enteringView) { // if no entering view then create a bogus one enteringView = new ViewController(); - enteringView.loaded(); + enteringView.fireLoaded(); } /* Async steps to complete a transition @@ -1042,19 +1043,8 @@ export class NavController extends Ion { this.setTransitioning(true, 500); this.loadPage(enteringView, null, opts, () => { - if (enteringView.onReady) { - // this entering view needs to wait for it to be ready - // this is used by Tabs to wait for the first page of - // the first selected tab to be loaded - enteringView.onReady(() => { - enteringView.loaded(); - this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done); - }); - - } else { - enteringView.loaded(); - this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done); - } + enteringView.fireLoaded(); + this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done); }); } } @@ -1112,13 +1102,13 @@ export class NavController extends Ion { if (leavingView.fireOtherLifecycles) { // only fire entering lifecycle if the leaving // view hasn't explicitly set not to - enteringView.willEnter(); + enteringView.fireWillEnter(); } if (enteringView.fireOtherLifecycles) { // only fire leaving lifecycle if the entering // view hasn't explicitly set not to - leavingView.willLeave(); + leavingView.fireWillLeave(); } } else { @@ -1224,13 +1214,13 @@ export class NavController extends Ion { if (leavingView.fireOtherLifecycles) { // only fire entering lifecycle if the leaving // view hasn't explicitly set not to - enteringView.didEnter(); + enteringView.fireDidEnter(); } if (enteringView.fireOtherLifecycles) { // only fire leaving lifecycle if the entering // view hasn't explicitly set not to - leavingView.didLeave(); + leavingView.fireDidLeave(); } } @@ -1440,56 +1430,61 @@ export class NavController extends Ion { // load the page component inside the nav this._loader.loadNextToLocation(view.componentType, this._viewport, providers).then(component => { - // the ElementRef of the actual ion-page created - let pageElementRef = component.location; // a new ComponentRef has been created // set the ComponentRef's instance to its ViewController view.setInstance(component.instance); - // remember the ChangeDetectorRef for this ViewController - view.setChangeDetector(component.changeDetectorRef); + // the component has been loaded, so call the view controller's loaded method to load any dependencies into the dom + view.loaded( () => { - // remember the ElementRef to the ion-page elementRef that was just created - view.setPageRef(pageElementRef); + // the ElementRef of the actual ion-page created + let pageElementRef = component.location; - // auto-add page css className created from component JS class name - let cssClassName = pascalCaseToDashCase(view.componentType['name']); - this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true); + // remember the ChangeDetectorRef for this ViewController + view.setChangeDetector(component.changeDetectorRef); - view.onDestroy(() => { - // ensure the element is cleaned up for when the view pool reuses this element - this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null); - this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null); - component.destroy(); - }); + // remember the ElementRef to the ion-page elementRef that was just created + view.setPageRef(pageElementRef); - if (!navbarContainerRef) { - // there was not a navbar container ref already provided - // so use the location of the actual navbar template - navbarContainerRef = view.getNavbarViewRef(); - } - - // find a navbar template if one is in the page - let navbarTemplateRef = view.getNavbarTemplateRef(); - - // check if we have both a navbar ViewContainerRef and a template - if (navbarContainerRef && navbarTemplateRef) { - // let's now create the navbar view - let navbarViewRef = navbarContainerRef.createEmbeddedView(navbarTemplateRef); + // auto-add page css className created from component JS class name + let cssClassName = pascalCaseToDashCase(view.componentType['name']); + this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true); view.onDestroy(() => { - // manually destroy the navbar when the page is destroyed - navbarViewRef.destroy(); + // ensure the element is cleaned up for when the view pool reuses this element + this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null); + this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null); + component.destroy(); }); - } - // options may have had a postLoad method - // used mainly by tabs - opts.postLoad && opts.postLoad(view); + if (!navbarContainerRef) { + // there was not a navbar container ref already provided + // so use the location of the actual navbar template + navbarContainerRef = view.getNavbarViewRef(); + } + + // find a navbar template if one is in the page + let navbarTemplateRef = view.getNavbarTemplateRef(); + + // check if we have both a navbar ViewContainerRef and a template + if (navbarContainerRef && navbarTemplateRef) { + // let's now create the navbar view + let navbarViewRef = navbarContainerRef.createEmbeddedView(navbarTemplateRef); + + view.onDestroy(() => { + // manually destroy the navbar when the page is destroyed + navbarViewRef.destroy(); + }); + } - // our job is done here - done(view); + // options may have had a postLoad method + // used mainly by tabs + opts.postLoad && opts.postLoad(view); + + // our job is done here + done(view); + }); }); } diff --git a/src/components/nav/test/nav-controller.spec.ts b/src/components/nav/test/nav-controller.spec.ts index 109f5955d4e..2b2bf7a43c3 100644 --- a/src/components/nav/test/nav-controller.spec.ts +++ b/src/components/nav/test/nav-controller.spec.ts @@ -269,20 +269,20 @@ export function run() { view4.state = STATE_ACTIVE; nav._views = [view1, view2, view3, view4]; - spyOn(view1, 'willLeave'); - spyOn(view1, 'didLeave'); + spyOn(view1, 'fireWillLeave'); + spyOn(view1, 'fireDidLeave'); spyOn(view1, 'destroy'); - spyOn(view2, 'willLeave'); - spyOn(view2, 'didLeave'); + spyOn(view2, 'fireWillLeave'); + spyOn(view2, 'fireDidLeave'); spyOn(view2, 'destroy'); - spyOn(view3, 'willLeave'); - spyOn(view3, 'didLeave'); + spyOn(view3, 'fireWillLeave'); + spyOn(view3, 'fireDidLeave'); spyOn(view3, 'destroy'); - spyOn(view4, 'willLeave'); - spyOn(view4, 'didLeave'); + spyOn(view4, 'fireWillLeave'); + spyOn(view4, 'fireDidLeave'); spyOn(view4, 'destroy'); nav._remove(1, 3); @@ -292,20 +292,20 @@ export function run() { expect(view3.state).toBe(STATE_REMOVE); expect(view4.state).toBe(STATE_INIT_LEAVE); - expect(view1.willLeave).not.toHaveBeenCalled(); - expect(view1.didLeave).not.toHaveBeenCalled(); + expect(view1.fireWillLeave).not.toHaveBeenCalled(); + expect(view1.fireDidLeave).not.toHaveBeenCalled(); expect(view1.destroy).not.toHaveBeenCalled(); - expect(view2.willLeave).toHaveBeenCalled(); - expect(view2.didLeave).toHaveBeenCalled(); + expect(view2.fireWillLeave).toHaveBeenCalled(); + expect(view2.fireDidLeave).toHaveBeenCalled(); expect(view2.destroy).toHaveBeenCalled(); - expect(view3.willLeave).toHaveBeenCalled(); - expect(view3.didLeave).toHaveBeenCalled(); + expect(view3.fireWillLeave).toHaveBeenCalled(); + expect(view3.fireDidLeave).toHaveBeenCalled(); expect(view3.destroy).toHaveBeenCalled(); - expect(view4.willLeave).not.toHaveBeenCalled(); - expect(view4.didLeave).not.toHaveBeenCalled(); + expect(view4.fireWillLeave).not.toHaveBeenCalled(); + expect(view4.fireDidLeave).not.toHaveBeenCalled(); expect(view4.destroy).not.toHaveBeenCalled(); }); }); @@ -412,11 +412,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); + spyOn(enteringView, 'fireWillEnter'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).toHaveBeenCalled(); + expect(enteringView.fireWillEnter).toHaveBeenCalled(); }); it('should not call willEnter on entering view when it is being preloaded', () => { @@ -428,11 +428,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); + spyOn(enteringView, 'fireWillEnter'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).not.toHaveBeenCalled(); + expect(enteringView.fireWillEnter).not.toHaveBeenCalled(); }); it('should call willLeave on leaving view', () => { @@ -442,11 +442,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(leavingView, 'willLeave'); + spyOn(leavingView, 'fireWillLeave'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(leavingView.willLeave).toHaveBeenCalled(); + expect(leavingView.fireWillLeave).toHaveBeenCalled(); }); it('should not call willEnter when the leaving view has fireOtherLifecycles not true', () => { @@ -456,15 +456,15 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); - spyOn(leavingView, 'willLeave'); + spyOn(enteringView, 'fireWillEnter'); + spyOn(leavingView, 'fireWillLeave'); leavingView.fireOtherLifecycles = false; nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).not.toHaveBeenCalled(); - expect(leavingView.willLeave).toHaveBeenCalled(); + expect(enteringView.fireWillEnter).not.toHaveBeenCalled(); + expect(leavingView.fireWillLeave).toHaveBeenCalled(); }); it('should not call willLeave when the entering view has fireOtherLifecycles not true', () => { @@ -474,15 +474,15 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); - spyOn(leavingView, 'willLeave'); + spyOn(enteringView, 'fireWillEnter'); + spyOn(leavingView, 'fireWillLeave'); enteringView.fireOtherLifecycles = false; nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).toHaveBeenCalled(); - expect(leavingView.willLeave).not.toHaveBeenCalled(); + expect(enteringView.fireWillEnter).toHaveBeenCalled(); + expect(leavingView.fireWillLeave).not.toHaveBeenCalled(); }); it('should not call willLeave on leaving view when it is being preloaded', () => { @@ -494,11 +494,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(leavingView, 'willLeave'); + spyOn(leavingView, 'fireWillLeave'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(leavingView.willLeave).not.toHaveBeenCalled(); + expect(leavingView.fireWillLeave).not.toHaveBeenCalled(); }); it('should set animate false when preloading', () => { @@ -725,13 +725,13 @@ export function run() { let doneCalled = false; let done = () => {doneCalled = true;} - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).toHaveBeenCalled(); - expect(leavingView.didLeave).toHaveBeenCalled(); + expect(enteringView.fireDidEnter).toHaveBeenCalled(); + expect(leavingView.fireDidLeave).toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -745,13 +745,13 @@ export function run() { let doneCalled = false; let done = () => {doneCalled = true;} - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).not.toHaveBeenCalled(); - expect(leavingView.didLeave).not.toHaveBeenCalled(); + expect(enteringView.fireDidEnter).not.toHaveBeenCalled(); + expect(leavingView.fireDidLeave).not.toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -765,13 +765,13 @@ export function run() { enteringView.fireOtherLifecycles = false; - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).toHaveBeenCalled(); - expect(leavingView.didLeave).not.toHaveBeenCalled(); + expect(enteringView.fireDidEnter).toHaveBeenCalled(); + expect(leavingView.fireDidLeave).not.toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -785,13 +785,13 @@ export function run() { leavingView.fireOtherLifecycles = false; - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).not.toHaveBeenCalled(); - expect(leavingView.didLeave).toHaveBeenCalled(); + expect(enteringView.fireDidEnter).not.toHaveBeenCalled(); + expect(leavingView.fireDidLeave).toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -803,13 +803,13 @@ export function run() { let doneCalled = false; let done = () => {doneCalled = true;} - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).not.toHaveBeenCalled(); - expect(leavingView.didLeave).not.toHaveBeenCalled(); + expect(enteringView.fireDidEnter).not.toHaveBeenCalled(); + expect(leavingView.fireDidLeave).not.toHaveBeenCalled(); expect(doneCalled).toBe(true); }); diff --git a/src/components/nav/test/view-controller.spec.ts b/src/components/nav/test/view-controller.spec.ts new file mode 100644 index 00000000000..680e29997f7 --- /dev/null +++ b/src/components/nav/test/view-controller.spec.ts @@ -0,0 +1,133 @@ +import {LifeCycleEvent, ViewController} from '../../../../src'; + +export function run() { + describe('ViewController', () => { + + afterEach(() => { + if ( subscription ){ + subscription.unsubscribe(); + } + }); + + describe('loaded', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didLoad.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireLoaded(); + }, 10000); + }); + + describe('willEnter', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.willEnter.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireWillEnter(); + }, 10000); + }); + + describe('didEnter', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didEnter.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireDidEnter(); + }, 10000); + }); + + describe('willLeave', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.willLeave.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireWillLeave(); + }, 10000); + }); + + describe('didLeave', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didLeave.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireDidLeave(); + }, 10000); + }); + + describe('willUnload', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.willUnload.subscribe((event:LifeCycleEvent) => { + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireWillUnload(); + }, 10000); + }); + + describe('destroy', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didUnload.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.destroy(); + }, 10000); + }); + }); + + let subscription = null; + class FakePage{} +} diff --git a/src/components/nav/view-controller.ts b/src/components/nav/view-controller.ts index 6936e2be5d6..53659708177 100644 --- a/src/components/nav/view-controller.ts +++ b/src/components/nav/view-controller.ts @@ -37,6 +37,14 @@ export class ViewController { private _cd: ChangeDetectorRef; protected _nav: NavController; + didLoad: EventEmitter