diff --git a/e2e/modal-navigation-ng/app/app.component.ts b/e2e/modal-navigation-ng/app/app.component.ts index a86fef745..4c61400d7 100644 --- a/e2e/modal-navigation-ng/app/app.component.ts +++ b/e2e/modal-navigation-ng/app/app.component.ts @@ -1,21 +1,27 @@ -import { Component } from "@angular/core"; +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: "ns-app", templateUrl: "app.component.html", }) export class AppComponent { - constructor(router: Router, location: NSLocationStrategy) { + constructor( + router: Router, + location: NSLocationStrategy, + private _vcRef: ViewContainerRef, + private _viewContainerRefService: ViewContainerRefService) { router.events.subscribe(e => { - // console.log("[ROUTER]: " + e.toString()); - 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/app/app.module.ts b/e2e/modal-navigation-ng/app/app.module.ts index af8060319..119ec1f16 100644 --- a/e2e/modal-navigation-ng/app/app.module.ts +++ b/e2e/modal-navigation-ng/app/app.module.ts @@ -9,9 +9,13 @@ import { ModalSecondComponent } from "./modal-second/modal-second.component"; import { ModalComponent } from "./modal/modal.component"; import { NestedModalComponent } from "./modal-nested/modal-nested.component"; import { ModalRouterComponent } from "./modal/modal-router/modal-router.component"; +import { ModalViewComponent } from "./modal-shared/modal-view.component"; +import { ModalViewContentComponent } from "./modal-shared/modal-view-content.component"; +import { ModalSharedSecondComponent } from "./modal-shared/modal-shared-second.component"; +import { ViewContainerRefService } from "./shared/ViewContainerRefService"; -import { enable as traceEnable, addCategories } from "tns-core-modules/trace"; -import { routerTraceCategory } from "nativescript-angular/trace"; +// import { enable as traceEnable, addCategories } from "tns-core-modules/trace"; +// import { routerTraceCategory } from "nativescript-angular/trace"; // addCategories(routerTraceCategory); // traceEnable(); @@ -24,7 +28,12 @@ import { routerTraceCategory } from "nativescript-angular/trace"; NativeScriptModule, AppRoutingModule ], - entryComponents: [ModalRouterComponent, NestedModalComponent, ModalComponent], + entryComponents: [ + ModalRouterComponent, + NestedModalComponent, + ModalComponent, + ModalViewComponent + ], declarations: [ AppComponent, HomeComponent, @@ -32,7 +41,13 @@ import { routerTraceCategory } from "nativescript-angular/trace"; ModalComponent, NestedModalComponent, ModalRouterComponent, - ModalSecondComponent + ModalSecondComponent, + ModalViewComponent, + ModalViewContentComponent, + ModalSharedSecondComponent + ], + providers: [ + ViewContainerRefService ], schemas: [ NO_ERRORS_SCHEMA diff --git a/e2e/modal-navigation-ng/app/app.routing.ts b/e2e/modal-navigation-ng/app/app.routing.ts index 9c5a1a973..6e7346d84 100644 --- a/e2e/modal-navigation-ng/app/app.routing.ts +++ b/e2e/modal-navigation-ng/app/app.routing.ts @@ -1,13 +1,14 @@ import { NgModule } from "@angular/core"; import { NativeScriptRouterModule } from "nativescript-angular/router"; -import { Routes, ChildrenOutletContexts } from "@angular/router"; +import { Routes } from "@angular/router"; import { HomeComponent } from "./home/home.component"; import { SecondComponent } from "./second/second.component"; import { ModalSecondComponent } from "./modal-second/modal-second.component"; import { ModalComponent } from "./modal/modal.component"; import { NestedModalComponent } from "./modal-nested/modal-nested.component"; -import { ModalRouterComponent } from "./modal/modal-router/modal-router.component"; +import { ModalViewContentComponent } from "./modal-shared/modal-view-content.component"; +import { ModalSharedSecondComponent } from "./modal-shared/modal-shared-second.component"; const routes: Routes = [ { path: "", redirectTo: "/home", pathMatch: "full" }, @@ -28,6 +29,12 @@ const routes: Routes = [ }, { path: "modal-second", component: ModalSecondComponent } ] + }, + { + path: "modal-shared", component: ModalViewContentComponent, outlet: "modalOutlet" + }, + { + path: "modal-shared-second-host", component: ModalSharedSecondComponent } ]; diff --git a/e2e/modal-navigation-ng/app/home/home.component.html b/e2e/modal-navigation-ng/app/home/home.component.html index ac5f99450..5379c23d9 100644 --- a/e2e/modal-navigation-ng/app/home/home.component.html +++ b/e2e/modal-navigation-ng/app/home/home.component.html @@ -9,4 +9,7 @@ + + + \ 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 d494180a6..d3410c3fc 100644 --- a/e2e/modal-navigation-ng/app/home/home.component.ts +++ b/e2e/modal-navigation-ng/app/home/home.component.ts @@ -1,12 +1,12 @@ import { Component, ViewContainerRef } from "@angular/core"; import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs"; +import { RouterExtensions } from "nativescript-angular/router"; import { EventData } from "tns-core-modules/data/observable"; -import { Frame } from "tns-core-modules/ui/frame"; -import { View } from "tns-core-modules/ui/core/view"; + +import { ViewContainerRefService } from "../shared/ViewContainerRefService"; import { ModalRouterComponent } from "../modal/modal-router/modal-router.component"; -import { PageRouterOutlet } from "nativescript-angular/router/page-router-outlet"; -import { RouterExtensions } from "nativescript-angular/router"; import { ModalComponent } from "../modal/modal.component"; +import { ModalViewComponent } from "../modal-shared/modal-view.component"; @Component({ moduleId: module.id, @@ -14,7 +14,11 @@ import { ModalComponent } from "../modal/modal.component"; templateUrl: "./home.component.html" }) export class HomeComponent { - constructor(private modal: ModalDialogService, private vcRef: ViewContainerRef, private routerExtension: RouterExtensions) { } + constructor( + private modal: ModalDialogService, + private vcRef: ViewContainerRef, + private viewContainerRefService: ViewContainerRefService, + private routerExtension: RouterExtensions) { } onModalNoFrame(args: EventData) { const options: ModalDialogOptions = { @@ -52,4 +56,17 @@ export class HomeComponent { onFrameRootViewReset(args: EventData) { } + + onRootModalTap(): void { + const options: ModalDialogOptions = { + viewContainerRef: this.viewContainerRefService.root, + context: {}, + fullscreen: true + }; + + this.modal.showModal(ModalViewComponent, options) + .then((result: string) => { + console.log(result); + }); + } } diff --git a/e2e/modal-navigation-ng/app/modal-shared/modal-shared-second.component.ts b/e2e/modal-navigation-ng/app/modal-shared/modal-shared-second.component.ts new file mode 100644 index 000000000..6f9c5ca21 --- /dev/null +++ b/e2e/modal-navigation-ng/app/modal-shared/modal-shared-second.component.ts @@ -0,0 +1,44 @@ +import { Component } from "@angular/core"; +import { ModalDialogOptions, ModalDialogService } from "nativescript-angular/modal-dialog"; + +import { ViewContainerRefService } from "../shared/ViewContainerRefService"; +import { ModalViewComponent } from "../modal-shared/modal-view.component"; +import { RouterExtensions } from "nativescript-angular/router"; + +@Component({ + selector: "ns-second", + moduleId: module.id, + template: ` + + + + + + ` +}) +export class ModalSharedSecondComponent { + constructor( + private _modalService: ModalDialogService, + private _viewContainerRefService: ViewContainerRefService, + private _routerExtensions: RouterExtensions + ) { } + + onRootModalTap(): void { + const options: ModalDialogOptions = { + viewContainerRef: this._viewContainerRefService.root, + context: {}, + fullscreen: true + }; + + this._modalService.showModal(ModalViewComponent, options) + .then((result: string) => { + console.log(result); + }); + } + + onBackTap() { + if (this._routerExtensions.canGoBack()) { + this._routerExtensions.back(); + } + } +} \ No newline at end of file diff --git a/e2e/modal-navigation-ng/app/modal-shared/modal-view-content.component.ts b/e2e/modal-navigation-ng/app/modal-shared/modal-view-content.component.ts new file mode 100644 index 000000000..30247073c --- /dev/null +++ b/e2e/modal-navigation-ng/app/modal-shared/modal-view-content.component.ts @@ -0,0 +1,27 @@ +import { Component } from "@angular/core"; +import { ModalDialogParams } from "nativescript-angular/modal-dialog"; + +@Component({ + selector: "ModalViewContent", + moduleId: module.id, + template: ` + + + + + + + `, + styles: [` + .action-bar, .page { + background-color: chocolate; + } + `] +}) +export class ModalViewContentComponent { + constructor(private _params: ModalDialogParams) { } + + onTap(): void { + this._params.closeCallback("return value"); + } +} \ No newline at end of file diff --git a/e2e/modal-navigation-ng/app/modal-shared/modal-view.component.ts b/e2e/modal-navigation-ng/app/modal-shared/modal-view.component.ts new file mode 100644 index 000000000..7089b8e14 --- /dev/null +++ b/e2e/modal-navigation-ng/app/modal-shared/modal-view.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from "@angular/core"; +import { RouterExtensions } from "nativescript-angular/router"; + +@Component({ + selector: "ModalView", + moduleId: module.id, + template:` + + ` +}) +export class ModalViewComponent implements OnInit { + constructor(private _routerExtensions: RouterExtensions) {} + + ngOnInit(): void { + this._routerExtensions.navigate([{ outlets: { modalOutlet: ["modal-shared"]}}]); + } +} diff --git a/e2e/modal-navigation-ng/app/shared/ViewContainerRefService.ts b/e2e/modal-navigation-ng/app/shared/ViewContainerRefService.ts new file mode 100644 index 000000000..e74a57a15 --- /dev/null +++ b/e2e/modal-navigation-ng/app/shared/ViewContainerRefService.ts @@ -0,0 +1,14 @@ +import { Injectable, ViewContainerRef } from "@angular/core"; + +@Injectable() +export class ViewContainerRefService { + private _rootViewContainerRef: ViewContainerRef; + + get root():ViewContainerRef { + return this._rootViewContainerRef; + } + + set root(viewContainerRef: ViewContainerRef) { + this._rootViewContainerRef = viewContainerRef; + } +} diff --git a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts new file mode 100644 index 000000000..ecdc240b4 --- /dev/null +++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts @@ -0,0 +1,168 @@ +import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; +import { assert } from "chai"; + +describe("Shared modal from home and back", () => { + let driver: AppiumDriver; + + before(async () => { + driver = await createDriver(); + await driver.resetApp(); + }); + + after(async () => { + await driver.quit(); + console.log("Quit driver!"); + }); + + afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logPageSource(this.currentTest.title); + await driver.logScreenshot(this.currentTest.title); + } + }); + + it ("should find home component", async () => { + await assertComponent(driver, "home component"); + }); + + it("should open/close shared modal from home component", async () => { + await openModal(driver); + await closeModal(driver); + }); + + it ("should find home component again", async () => { + await assertComponent(driver, "home component"); + }); +}); + +describe("Shared modal from second and back", () => { + let driver: AppiumDriver; + + before(async () => { + driver = await createDriver(); + await driver.resetApp(); + }); + + after(async () => { + await driver.quit(); + console.log("Quit driver!"); + }); + + afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logPageSource(this.currentTest.title); + await driver.logScreenshot(this.currentTest.title); + } + }); + + it ("should find home component", async () => { + await assertComponent(driver, "home component"); + }); + + it ("should navigate to second component", async() => { + await navigateToSecondComponent(driver); + }); + + it ("should find second component", async () => { + await assertComponent(driver, "second component"); + }); + + it("should open/close shared modal from second component", async () => { + await openModal(driver); + await closeModal(driver); + }); + + it ("should find second component again", async () => { + await assertComponent(driver, "second component"); + }); +}); + +describe("Shared modal from different components", () => { + let driver: AppiumDriver; + + before(async () => { + driver = await createDriver(); + await driver.resetApp(); + }); + + after(async () => { + await driver.quit(); + console.log("Quit driver!"); + }); + + afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logPageSource(this.currentTest.title); + await driver.logScreenshot(this.currentTest.title); + } + }); + + it ("should find home component", async () => { + await assertComponent(driver, "home component"); + }); + + it("should open/close shared modal from home component", async () => { + await openModal(driver); + await closeModal(driver); + }); + + it ("should find home component again", async () => { + await assertComponent(driver, "home component"); + }); + + it ("should navigate to second component", async() => { + await navigateToSecondComponent(driver); + }); + + it ("should find second component", async () => { + await assertComponent(driver, "second component"); + }); + + it("should open/close shared modal from second component", async () => { + await openModal(driver); + await closeModal(driver); + }); + + it ("should find second component again", async () => { + await assertComponent(driver, "second component"); + }); + + it ("should navigate back to home component", async () => { + await goBack(driver); + await assertComponent(driver, "home component"); + }); + + it("should open/close shared modal from home component after manipulations with second", async () => { + await openModal(driver); + await closeModal(driver); + }); + + it ("should find home component again", async () => { + await assertComponent(driver, "home component"); + }); +}); + +async function assertComponent(driver: AppiumDriver, message: string) { + const lbl = await driver.findElementByText(message, SearchOptions.exact); + assert.isTrue(await lbl.isDisplayed()); +} + +async function navigateToSecondComponent(driver: AppiumDriver) { + const navigateBtnTap = await driver.findElementByText("go to second (to open shared modal)", SearchOptions.exact); + await navigateBtnTap.click(); +} + +async function openModal(driver: AppiumDriver) { + const btnTap = await driver.findElementByText("show shared modal", SearchOptions.exact); + await btnTap.click(); +} + +async function closeModal(driver: AppiumDriver) { + const closeBtnTap = await driver.findElementByText("close modal", SearchOptions.exact); + await closeBtnTap.click(); +} + +async function goBack(driver: AppiumDriver) { + const backBtnTap = await driver.findElementByText("go back", SearchOptions.exact); + await backBtnTap.click(); +} diff --git a/e2e/modal-navigation-ng/e2e/sample.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/sample.e2e-spec.ts deleted file mode 100644 index 45bda4c9a..000000000 --- a/e2e/modal-navigation-ng/e2e/sample.e2e-spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; -import { assert } from "chai"; - -describe("sample scenario", () => { - const defaultWaitTime = 5000; - let driver: AppiumDriver; - - before(async () => { - driver = await createDriver(); - }); - - after(async () => { - await driver.quit(); - console.log("Quit driver!"); - }); - - afterEach(async function () { - if (this.currentTest.state === "failed") { - await driver.logScreenshot(this.currentTest.title); - } - }); - - it("should find an element by text", async () => { - const btnTap = await driver.findElementByText("TAP", SearchOptions.exact); - await btnTap.click(); - - const message = " taps left"; - const lblMessage = await driver.findElementByText(message, SearchOptions.contains); - assert.equal(await lblMessage.text(), "41" + message); - - // Image verification - // const screen = await driver.compareScreen("hello-world-41"); - // assert.isTrue(screen); - }); - - it("should find an element by type", async () => { - const btnTap = await driver.findElementByClassName(driver.locators.button); - await btnTap.click(); - - const message = " taps left"; - const lblMessage = await driver.findElementByText(message, SearchOptions.contains); - assert.equal(await lblMessage.text(), "40" + message); - }); -}); \ No newline at end of file diff --git a/e2e/modal-navigation-ng/package.json b/e2e/modal-navigation-ng/package.json index 021f56a06..4eb42d82c 100644 --- a/e2e/modal-navigation-ng/package.json +++ b/e2e/modal-navigation-ng/package.json @@ -6,57 +6,59 @@ "nativescript": { "id": "org.nativescript.modalnavigationng", "tns-android": { - "version": "4.1.0-2018.4.16.8" + "version": "next" }, "tns-ios": { "version": "next" } }, "dependencies": { - "@angular/animations": "~6.0.0-rc.3", - "@angular/common": "~6.0.0-rc.3", - "@angular/compiler": "~6.0.0-rc.3", - "@angular/core": "~6.0.0-rc.3", - "@angular/forms": "~6.0.0-rc.3", - "@angular/http": "~6.0.0-rc.3", - "@angular/platform-browser": "~6.0.0-rc.3", - "@angular/platform-browser-dynamic": "~6.0.0-rc.3", - "@angular/router": "~6.0.0-rc.3", + "@angular/animations": "~6.0.0", + "@angular/common": "~6.0.0", + "@angular/compiler": "~6.0.0", + "@angular/core": "~6.0.0", + "@angular/forms": "~6.0.0", + "@angular/http": "~6.0.0", + "@angular/platform-browser": "~6.0.0", + "@angular/platform-browser-dynamic": "~6.0.0", + "@angular/router": "~6.0.0", "nativescript-angular": "file:../../nativescript-angular", "nativescript-theme-core": "~1.0.4", "reflect-metadata": "~0.1.8", - "rxjs": "~6.0.0-rc.1", + "rxjs": "~6.1.0", "tns-core-modules": "next", "zone.js": "~0.8.2" }, "devDependencies": { - "@angular/compiler-cli": "~6.0.0-rc.3", - "@ngtools/webpack": "~1.9.4", + "@angular-devkit/core": "~0.6.3", + "@angular/compiler-cli": "~6.0.0", + "@ngtools/webpack": "~6.0.3", "@types/chai": "^4.0.2", "@types/mocha": "^2.2.41", "@types/node": "^7.0.5", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", - "copy-webpack-plugin": "~4.3.0", - "css-loader": "~0.28.7", + "clean-webpack-plugin": "~0.1.19", + "copy-webpack-plugin": "~4.5.1", + "css-loader": "~0.28.11", "extract-text-webpack-plugin": "~3.0.2", "lazy": "1.0.11", "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", - "nativescript-worker-loader": "~0.8.1", + "nativescript-worker-loader": "~0.9.0", "raw-loader": "~0.5.1", - "resolve-url-loader": "~2.2.1", + "resolve-url-loader": "~2.3.0", "typescript": "~2.7.2", - "uglifyjs-webpack-plugin": "~1.1.6", - "webpack": "~3.10.0", - "webpack-bundle-analyzer": "^2.9.1", - "webpack-sources": "~1.1.0", - "clean-webpack-plugin": "~0.1.19" + "uglifyjs-webpack-plugin": "~1.2.5", + "webpack": "~4.6.0", + "webpack-bundle-analyzer": "~2.13.0", + "webpack-cli": "~2.1.3", + "webpack-sources": "~1.1.0" }, "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", "e2e-watch": "tsc -p e2e --watch" } } diff --git a/e2e/modal-navigation-ng/tsconfig.esm.json b/e2e/modal-navigation-ng/tsconfig.esm.json new file mode 100644 index 000000000..95f2ecee0 --- /dev/null +++ b/e2e/modal-navigation-ng/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node" + } +} diff --git a/e2e/router/tsconfig.esm.json b/e2e/router/tsconfig.esm.json new file mode 100644 index 000000000..95f2ecee0 --- /dev/null +++ b/e2e/router/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node" + } +} diff --git a/nativescript-angular/router/ns-location-strategy.ts b/nativescript-angular/router/ns-location-strategy.ts index 9a8b1d22f..0ed989cd1 100644 --- a/nativescript-angular/router/ns-location-strategy.ts +++ b/nativescript-angular/router/ns-location-strategy.ts @@ -173,6 +173,7 @@ export class NSLocationStrategy extends LocationStrategy { if (!state) { modalStatesCleared = true; + this.callPopState(null, true); continue; } @@ -239,7 +240,16 @@ export class NSLocationStrategy extends LocationStrategy { private callPopState(state: LocationState, pop: boolean = true) { const urlSerializer = new DefaultUrlSerializer(); - this.currentUrlTree.root.children[this.currentOutlet] = state.segmentGroup; + if (state) { + this.currentUrlTree.root.children[this.currentOutlet] = state.segmentGroup; + } else { + // when closing modal view there are scenarios (e.g. root viewContainerRef) when we need + // to clean up the named page router outlet to make sure we will open the modal properly again if needed. + delete this.statesByOutlet[this.currentOutlet]; + delete this.currentUrlTree.root.children[this.currentOutlet]; + this.currentOutlet = Object.keys(this.statesByOutlet)[0]; + } + const url = urlSerializer.serialize(this.currentUrlTree); const change = { url: url, pop: pop }; for (let fn of this.popStateCallbacks) {