diff --git a/e2e/modal-navigation-ng/app/app.css b/e2e/modal-navigation-ng/app/app.css
index d23504c4e..5e41a4552 100644
--- a/e2e/modal-navigation-ng/app/app.css
+++ b/e2e/modal-navigation-ng/app/app.css
@@ -10,3 +10,6 @@ of writing your own CSS rules. For a full list of class names in the theme
refer to http://docs.nativescript.org/ui/theme.
*/
@import '~nativescript-theme-core/css/core.light.css';
+Button {
+ font-size: 10px;
+}
diff --git a/e2e/modal-navigation-ng/app/app.module.ts b/e2e/modal-navigation-ng/app/app.module.ts
index 2afa7f59a..9251b6661 100644
--- a/e2e/modal-navigation-ng/app/app.module.ts
+++ b/e2e/modal-navigation-ng/app/app.module.ts
@@ -2,6 +2,7 @@ import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppRoutingModule } from "./app.routing";
import { AppComponent } from "./app.component";
+import { NamedRouterComponent } from "./named-router.component";
import { TabComponent } from "./tab.component";
import { LayoutComponent } from "./layout.component";
@@ -29,16 +30,18 @@ traceEnable();
AppRoutingModule
],
entryComponents: [
- AppComponent,
- TabComponent,
- LayoutComponent,
- ModalRouterComponent,
- NestedModalComponent,
- ModalComponent,
+ AppComponent,
+ NamedRouterComponent,
+ TabComponent,
+ LayoutComponent,
+ ModalRouterComponent,
+ NestedModalComponent,
+ ModalComponent,
ModalViewComponent
],
declarations: [
AppComponent,
+ NamedRouterComponent,
TabComponent,
LayoutComponent,
HomeComponent,
@@ -75,8 +78,9 @@ export class AppModule {
static bootstrapRootComponent() {
const options = {
'page-router': AppComponent,
+ 'named-page-router': NamedRouterComponent,
'tab': TabComponent,
- 'layout': LayoutComponent
+ 'layout': LayoutComponent
};
const component = options[AppModule.root];
diff --git a/e2e/modal-navigation-ng/app/app.routing.ts b/e2e/modal-navigation-ng/app/app.routing.ts
index f4b156db3..0a49e8251 100644
--- a/e2e/modal-navigation-ng/app/app.routing.ts
+++ b/e2e/modal-navigation-ng/app/app.routing.ts
@@ -40,6 +40,34 @@ const routes: Routes = [
}
];
+const namedOutletRoutes: Routes = [
+ { path: "", redirectTo: "/(namedRouter:home)", pathMatch: "full" },
+ {
+ path: "home", component: HomeComponent, outlet: "namedRouter", children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", component: ModalSecondComponent }
+ ]
+ },
+ {
+ path: "second", outlet: "namedRouter", component: SecondComponent, children: [
+ {
+ path: "modal", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", component: ModalSecondComponent }
+ ]
+ },
+ {
+ path: "modal-shared", component: ModalViewContentComponent, outlet: "modalOutlet"
+ },
+ {
+ path: "modal-shared-second-host", outlet: "namedRouter", component: ModalSharedSecondComponent
+ }
+];
+
const routesTab: Routes = [
{ path: "", redirectTo: "/home(secondOutlet:second)", pathMatch: "full" },
{
@@ -96,6 +124,8 @@ export class AppRoutingModule {
this.router.resetConfig(routes);
} else if (AppModule.root === "layout") {
this.router.resetConfig(routesLayout);
+ } else if (AppModule.root === "named-page-router") {
+ this.router.resetConfig(namedOutletRoutes);
} else {
this.router.resetConfig(routesTab);
}
diff --git a/e2e/modal-navigation-ng/app/home/home.component.html b/e2e/modal-navigation-ng/app/home/home.component.html
index 3f1172827..9601e29ce 100644
--- a/e2e/modal-navigation-ng/app/home/home.component.html
+++ b/e2e/modal-navigation-ng/app/home/home.component.html
@@ -1,11 +1,12 @@
-
+
-
+
+
@@ -13,5 +14,4 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/home/home.component.ts b/e2e/modal-navigation-ng/app/home/home.component.ts
index df5a57139..8fc08fb7c 100644
--- a/e2e/modal-navigation-ng/app/home/home.component.ts
+++ b/e2e/modal-navigation-ng/app/home/home.component.ts
@@ -61,6 +61,11 @@ export class HomeComponent {
AppModule.platformRef._livesync();
}
+ onNamedFrameRootViewReset() {
+ AppModule.root = "named-page-router";
+ AppModule.platformRef._livesync();
+ }
+
onTabRootViewReset() {
AppModule.root = "tab";
AppModule.platformRef._livesync();
diff --git a/e2e/modal-navigation-ng/app/layout.component.html b/e2e/modal-navigation-ng/app/layout.component.html
index 96f4323fc..3d50caa77 100644
--- a/e2e/modal-navigation-ng/app/layout.component.html
+++ b/e2e/modal-navigation-ng/app/layout.component.html
@@ -1,9 +1,10 @@
-
-
+
+
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/layout.component.ts b/e2e/modal-navigation-ng/app/layout.component.ts
index 93407ca2f..de4c8eb45 100644
--- a/e2e/modal-navigation-ng/app/layout.component.ts
+++ b/e2e/modal-navigation-ng/app/layout.component.ts
@@ -67,6 +67,11 @@ export class LayoutComponent {
AppModule.platformRef._livesync();
}
+ onNamedFrameRootViewReset() {
+ AppModule.root = "named-page-router";
+ AppModule.platformRef._livesync();
+ }
+
onTabRootViewReset() {
AppModule.root = "tab";
AppModule.platformRef._livesync();
diff --git a/e2e/modal-navigation-ng/app/named-router.component.html b/e2e/modal-navigation-ng/app/named-router.component.html
new file mode 100644
index 000000000..83ba115c4
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/named-router.component.html
@@ -0,0 +1 @@
+
diff --git a/e2e/modal-navigation-ng/app/named-router.component.ts b/e2e/modal-navigation-ng/app/named-router.component.ts
new file mode 100644
index 000000000..e00c929a3
--- /dev/null
+++ b/e2e/modal-navigation-ng/app/named-router.component.ts
@@ -0,0 +1,27 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { Router, NavigationEnd } from "@angular/router";
+import { NSLocationStrategy } from "nativescript-angular/router/ns-location-strategy";
+
+import { ViewContainerRefService } from "./shared/ViewContainerRefService";
+
+@Component({
+ selector: "named-router",
+ templateUrl: "named-router.component.html",
+})
+
+export class NamedRouterComponent {
+ constructor(
+ router: Router,
+ location: NSLocationStrategy,
+ private _vcRef: ViewContainerRef,
+ private _viewContainerRefService: ViewContainerRefService) {
+ router.events.subscribe(e => {
+ if (e instanceof NavigationEnd) {
+ console.log("[ROUTER]: " + e.toString());
+ console.log(location.toString());
+ }
+ });
+
+ this._viewContainerRefService.root = this._vcRef;
+ }
+}
diff --git a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts
new file mode 100644
index 000000000..2c9b95206
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts
@@ -0,0 +1,77 @@
+import { AppiumDriver, createDriver } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ roots,
+ modalFrameBackground,
+ testSecondPageBackground,
+ testSecondPageClose,
+ testNestedModalFrameBackground,
+ testNestedModalPageBackground,
+ testDialogBackground
+} from "./shared.e2e-spec"
+
+describe("modal-frame:", () => {
+
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async () => {
+ driver = await createDriver();
+ screen = new Screen(driver);
+ });
+
+ roots.forEach(root => {
+ describe(`${root} modal frame background scenarios:`, () => {
+
+ before(async () => {
+ await screen[root]();
+ });
+
+ beforeEach(async function () {
+ await screen.loadModalFrame();
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logPageSource(this.currentTest.title);
+ await driver.logScreenshot(this.currentTest.title);
+ await driver.resetApp();
+ await screen[root]();
+ }
+ });
+
+ after(async () => {
+ await screen.closeModal();
+ await screen.loadedHome();
+ });
+
+ it("should show dialog confirm, run in background", async () => {
+ await testDialogBackground(driver, screen);
+ });
+
+ it("should run modal page with frame in background", async () => {
+ await modalFrameBackground(driver, screen);
+ });
+
+ it("should navigate to second page, run in background, go back", async () => {
+ await testSecondPageBackground(driver, screen);
+ });
+
+ it("should show nested modal page with frame, run in background, close", async () => {
+ await testNestedModalFrameBackground(driver, screen);
+ });
+
+ it("should show nested modal page, run in background, close", async () => {
+ await testNestedModalPageBackground(driver, screen);
+ });
+
+ it("should navigate to second page, close", async () => {
+ await testSecondPageClose(driver, screen);
+ });
+
+ it("should navigate to second page, run in background, go back", async () => {
+ await testSecondPageBackground(driver, screen);
+ });
+ });
+ });
+});
diff --git a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts
new file mode 100644
index 000000000..9cd95bea6
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts
@@ -0,0 +1,53 @@
+import { AppiumDriver, createDriver } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ roots,
+ modalFrameBackground,
+ testSecondPageBackground,
+ testSecondPageClose,
+ testNestedModalFrameBackground,
+ testNestedModalPageBackground,
+ testDialogBackground,
+} from "./shared.e2e-spec"
+
+describe("modal-layout:", () => {
+
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async () => {
+ driver = await createDriver();
+ screen = new Screen(driver);
+ });
+
+ roots.forEach(root => {
+ describe(`${root} modal no frame background scenarios:`, () => {
+
+ before(async () => {
+ await screen[root]();
+ });
+
+ beforeEach(async function () {
+ await screen.loadModalNoFrame();
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logPageSource(this.currentTest.title);
+ await driver.logScreenshot(this.currentTest.title);
+ await driver.resetApp();
+ await screen[root]();
+ }
+ });
+
+ after(async () => {
+ await screen.closeModal();
+ await screen.loadedHome();
+ });
+
+ it("should show dialog confirm inside modal view with no frame, run in background", async () => {
+ await testDialogBackground(driver, screen, false);
+ });
+ });
+ });
+});
diff --git a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
index ecdc240b4..75f4867ad 100644
--- a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
+++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
@@ -1,6 +1,8 @@
import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium";
import { assert } from "chai";
+const homeComponent = "Home Component";
+
describe("Shared modal from home and back", () => {
let driver: AppiumDriver;
@@ -22,7 +24,7 @@ describe("Shared modal from home and back", () => {
});
it ("should find home component", async () => {
- await assertComponent(driver, "home component");
+ await assertComponent(driver, homeComponent);
});
it("should open/close shared modal from home component", async () => {
@@ -31,7 +33,7 @@ describe("Shared modal from home and back", () => {
});
it ("should find home component again", async () => {
- await assertComponent(driver, "home component");
+ await assertComponent(driver, homeComponent);
});
});
@@ -56,7 +58,7 @@ describe("Shared modal from second and back", () => {
});
it ("should find home component", async () => {
- await assertComponent(driver, "home component");
+ await assertComponent(driver, homeComponent);
});
it ("should navigate to second component", async() => {
@@ -98,7 +100,7 @@ describe("Shared modal from different components", () => {
});
it ("should find home component", async () => {
- await assertComponent(driver, "home component");
+ await assertComponent(driver, homeComponent);
});
it("should open/close shared modal from home component", async () => {
@@ -107,7 +109,7 @@ describe("Shared modal from different components", () => {
});
it ("should find home component again", async () => {
- await assertComponent(driver, "home component");
+ await assertComponent(driver, homeComponent);
});
it ("should navigate to second component", async() => {
@@ -129,7 +131,7 @@ describe("Shared modal from different components", () => {
it ("should navigate back to home component", async () => {
await goBack(driver);
- await assertComponent(driver, "home component");
+ await assertComponent(driver, homeComponent);
});
it("should open/close shared modal from home component after manipulations with second", async () => {
@@ -138,7 +140,7 @@ describe("Shared modal from different components", () => {
});
it ("should find home component again", async () => {
- await assertComponent(driver, "home component");
+ await assertComponent(driver, homeComponent);
});
});
diff --git a/e2e/modal-navigation-ng/e2e/screen.ts b/e2e/modal-navigation-ng/e2e/screen.ts
new file mode 100644
index 000000000..9033d90e1
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/screen.ts
@@ -0,0 +1,259 @@
+import { AppiumDriver } from "nativescript-dev-appium";
+import { assert } from "chai";
+
+const home = "Home Component"
+const first = "First"
+const modal = "Modal";
+const modalFirst = "Modal First";
+const dialogConfirm = "Dialog";
+const modalSecond = "Modal Second";
+const modalNested = "Modal Nested";
+
+const modalFrame = "Show Modal Page With Frame";
+const modalNoFrame = "Show Modal Without Frame";
+const modalLayout = "Show Modal Layout";
+const modalTabView = "Show Modal TabView";
+const navToSecondPage = "Navigate To Second Page";
+const showDialog = "Show Dialog";
+const resetFrameRootView = "Reset Frame Root View";
+const resetNamedFrameRootView = "Reset Named Frame Root View";
+const resetTabRootView = "Reset Tab Root View";
+const resetLayoutRootView = "Reset Layout Root View";
+
+const showNestedModalFrame = "Show Nested Modal Page With Frame";
+const showNestedModalPage = "Show Nested Modal Page";
+
+const confirmDialog = "Yes";
+const confirmDialogMessage = "Message";
+const closeModalNested = "Close Modal Nested";
+const closeModal = "Close Modal";
+const goBack = "Go Back";
+
+export class Screen {
+
+ private _driver: AppiumDriver
+
+ constructor(driver: AppiumDriver) {
+ this._driver = driver;
+ }
+
+ loadedHome = async () => {
+ const lblHome = await this._driver.findElementByText(home);
+ assert.isTrue(await lblHome.isDisplayed());
+ console.log(home + " loaded!");
+ }
+
+ resetFrameRootView = async () => {
+ console.log("Setting frame root ...");
+ const btnResetFrameRootView = await this._driver.findElementByText(resetFrameRootView);
+ await btnResetFrameRootView.tap();
+ }
+
+ resetNamedFrameRootView = async () => {
+ console.log("Setting named frame root ...");
+ const btnResetFrameRootView = await this._driver.findElementByText(resetNamedFrameRootView);
+ await btnResetFrameRootView.tap();
+ }
+
+ resetLayoutRootView = async () => {
+ console.log("Setting layout root ...");
+ const btnResetLayoutRootView = await this._driver.findElementByText(resetLayoutRootView);
+ await btnResetLayoutRootView.tap();
+ }
+
+ resetTabRootView = async () => {
+ const btnResetTabRootView = await this._driver.findElementByText(resetTabRootView);
+ await btnResetTabRootView.tap();
+ }
+
+ loadedTabRootView = async () => {
+ const tabFirst = await this._driver.findElementByText(first);
+ assert.isTrue(await tabFirst.isDisplayed());
+ console.log("Tab root view loaded!");
+ }
+
+ setFrameRootView = async () => {
+ // should load frame root, no need to verify it is loaded
+ await this.loadedHome();
+ await this.resetFrameRootView();
+ }
+
+ setNamedFrameRootView = async () => {
+ // should load named frame root, no need to verify it is loaded
+ await this.loadedHome();
+ await this.resetNamedFrameRootView();
+ }
+
+ setTabRootView = async () => {
+ // should load tab root
+ await this.loadedHome();
+ try {
+ await this.loadedTabRootView();
+ } catch (err) {
+ await this.resetTabRootView();
+ await this.loadedTabRootView();
+ }
+ }
+
+ setLayoutRootView = async () => {
+ // should load layout root, no need to verify it is loaded
+ await this.loadedHome();
+ await this.resetLayoutRootView();
+ }
+
+ showModalFrame = async () => {
+ const btnModalFrame = await this._driver.findElementByText(modalFrame);
+ await btnModalFrame.tap();
+ }
+
+ loadedModalFrame = async () => {
+ const lblModal = await this._driver.findElementByText(modal);
+ assert.isTrue(await lblModal.isDisplayed());
+ console.log(modal + " loaded!");
+ }
+
+ showModalNoFrame = async () => {
+ const btnModalPage = await this._driver.findElementByText(modalNoFrame);
+ await btnModalPage.tap();
+ }
+
+ loadedModalPage = async () => {
+ const btnShowNestedModalPage = await this._driver.findElementByText(showNestedModalPage);
+ assert.isTrue(await btnShowNestedModalPage.isDisplayed());
+ console.log("Modal Page loaded!");
+ }
+
+ showModalLayout = async () => {
+ const btnModalLayout = await this._driver.findElementByText(modalLayout);
+ await btnModalLayout.tap();
+ }
+
+ loadedModalLayout = async () => {
+ await this.loadedModalFrame();
+ }
+
+ showModalTabView = async () => {
+ const btnModalTabView = await this._driver.findElementByText(modalTabView);
+ await btnModalTabView.tap();
+ }
+
+ loadedModalTabView = async () => {
+ const itemModalFirst = await this._driver.findElementByText(modalFirst);
+ assert.isTrue(await itemModalFirst.isDisplayed());
+ console.log("Modal TabView loaded!");
+ }
+
+ navigateToSecondPage = async () => {
+ const btnNavToSecondPage = await this._driver.findElementByText(navToSecondPage);
+ await btnNavToSecondPage.tap();
+ }
+
+ showDialogConfirm = async () => {
+ const btnShowDialogConfirm = await this._driver.findElementByText(showDialog);
+ await btnShowDialogConfirm.tap();
+ }
+
+ navigateToFirstItem = async () => {
+ const itemModalFirst = await this._driver.findElementByText(modalFirst);
+ await itemModalFirst.tap();
+ }
+
+ navigateToSecondItem = async () => {
+ const itemModalSecond = await this._driver.findElementByText(modalSecond);
+ await itemModalSecond.tap();
+ }
+
+ loadedModalNoFrame = async () => {
+ const btnShowDialogConfirm = await this._driver.findElementByText(showDialog);
+ const btnCloseModal = await this._driver.findElementByText(closeModal);
+ assert.isTrue(await btnShowDialogConfirm.isDisplayed());
+ assert.isTrue(await btnCloseModal.isDisplayed());
+ console.log("Modal Without Frame shown!");
+ }
+
+ loadedConfirmDialog = async () => {
+ const lblDialogMessage = await this._driver.findElementByText(confirmDialogMessage);
+ assert.isTrue(await lblDialogMessage.isDisplayed());
+ console.log(dialogConfirm + " shown!");
+ }
+
+ loadedSecondPage = async () => {
+ const lblModalSecond = await this._driver.findElementByText(modalSecond);
+ assert.isTrue(await lblModalSecond.isDisplayed());
+ console.log(modalSecond + " loaded!");
+ }
+
+ loadedFirstItem = async () => {
+ const lblModal = await this._driver.findElementByText(modal);
+ assert.isTrue(await lblModal.isDisplayed());
+ console.log("First Item loaded!");
+ }
+
+ loadedSecondItem = async () => {
+ const btnGoBack = await this._driver.findElementByText(goBack);
+ assert.isTrue(await btnGoBack.isDisplayed());
+ console.log("Second Item loaded!");
+ }
+
+ closeDialog = async () => {
+ const btnYesDialog = await this._driver.findElementByText(confirmDialog);
+ await btnYesDialog.tap();
+ }
+
+ goBackFromSecondPage = async () => {
+ const btnGoBackFromSecondPage = await this._driver.findElementByText(goBack);
+ await btnGoBackFromSecondPage.tap();
+ }
+
+ showNestedModalFrame = async () => {
+ const btnShowNestedModalFrame = await this._driver.findElementByText(showNestedModalFrame);
+ await btnShowNestedModalFrame.tap();
+ }
+
+ loadedNestedModalFrame = async () => {
+ const lblModalNested = await this._driver.findElementByText(modalNested);
+ assert.isTrue(await lblModalNested.isDisplayed());
+ console.log(modalNested + " loaded!");
+ }
+
+ closeModalNested = async () => {
+ const btnCloseNestedModal = await this._driver.findElementByText(closeModalNested);
+ await btnCloseNestedModal.tap();
+ }
+
+ showNestedModalPage = async () => {
+ const btnShowNestedModalPage = await this._driver.findElementByText(showNestedModalPage);
+ await btnShowNestedModalPage.tap();
+ }
+
+ loadedNestedModalPage = async () => {
+ const btnCloseModalNested = await this._driver.findElementByText(closeModalNested);
+ assert.isTrue(await btnCloseModalNested.isDisplayed());
+ console.log(closeModalNested + " loaded!");
+ }
+
+ closeModal = async () => {
+ const btnCloseModal = await this._driver.findElementByText(closeModal);
+ await btnCloseModal.tap();
+ }
+
+ loadModalNoFrame = async () => {
+ try {
+ await this.loadedModalNoFrame();
+ } catch (err) {
+ // should show modal with no frame
+ await this.showModalNoFrame();
+ await this.loadedModalNoFrame();
+ }
+ }
+
+ loadModalFrame = async () => {
+ try {
+ await this.loadedModalFrame();
+ } catch (err) {
+ // should show modal page with frame
+ await this.showModalFrame();
+ await this.loadedModalFrame();
+ }
+ }
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/e2e/shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/shared.e2e-spec.ts
new file mode 100644
index 000000000..f0c6e3192
--- /dev/null
+++ b/e2e/modal-navigation-ng/e2e/shared.e2e-spec.ts
@@ -0,0 +1,86 @@
+import { AppiumDriver, createDriver } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+
+const time = 1;
+
+export const roots = ["setFrameRootView", "setLayoutRootView", "setTabRootView", "setNamedFrameRootView"];
+
+export async function modalFrameBackground(driver: AppiumDriver, screen: Screen) {
+ await driver.backgroundApp(time);
+ await screen.loadedModalFrame();
+}
+
+export async function testDialogBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await screen.showDialogConfirm();
+ await screen.loadedConfirmDialog();
+
+ await driver.backgroundApp(time);
+ await screen.loadedConfirmDialog();
+
+ await screen.closeDialog();
+ if (isInFrame) {
+ await screen.loadedModalFrame();
+ }
+}
+
+export async function testSecondPageBackground(driver: AppiumDriver, screen: Screen) {
+ await screen.navigateToSecondPage();
+ await screen.loadedSecondPage();
+
+ await driver.backgroundApp(time);
+ await screen.loadedSecondPage();
+
+ await screen.goBackFromSecondPage();
+ await screen.loadedModalFrame();
+}
+
+export async function testSecondPageClose(driver: AppiumDriver, screen: Screen) {
+ await screen.navigateToSecondPage();
+ await screen.loadedSecondPage();
+
+ await screen.closeModal();
+ await screen.loadedHome();
+}
+
+export async function testNestedModalFrameBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await screen.showNestedModalFrame();
+ await screen.loadedNestedModalFrame();
+
+ await driver.backgroundApp(time);
+ await screen.loadedNestedModalFrame();
+
+ await screen.closeModalNested();
+ isInFrame ? await screen.loadedModalFrame() : await screen.loadedModalPage();
+}
+
+export async function testNestedModalPageBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await screen.showNestedModalPage();
+ await screen.loadedNestedModalPage();
+
+ await driver.backgroundApp(time);
+ await screen.loadedNestedModalPage();
+
+ await screen.closeModalNested();
+ isInFrame ? await screen.loadedModalFrame() : await screen.loadedModalPage();
+}
+
+export async function modalPageBackground(driver: AppiumDriver, screen: Screen, isInFrame: boolean = true) {
+ await driver.backgroundApp(time);
+ isInFrame ? await screen.loadedModalFrame() : await screen.loadedModalPage();
+}
+
+export async function modalTabViewBackground(driver: AppiumDriver, screen: Screen) {
+ await driver.backgroundApp(time);
+ await screen.loadedModalTabView();
+}
+
+export async function testSecondItemBackground(driver: AppiumDriver, screen: Screen) {
+ await screen.navigateToSecondItem();
+ await screen.loadedSecondItem();
+
+ await driver.backgroundApp(time);
+ await screen.loadedSecondItem();
+
+ await screen.navigateToFirstItem();
+ await screen.loadedFirstItem();
+}
diff --git a/e2e/router-tab-view/package.json b/e2e/router-tab-view/package.json
index 28e98d89c..451dcfb34 100644
--- a/e2e/router-tab-view/package.json
+++ b/e2e/router-tab-view/package.json
@@ -36,10 +36,13 @@
"lazy": "1.0.11",
"nativescript-dev-appium": "^3.1.0",
"nativescript-dev-typescript": "~0.6.0",
- "typescript": "~2.7.2"
+ "typescript": "~2.7.2",
+ "@types/chai": "^4.0.2",
+ "@types/mocha": "^2.2.41",
+ "@types/node": "^7.0.5"
},
"scripts": {
- "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts",
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
"compile-tests": "tsc -p e2e --watch"
}
}
diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts
index 612b2788b..961ca362b 100644
--- a/nativescript-angular/directives/dialogs.ts
+++ b/nativescript-angular/directives/dialogs.ts
@@ -74,7 +74,9 @@ export class ModalDialogService {
const componentContainer = moduleRef || viewContainerRef;
const resolver = componentContainer.injector.get(ComponentFactoryResolver);
- this.location._beginModalNavigation();
+ const frame = parentView.page && parentView.page.frame;
+
+ this.location._beginModalNavigation(frame);
return new Promise((resolve, reject) => {
setTimeout(() => {
diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts
index 9c1cef6f6..e6d2ff380 100644
--- a/nativescript-angular/platform-providers.ts
+++ b/nativescript-angular/platform-providers.ts
@@ -66,7 +66,80 @@ export const defaultPageFactoryProvider = { provide: PAGE_FACTORY, useValue: def
@Injectable()
export class FrameService {
// TODO: Add any methods that are needed to handle frame/page navigation
+ private frames: { frame: Frame, name: string, rootOutlet: string }[] = [];
+
+ // Return the topmost frame.
+ // TabView with frames scenario: topmost() will return the root TabViewItem frame,
+ // which could be the wrong topmost frame (modal with nested frame e.g.):
+ // TabViewItem -> Frame -> Modal -> Frame2 -> Frame2-Navigation
getFrame(): Frame {
- return topmost();
+ let topmostFrame = topmost();
+ const { cachedFrame, cachedFrameRootOutlet } = this.findFrame(topmostFrame);
+
+ if (cachedFrame && cachedFrameRootOutlet) {
+ const topmostFrameByOutlet = this.getTopmostFrameByOutlet(cachedFrameRootOutlet);
+
+ if (topmostFrameByOutlet && topmostFrameByOutlet !== cachedFrame) {
+ topmostFrame = topmostFrameByOutlet;
+ }
+ }
+
+ return topmostFrame;
+ }
+
+ addFrame(frame: Frame, name: string, rootOutlet: string) {
+ this.frames.push({ frame: frame, name: name, rootOutlet: rootOutlet });
+ }
+
+ removeFrame(frame: Frame) {
+ this.frames = this.frames.filter(currentFrame => currentFrame.frame !== frame);
+ }
+
+ containsOutlet(name: string) {
+ let nameFound = false;
+
+ for (let i = 0; i < this.frames.length; i++) {
+ const currentFrame = this.frames[i];
+
+ if (name && currentFrame.name === name) {
+ nameFound = true;
+ break;
+ }
+ }
+
+ return nameFound;
+ }
+
+ findFrame(frame: Frame) {
+ let cachedFrame;
+ let cachedFrameRootOutlet;
+
+ for (let i = 0; i < this.frames.length; i++) {
+ const currentFrame = this.frames[i];
+
+ if (currentFrame.frame === frame) {
+ cachedFrame = currentFrame;
+ cachedFrameRootOutlet = currentFrame.rootOutlet;
+ break;
+ }
+ }
+
+ return { cachedFrame, cachedFrameRootOutlet };
+ }
+
+ // Return the latest navigated frame from the given outlet branch.
+ private getTopmostFrameByOutlet(rootOutlet: string): Frame {
+ let frame: Frame;
+
+ for (let i = this.frames.length - 1; i > 0; i--) {
+ const currentFrame = this.frames[i];
+
+ if (currentFrame.rootOutlet === rootOutlet) {
+ frame = currentFrame.frame;
+ break;
+ }
+ }
+
+ return frame;
}
}
diff --git a/nativescript-angular/router/ns-location-strategy.ts b/nativescript-angular/router/ns-location-strategy.ts
index 9ab581cdd..7cf31e270 100644
--- a/nativescript-angular/router/ns-location-strategy.ts
+++ b/nativescript-angular/router/ns-location-strategy.ts
@@ -2,7 +2,7 @@ import { Injectable } from "@angular/core";
import { LocationStrategy } from "@angular/common";
import { DefaultUrlSerializer, UrlSegmentGroup, UrlTree } from "@angular/router";
import { routerLog } from "../trace";
-import { NavigationTransition } from "tns-core-modules/ui/frame";
+import { NavigationTransition, Frame } from "tns-core-modules/ui/frame";
import { isPresent } from "../lang-facade";
import { FrameService } from "../platform-providers";
@@ -77,7 +77,7 @@ export class NSLocationStrategy extends LocationStrategy {
}
pushState(state: any, title: string, url: string, queryParams: string): void {
- routerLog("NSLocationStrategy.pushState state: " +
+ routerLog("NSLocationStrategy.pushState state: " +
`${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
this.pushStateInternal(state, title, url, queryParams);
}
@@ -240,7 +240,9 @@ export class NSLocationStrategy extends LocationStrategy {
private callPopState(state: LocationState, pop: boolean = true) {
const urlSerializer = new DefaultUrlSerializer();
- if (state) {
+ const rootOutlet = this.currentUrlTree.root.children[this.currentOutlet];
+
+ if (state && rootOutlet) {
this.currentUrlTree.root.children[this.currentOutlet] = state.segmentGroup;
} else {
// when closing modal view there are scenarios (e.g. root viewContainerRef) when we need
@@ -283,13 +285,20 @@ export class NSLocationStrategy extends LocationStrategy {
}
// Methods for syncing with page navigation in PageRouterOutlet
- public _beginBackPageNavigation(name: string) {
+ public _beginBackPageNavigation(name: string, frame: Frame) {
routerLog("NSLocationStrategy.startGoBack()");
if (this._isPageNavigationBack) {
throw new Error("Calling startGoBack while going back.");
}
this._isPageNavigationBack = true;
- this.currentOutlet = name;
+
+ let { cachedFrame } = this.frameService.findFrame(frame);
+
+ if (cachedFrame) {
+ this.currentOutlet = cachedFrame.rootOutlet;
+ } else if (!this.frameService.containsOutlet(name)) {
+ this.currentOutlet = name;
+ }
}
public _finishBackPageNavigation() {
@@ -304,9 +313,12 @@ export class NSLocationStrategy extends LocationStrategy {
return this._isPageNavigationBack;
}
- public _beginModalNavigation(): void {
+ public _beginModalNavigation(frame: Frame): void {
routerLog("NSLocationStrategy._beginModalNavigation()");
- const lastState = this.peekState(this.currentOutlet);
+
+ let { cachedFrameRootOutlet } = this.frameService.findFrame(frame);
+
+ const lastState = this.peekState(cachedFrameRootOutlet || this.currentOutlet);
if (lastState) {
lastState.isModalNavigation = true;
@@ -333,19 +345,31 @@ export class NSLocationStrategy extends LocationStrategy {
this._isModalClosing = false;
}
- public _beginPageNavigation(name: string): NavigationOptions {
+ public _beginPageNavigation(name: string, frame: Frame): NavigationOptions {
routerLog("NSLocationStrategy._beginPageNavigation()");
- const lastState = this.peekState(name);
+
+ let { cachedFrame } = this.frameService.findFrame(frame);
+
+ if (cachedFrame) {
+ this.currentOutlet = cachedFrame.rootOutlet;
+ } else {
+ // Changing the current outlet only if navigating in non-cached root outlet.
+ if (!this.frameService.containsOutlet(name) && this.statesByOutlet[name] /* ensure root outlet exists */) {
+ this.currentOutlet = name;
+ }
+
+ this.frameService.addFrame(frame, name, this.currentOutlet);
+ }
+
+ const lastState = this.peekState(this.currentOutlet);
if (lastState) {
lastState.isPageNavigation = true;
}
- this.currentOutlet = name;
-
const navOptions = this._currentNavigationOptions || defaultNavOptions;
if (navOptions.clearHistory) {
routerLog("NSLocationStrategy._beginPageNavigation clearing states history");
- this.statesByOutlet[name] = [lastState];
+ this.statesByOutlet[this.currentOutlet] = [lastState];
}
this._currentNavigationOptions = undefined;
diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts
index eb5950558..565414386 100644
--- a/nativescript-angular/router/page-router-outlet.ts
+++ b/nativescript-angular/router/page-router-outlet.ts
@@ -25,6 +25,7 @@ import { DetachedLoader } from "../common/detached-loader";
import { ViewUtil } from "../view-util";
import { NSLocationStrategy } from "./ns-location-strategy";
import { NSRouteReuseStrategy } from "./ns-route-reuse-strategy";
+import { FrameService } from "../platform-providers";
export class PageRoute {
activatedRoute: BehaviorSubject;
@@ -149,7 +150,8 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
@Inject(DEVICE) device: Device,
@Inject(PAGE_FACTORY) private pageFactory: PageFactory,
private routeReuseStrategy: NSRouteReuseStrategy,
- elRef: ElementRef
+ elRef: ElementRef,
+ private frameService: FrameService
) {
this.frame = elRef.nativeElement;
log("PageRouterOutlet.constructor frame:" + this.frame);
@@ -166,6 +168,7 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
// destroyed on modal view closing
this.routeReuseStrategy.clearModalCache(this.name);
this.parentContexts.onChildOutletDestroyed(this.name);
+ this.frameService.removeFrame(this.frame);
}
deactivate(): void {
@@ -286,12 +289,12 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
page.on(Page.navigatedFromEvent, (global).Zone.current.wrap((args: NavigatedData) => {
if (args.isBackNavigation) {
- this.locationStrategy._beginBackPageNavigation(this.name);
+ this.locationStrategy._beginBackPageNavigation(this.name, this.frame);
this.locationStrategy.back();
}
}));
- const navOptions = this.locationStrategy._beginPageNavigation(this.name);
+ const navOptions = this.locationStrategy._beginPageNavigation(this.name, this.frame);
// Clear refCache if navigation with clearHistory
if (navOptions.clearHistory) {
diff --git a/tests/app/tests/ns-location-strategy.ts b/tests/app/tests/ns-location-strategy.ts
index 5519075d1..01824f0ec 100644
--- a/tests/app/tests/ns-location-strategy.ts
+++ b/tests/app/tests/ns-location-strategy.ts
@@ -134,11 +134,11 @@ function createState(url: string,
function simulatePageNavigation(strategy: NSLocationStrategy, url: string, outletName: string) {
strategy.pushState(null, null, url, null);
- strategy._beginPageNavigation(outletName);
+ strategy._beginPageNavigation(outletName, null);
}
function simulatePageBack(strategy: NSLocationStrategy, outletName: string) {
- strategy._beginBackPageNavigation(outletName);
+ strategy._beginBackPageNavigation(outletName, null);
strategy.back();
strategy._finishBackPageNavigation();
}