diff --git a/tests/app/navigation/navigation-tests.ts b/tests/app/navigation/navigation-tests.ts index f601b13c1..bdd5de1c2 100644 --- a/tests/app/navigation/navigation-tests.ts +++ b/tests/app/navigation/navigation-tests.ts @@ -1,9 +1,11 @@ import * as TKUnit from "../TKUnit"; import { EventData, Page, NavigatedData } from "tns-core-modules/ui/page"; import { topmost as topmostFrame, NavigationTransition } from "tns-core-modules/ui/frame"; +import { StackLayout, } from "tns-core-modules/ui/layouts/stack-layout"; +import { GridLayout, } from "tns-core-modules/ui/layouts/grid-layout"; import { Color } from "tns-core-modules/color"; import * as helper from "../ui/helper"; - +import * as frame from "tns-core-modules/ui/frame"; // Creates a random colorful page full of meaningless stuff. let id = 0; let pageFactory = function (): Page { @@ -51,6 +53,65 @@ export function test_backstackVisible_WithTransition() { _test_backstackVisible({ name: "fade", duration: 10 }); } +export function test_backAndForwardParentPage_nestedFrames() { + const topmost = topmostFrame(); + const mainTestPage = topmost.currentPage; + let innerFrame; + + const page = (title) => { + const p = new Page(); + p["tag"] = title; + return p; + }; + + const parentPage = (title, innerPage) => { + const parentPage = new Page(); + parentPage["tag"] = title; + + const stack = new StackLayout(); + innerFrame = new frame.Frame(); + innerFrame.navigate({ create: () => innerPage }); + stack.addChild(innerFrame); + parentPage.content = stack; + + return parentPage; + } + + const back = pages => topmostFrame().goBack(topmostFrame().backStack[topmostFrame().backStack.length - pages]); + const currentPageMustBe = tag => TKUnit.assertEqual(topmostFrame().currentPage["tag"], tag, "Expected current page to be " + tag + " it was " + topmostFrame().currentPage["tag"] + " instead."); + + let parentPage1, parentPage2, innerPage1, innerPage2; + innerPage1 = page("InnerPage1"); + innerPage2 = page("InnerPage2"); + parentPage1 = page("ParentPage1"); + parentPage2 = parentPage("ParentPage2", innerPage1); + + helper.waitUntilNavigatedTo(parentPage1, () => topmost.navigate({ create: () => parentPage1 })); + currentPageMustBe("ParentPage1"); + + helper.waitUntilNavigatedTo(parentPage2, () => topmost.navigate({ create: () => parentPage2 })); + currentPageMustBe("ParentPage2"); + + helper.waitUntilNavigatedTo(innerPage2, () => innerFrame.navigate({ create: () => innerPage2 })); + currentPageMustBe("InnerPage2"); + + helper.waitUntilNavigatedTo(innerPage1, () => frame.goBack()); + currentPageMustBe("InnerPage1"); + + helper.waitUntilNavigatedTo(parentPage1, () => frame.goBack()); + currentPageMustBe("ParentPage1"); + + helper.waitUntilNavigatedTo(parentPage2, () => topmost.navigate({ create: () => parentPage2 })); + currentPageMustBe("ParentPage2"); + + back(2); + TKUnit.waitUntilReady(() => topmostFrame().navigationQueueIsEmpty()); + + const frameStack = frame.stack(); + TKUnit.assertEqual(frameStack.length, 1, "There should be only one frame left in the stack"); + TKUnit.assertEqual(topmostFrame().currentPage, mainTestPage, "We should be on the main test page at the end of the test."); +} + function _test_backToEntry(transition?: NavigationTransition) { const topmost = topmostFrame(); const page = (tag) => () => { @@ -362,10 +423,10 @@ function _test_NavigationEvents_WithClearHistory(transition?: NavigationTransiti // Go to second page helper.navigateWithEntry({ create: secondPageFactory, transition: transition, animated: !!transition, clearHistory: true }); - const expectedMainPageEvents = [ "main-page navigatingFrom forward", "main-page navigatedFrom forward" ]; + const expectedMainPageEvents = ["main-page navigatingFrom forward", "main-page navigatedFrom forward"]; TKUnit.arrayAssert(actualMainPageEvents, expectedMainPageEvents, "Actual main-page events are different from expected."); - const expectedSecondPageEvents = [ "second-page navigatingTo forward", "second-page navigatedTo forward" ]; + const expectedSecondPageEvents = ["second-page navigatingTo forward", "second-page navigatedTo forward"]; TKUnit.arrayAssert(actualSecondPageEvents, expectedSecondPageEvents, "Actual main-page events are different from expected."); TKUnit.assertEqual(topmost.currentPage, secondPage, "We should be on the second page at the end of the test."); @@ -393,7 +454,7 @@ function _test_Navigate_From_Page_Event_Handler(eventName: string) { const firstPageFactory = function (): Page { const firstPage = new Page(); firstPage.id = "first-page"; - firstPage.on(eventName, (args: EventData) => { + firstPage.on(eventName, (args: EventData) => { const page = args.object; const frame = page.frame; diff --git a/tns-core-modules/ui/frame/frame-common.ts b/tns-core-modules/ui/frame/frame-common.ts index fe6c61ba4..ddc65915d 100644 --- a/tns-core-modules/ui/frame/frame-common.ts +++ b/tns-core-modules/ui/frame/frame-common.ts @@ -3,6 +3,7 @@ import { Frame as FrameDefinition, NavigationEntry, BackstackEntry, NavigationTr import { Page } from "../page"; // Types. +import { getAncestor } from "../core/view/view-common"; import { View, CustomLayoutView, isIOS, isAndroid, traceEnabled, traceWrite, traceCategories, Property, CSSType } from "../core/view"; import { createViewFromEntry } from "../builder"; import { profile } from "../../profiling"; @@ -215,6 +216,10 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition { this._currentEntry = entry; + if (isBack) { + this._pushInFrameStack(); + } + newPage.onNavigatedTo(isBack); // Reset executing entry after NavigatedTo is raised; @@ -573,6 +578,22 @@ export function goBack(): boolean { if (top && top.canGoBack()) { top.goBack(); return true; + } else if (top) { + let parentFrameCanGoBack = false; + let parentFrame = getAncestor(top, "Frame"); + + while (parentFrame && !parentFrameCanGoBack) { + if (parentFrame && parentFrame.canGoBack()) { + parentFrameCanGoBack = true; + } else { + parentFrame = getAncestor(top, "Frame"); + } + } + + if (parentFrame && parentFrameCanGoBack) { + parentFrame.goBack(); + return true; + } } if (frameStack.length > 1) { diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 6dba886d6..5e2f8f2e9 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -204,8 +204,8 @@ export class Frame extends FrameBase { // however, we must add a fragment.isAdded() guard as our logic will try to // explicitly remove the already removed child fragment causing an // IllegalStateException: Fragment has not been attached yet. - if (!this._currentEntry || - !this._currentEntry.fragment || + if (!this._currentEntry || + !this._currentEntry.fragment || !this._currentEntry.fragment.isAdded()) { return; } @@ -423,6 +423,7 @@ export class Frame extends FrameBase { } this._android.rootViewGroup = null; + this._removeFromFrameStack(); super.disposeNativeView(); } diff --git a/tns-core-modules/ui/frame/frame.ios.ts b/tns-core-modules/ui/frame/frame.ios.ts index 7e5d6ab7c..bdf5bd72b 100644 --- a/tns-core-modules/ui/frame/frame.ios.ts +++ b/tns-core-modules/ui/frame/frame.ios.ts @@ -36,6 +36,11 @@ export class Frame extends FrameBase { return this.viewController.view; } + public disposeNativeView() { + this._removeFromFrameStack(); + super.disposeNativeView(); + } + public get ios(): iOSFrame { return this._ios; }