diff --git a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html index fb667fe3a..9c6b4052e 100644 --- a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html +++ b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html @@ -4,6 +4,6 @@ - + \ No newline at end of file diff --git a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts index 01e203e56..7b69233e6 100644 --- a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts +++ b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts @@ -1,5 +1,6 @@ import { Component } from "@angular/core"; -import { View, EventData } from "tns-core-modules/ui/core/view" +import { View } from "tns-core-modules/ui/core/view" +import { ActivatedRoute } from "@angular/router"; import { RouterExtensions } from "nativescript-angular/router"; @Component({ @@ -8,14 +9,14 @@ import { RouterExtensions } from "nativescript-angular/router"; templateUrl: "./modal-second.component.html" }) export class ModalSecondComponent { - constructor(private routerExtension: RouterExtensions) { } + constructor(private routerExtension: RouterExtensions, private activeRoute: ActivatedRoute) { } - onLoaded(args: EventData) { + onLoaded() { console.log("modal-second loaded"); } goBack() { - this.routerExtension.back(); + this.routerExtension.back({ relativeTo: this.activeRoute }); } close(layoutRoot: View) { diff --git a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts index 2c9b95206..79010a093 100644 --- a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts @@ -33,8 +33,7 @@ describe("modal-frame:", () => { afterEach(async function () { if (this.currentTest.state === "failed") { - await driver.logPageSource(this.currentTest.title); - await driver.logScreenshot(this.currentTest.title); + await driver.logTestArtifacts(this.currentTest.title); await driver.resetApp(); await screen[root](); } diff --git a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts index 945cc4431..6d6846d58 100644 --- a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts @@ -33,8 +33,7 @@ describe("modal-layout:", () => { afterEach(async function () { if (this.currentTest.state === "failed") { - await driver.logPageSource(this.currentTest.title); - await driver.logScreenshot(this.currentTest.title); + await driver.logTestArtifacts(this.currentTest.title); await driver.resetApp(); await screen[root](); } 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 75f4867ad..b7fbbe9be 100644 --- a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts @@ -18,8 +18,7 @@ describe("Shared modal from home and back", () => { afterEach(async function () { if (this.currentTest.state === "failed") { - await driver.logPageSource(this.currentTest.title); - await driver.logScreenshot(this.currentTest.title); + await driver.logTestArtifacts(this.currentTest.title); } }); diff --git a/e2e/modal-navigation-ng/e2e/screen.ts b/e2e/modal-navigation-ng/e2e/screen.ts index 9033d90e1..ba44fdeea 100644 --- a/e2e/modal-navigation-ng/e2e/screen.ts +++ b/e2e/modal-navigation-ng/e2e/screen.ts @@ -27,7 +27,7 @@ const confirmDialog = "Yes"; const confirmDialogMessage = "Message"; const closeModalNested = "Close Modal Nested"; const closeModal = "Close Modal"; -const goBack = "Go Back"; +const goBack = "Go Back(activatedRoute)"; export class Screen { diff --git a/e2e/modal-navigation-ng/package.json b/e2e/modal-navigation-ng/package.json index 2d1b54d97..0fd0f8b0f 100644 --- a/e2e/modal-navigation-ng/package.json +++ b/e2e/modal-navigation-ng/package.json @@ -57,7 +57,8 @@ "webpack": "~4.6.0", "webpack-bundle-analyzer": "~2.13.0", "webpack-cli": "~2.1.3", - "webpack-sources": "~1.1.0" + "webpack-sources": "~1.1.0", + "@angular-devkit/core": "~0.7.0-beta.1" }, "scripts": { "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json", diff --git a/e2e/nested-router-tab-view/.gitignore b/e2e/nested-router-tab-view/.gitignore new file mode 100644 index 000000000..dbb747daa --- /dev/null +++ b/e2e/nested-router-tab-view/.gitignore @@ -0,0 +1,12 @@ +.vscode + +platforms +node_modules +hooks + +/**/*.js +/**/*.map +e2e/reports +test-results.xml + +instrumentscli*.trace \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/AndroidManifest.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/AndroidManifest.xml new file mode 100644 index 000000000..9db832151 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/Android/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/app.gradle b/e2e/nested-router-tab-view/app/App_Resources/Android/app.gradle new file mode 100644 index 000000000..680e43542 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/Android/app.gradle @@ -0,0 +1,16 @@ +// Add your native dependencies here: + +// Uncomment to add recyclerview-v7 dependency +//dependencies { +// compile 'com.android.support:recyclerview-v7:+' +//} + +android { + defaultConfig { + generatedDensities = [] + applicationId = "org.nativescript.nestedroutertabview" + } + aaptOptions { + additionalParameters "--no-version-vectors" + } +} diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/background.png new file mode 100644 index 000000000..eb381c258 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/background.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/icon.png new file mode 100755 index 000000000..9cde84cd5 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/icon.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/logo.png new file mode 100644 index 000000000..5218f4c90 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/logo.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/background.png new file mode 100644 index 000000000..748b2adf5 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/background.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/icon.png new file mode 100755 index 000000000..4d6a674b3 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/icon.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/logo.png new file mode 100644 index 000000000..b9e102a76 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/logo.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/background.png new file mode 100644 index 000000000..efeaf2907 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/background.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/icon.png new file mode 100755 index 000000000..92ccc85a6 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/icon.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/logo.png new file mode 100644 index 000000000..626338766 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/logo.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-nodpi/splash_screen.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-nodpi/splash_screen.xml new file mode 100644 index 000000000..ada77f92c --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-nodpi/splash_screen.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/background.png new file mode 100644 index 000000000..612bbd072 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/background.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/icon.png new file mode 100644 index 000000000..8bcde6277 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/icon.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/logo.png new file mode 100644 index 000000000..ad8ee2f4b Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/logo.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/background.png new file mode 100644 index 000000000..0fa88e235 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/background.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/icon.png new file mode 100644 index 000000000..9d81c85dc Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/icon.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/logo.png new file mode 100644 index 000000000..668327832 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/logo.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/background.png new file mode 100644 index 000000000..c650f6438 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/background.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/icon.png new file mode 100644 index 000000000..9a34d0d43 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/icon.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/logo.png new file mode 100644 index 000000000..fa6331c8d Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/logo.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/colors.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/colors.xml new file mode 100644 index 000000000..a64641a9d --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/colors.xml @@ -0,0 +1,4 @@ + + + #3d5afe + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/styles.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/styles.xml new file mode 100644 index 000000000..dac8727c8 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/styles.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values/colors.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values/colors.xml new file mode 100644 index 000000000..74ad8829c --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values/colors.xml @@ -0,0 +1,7 @@ + + + #F5F5F5 + #757575 + #33B5E5 + #272734 + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values/styles.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values/styles.xml new file mode 100644 index 000000000..c793e6d4c --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values/styles.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..4034b76e6 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon-60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon-60@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon-29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon-76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "icon-83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "icon-1024.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 000000000..a1d7eb479 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png new file mode 100644 index 000000000..bb9b9e86d Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png new file mode 100644 index 000000000..ec609dcf3 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png new file mode 100644 index 000000000..a97180800 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png new file mode 100644 index 000000000..214800ee6 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png new file mode 100644 index 000000000..8554b88a8 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png new file mode 100644 index 000000000..a22626bae Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png new file mode 100644 index 000000000..a22626bae Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png new file mode 100644 index 000000000..492c9c8e8 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png new file mode 100644 index 000000000..9208113cf Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png new file mode 100644 index 000000000..24415e5a3 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png new file mode 100644 index 000000000..b3ef1bf0c Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 000000000..11bfcf55c --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,176 @@ +{ + "images" : [ + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "2436h", + "filename" : "Default-1125h.png", + "minimum-system-version" : "11.0", + "orientation" : "portrait", + "scale" : "3x" + }, + { + "orientation" : "landscape", + "idiom" : "iphone", + "extent" : "full-screen", + "filename" : "Default-Landscape-X.png", + "minimum-system-version" : "11.0", + "subtype" : "2436h", + "scale" : "3x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "736h", + "filename" : "Default-736h@3x.png", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "3x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "736h", + "filename" : "Default-Landscape@3x.png", + "minimum-system-version" : "8.0", + "orientation" : "landscape", + "scale" : "3x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "667h", + "filename" : "Default-667h@2x.png", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default@2x.png", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "retina4", + "filename" : "Default-568h@2x.png", + "minimum-system-version" : "7.0", + "orientation" : "portrait", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "filename" : "Default-Portrait.png", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "filename" : "Default-Landscape.png", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "filename" : "Default-Portrait@2x.png", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "filename" : "Default-Landscape@2x.png", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default.png", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default@2x.png", + "extent" : "full-screen", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default-568h@2x.png", + "extent" : "full-screen", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "filename" : "Default-Portrait.png", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "filename" : "Default-Landscape.png", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "filename" : "Default-Portrait@2x.png", + "extent" : "full-screen", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "to-status-bar", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "filename" : "Default-Landscape@2x.png", + "extent" : "full-screen", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png new file mode 100644 index 000000000..2913f85d9 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png new file mode 100644 index 000000000..d7f17fcd2 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png new file mode 100644 index 000000000..b88415405 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png new file mode 100644 index 000000000..faab4b631 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png new file mode 100644 index 000000000..cd94a3ac2 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png new file mode 100644 index 000000000..3365ba3cd Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png new file mode 100644 index 000000000..a44945c1a Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png new file mode 100644 index 000000000..e6dca6269 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png new file mode 100644 index 000000000..1a5007962 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png new file mode 100644 index 000000000..73d8b920f Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png new file mode 100644 index 000000000..9f1f6ce3e Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png new file mode 100644 index 000000000..514fc5cde Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json new file mode 100644 index 000000000..4f4e9c506 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchScreen-AspectFill.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchScreen-AspectFill@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png new file mode 100644 index 000000000..c293f9c7a Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png new file mode 100644 index 000000000..233693a6e Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json new file mode 100644 index 000000000..23c0ffd7a --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchScreen-Center.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchScreen-Center@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png new file mode 100644 index 000000000..a5a775a2b Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png new file mode 100644 index 000000000..154c19343 Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Info.plist b/e2e/nested-router-tab-view/app/App_Resources/iOS/Info.plist new file mode 100644 index 000000000..ea3e3ea23 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiresFullScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/nested-router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard new file mode 100644 index 000000000..2ad9471e1 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/build.xcconfig b/e2e/nested-router-tab-view/app/App_Resources/iOS/build.xcconfig new file mode 100644 index 000000000..4b0118490 --- /dev/null +++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/build.xcconfig @@ -0,0 +1,7 @@ +// You can add custom settings here +// for example you can uncomment the following line to force distribution code signing +// CODE_SIGN_IDENTITY = iPhone Distribution +// To build for device with Xcode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html +// DEVELOPMENT_TEAM = YOUR_TEAM_ID; +ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; +ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; diff --git a/e2e/nested-router-tab-view/app/app.android.css b/e2e/nested-router-tab-view/app/app.android.css new file mode 100644 index 000000000..d69fe22a0 --- /dev/null +++ b/e2e/nested-router-tab-view/app/app.android.css @@ -0,0 +1,35 @@ +Button{ + font-size: 8; + padding-left: 5; + padding-right: 5; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; + margin-right: 0; + height: 50px; + color: blue; +} + +TextView{ + font-size: 10; + margin: 0; + padding: 0; + color: green; +} + +Label{ + font-size: 10; + margin: 0; + padding: 0; +} + +GridLayout{ + margin: 0; + padding: 0; +} + +ActionBar{ + height: 30; + margin: 0; +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/app.component.html b/e2e/nested-router-tab-view/app/app.component.html new file mode 100644 index 000000000..5aeb4e2b0 --- /dev/null +++ b/e2e/nested-router-tab-view/app/app.component.html @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/app.component.ts b/e2e/nested-router-tab-view/app/app.component.ts new file mode 100644 index 000000000..d3e62efe5 --- /dev/null +++ b/e2e/nested-router-tab-view/app/app.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, AfterViewInit, AfterContentInit, ViewChild } from "@angular/core"; +import { TabViewDirective } from "nativescript-angular/directives"; +import { Router, NavigationEnd } from "@angular/router"; +import { NSLocationStrategy } from "nativescript-angular/router/ns-location-strategy"; + + +@Component({ + selector: "ns-app", + templateUrl: "app.component.html", +}) + +export class AppComponent { + private isInitialNavigation = true; + + @ViewChild(TabViewDirective) tabView: TabViewDirective; + + constructor(router: Router, location: NSLocationStrategy) { + router.events.subscribe(e => { + if (e instanceof NavigationEnd) { + this.isInitialNavigation = false; + console.log("[ROUTER]: " + e.toString()); + console.log(location.toString()); + } + }) + } +} diff --git a/e2e/nested-router-tab-view/app/app.css b/e2e/nested-router-tab-view/app/app.css new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/nested-router-tab-view/app/app.ios.css b/e2e/nested-router-tab-view/app/app.ios.css new file mode 100644 index 000000000..38a30b36a --- /dev/null +++ b/e2e/nested-router-tab-view/app/app.ios.css @@ -0,0 +1,7 @@ +Button{ + color: blue; +} + +TextView{ + color: green; +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/app.module.ngfactory.d.ts b/e2e/nested-router-tab-view/app/app.module.ngfactory.d.ts new file mode 100644 index 000000000..793157de3 --- /dev/null +++ b/e2e/nested-router-tab-view/app/app.module.ngfactory.d.ts @@ -0,0 +1,4 @@ +/** + * A dynamically generated module when compiled with AoT. + */ +export const AppModuleNgFactory: any; \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/app.module.ts b/e2e/nested-router-tab-view/app/app.module.ts new file mode 100644 index 000000000..2be80e9fd --- /dev/null +++ b/e2e/nested-router-tab-view/app/app.module.ts @@ -0,0 +1,50 @@ +import { NgModule, NO_ERRORS_SCHEMA, ErrorHandler, NgModuleFactoryLoader } from "@angular/core"; +import { NativeScriptModule } from "nativescript-angular/nativescript.module"; +import { AppRoutingModule, COMPONENTS, MODALCOMPONENTS } from "./app.routing"; +import { AppComponent } from "./app.component"; + +import { DataService } from "./data.service"; +import { NSModuleFactoryLoader } from "nativescript-angular/router"; + +import { SharedModule } from "./shared.module"; +import { enable as traceEnable, addCategories } from "tns-core-modules/trace"; +import { routerTraceCategory } from "nativescript-angular/trace"; + +// addCategories(routerTraceCategory); +traceEnable(); + +class MyErrorHandler implements ErrorHandler { + handleError(error) { + console.log("### ErrorHandler Error: " + error.toString()); + console.log("### ErrorHandler Stack: " + error.stack); + } +} + +@NgModule({ + bootstrap: [ + AppComponent + ], + entryComponents: [MODALCOMPONENTS], + imports: [ + SharedModule, + NativeScriptModule, + AppRoutingModule, + ], + declarations: [ + AppComponent, + ...COMPONENTS, + MODALCOMPONENTS + ], + providers: [ + DataService, + { provide: ErrorHandler, useClass: MyErrorHandler }, + { provide: NgModuleFactoryLoader, useClass: NSModuleFactoryLoader } + ], + schemas: [ + NO_ERRORS_SCHEMA + ] +}) +/* +Pass your application module to the bootstrapModule function located in main.ts to start your app +*/ +export class AppModule { } \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/app.routing.ts b/e2e/nested-router-tab-view/app/app.routing.ts new file mode 100644 index 000000000..8dd38fb0e --- /dev/null +++ b/e2e/nested-router-tab-view/app/app.routing.ts @@ -0,0 +1,64 @@ +import { NgModule } from "@angular/core"; +import { NativeScriptRouterModule } from "nativescript-angular/router"; +import { Routes } from "@angular/router"; + +import { PlayerComponent } from "./player/players.component"; +import { PlayerDetailComponent } from "./player/player-detail.component"; +import { TeamsComponent } from "./team/teams.component"; +import { TeamDetailComponent } from "./team/team-detail.component"; +import { LoginComponent } from "./login/login.component"; +import { TabsComponent } from "./tabs/tabs.component"; +import { HomeComponent } from "./home/home.component"; + +import { ModalComponent } from "./modal/modal.component"; +import { NestedModalComponent } from "./modal-nested/modal-nested.component"; +import { ModalSecondComponent } from "./modal-second/modal-second.component"; +import { ModalRouterComponent } from "./modal/modal-router/modal-router.component"; + +export const COMPONENTS = [LoginComponent, TabsComponent]; +export const MODALCOMPONENTS = [ModalComponent, NestedModalComponent, ModalSecondComponent, ModalRouterComponent]; + +const routes: Routes = [ + { path: "", redirectTo: "/login", pathMatch: "full" }, + //{ path: "", component: LoginComponent }, + { + path: "login", component: LoginComponent + }, + { + path: "home", component: HomeComponent, children: [ + { + path: "players", component: PlayerComponent, outlet: "playerTab", children: [ + { + path: "modal", outlet: "modalOutlet", component: ModalComponent, children: [ + { path: "nested-frame-modal", component: NestedModalComponent }] + }, + { path: "modal-second", outlet: "modalOutlet", component: ModalSecondComponent } + ] + }, + { path: "player/:id", component: PlayerDetailComponent, outlet: "playerTab" }, + + { path: "teams", component: TeamsComponent, outlet: "teamTab" }, + { path: "team/:id", component: TeamDetailComponent, outlet: "teamTab" }, + + ] + }, + { + path: "home-lazy", + loadChildren: "./home-lazy/home-lazy.module#HomeLazyModule", + }, + { + path: "tabs", component: TabsComponent, children: [ + { path: "players", component: PlayerComponent, outlet: "playerTab" }, + { path: "player/:id", component: PlayerDetailComponent, outlet: "playerTab" }, + + { path: "teams", component: TeamsComponent, outlet: "teamTab" }, + { path: "team/:id", component: TeamDetailComponent, outlet: "teamTab" }, + ] + } +]; + +@NgModule({ + imports: [NativeScriptRouterModule.forRoot(routes)], + exports: [NativeScriptRouterModule], +}) +export class AppRoutingModule { } diff --git a/e2e/nested-router-tab-view/app/data.service.ts b/e2e/nested-router-tab-view/app/data.service.ts new file mode 100644 index 000000000..8242ebd77 --- /dev/null +++ b/e2e/nested-router-tab-view/app/data.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from "@angular/core"; + +export interface DataItem { + id: number; + name: string; +} + +@Injectable() +export class DataService { + private players = new Array( + { id: 1, name: "Player One" }, + { id: 2, name: "Player Two" }, + ); + + private teams = new Array( + { id: 1, name: "Team One" }, + { id: 2, name: "Team Two" }, + ); + + getPlayers(): DataItem[] { + return this.players; + } + + getPlayer(id: number): DataItem { + return this.players.filter(item => item.id === id)[0]; + } + + getTeams(): DataItem[] { + return this.teams; + } + + getTeam(id: number): DataItem { + return this.teams.filter(item => item.id === id)[0]; + } +} diff --git a/e2e/nested-router-tab-view/app/home-lazy/home-lazy.module.ts b/e2e/nested-router-tab-view/app/home-lazy/home-lazy.module.ts new file mode 100644 index 000000000..ea82d7ea2 --- /dev/null +++ b/e2e/nested-router-tab-view/app/home-lazy/home-lazy.module.ts @@ -0,0 +1,40 @@ +import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; +import { Route } from "@angular/router"; + +import { NativeScriptCommonModule } from "nativescript-angular/common"; +import { NativeScriptRouterModule } from "nativescript-angular/router"; +import { PlayerComponent } from "../player/players.component"; +import { PlayerDetailComponent } from "../player/player-detail.component"; +import { TeamsComponent } from "../team/teams.component"; +import { TeamDetailComponent } from "../team/team-detail.component"; +import { HomeComponent } from "../home/home.component"; +import { DataService } from "../data.service"; +import { SharedModule } from "../shared.module"; + +const routes: Route[] = [ + { + path: "home", + component: HomeComponent, + children: [ + { path: "players", component: PlayerComponent, outlet: "playerTab" }, + { path: "player/:id", component: PlayerDetailComponent, outlet: "playerTab" }, + + { path: "teams", component: TeamsComponent, outlet: "teamTab" }, + { path: "team/:id", component: TeamDetailComponent, outlet: "teamTab" }, + ] + } +]; + +@NgModule({ + schemas: [NO_ERRORS_SCHEMA], + imports: [ + NativeScriptCommonModule, + NativeScriptRouterModule, + NativeScriptRouterModule.forChild(routes), + SharedModule + ], + providers: [ + DataService + ] +}) +export class HomeLazyModule { } \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/home/home.component.html b/e2e/nested-router-tab-view/app/home/home.component.html new file mode 100644 index 000000000..6779e7468 --- /dev/null +++ b/e2e/nested-router-tab-view/app/home/home.component.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/home/home.component.ts b/e2e/nested-router-tab-view/app/home/home.component.ts new file mode 100644 index 000000000..9ad9c71cd --- /dev/null +++ b/e2e/nested-router-tab-view/app/home/home.component.ts @@ -0,0 +1,84 @@ +import { Component, ViewContainerRef } from "@angular/core"; +import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs"; +import { RouterExtensions } from "nativescript-angular/router"; +import { ActivatedRoute } from "@angular/router"; +import { confirm } from "tns-core-modules/ui/dialogs"; + +@Component({ + moduleId: module.id, + selector: "home-page", + templateUrl: "./home.component.html" +}) +export class HomeComponent { + constructor( + private modal: ModalDialogService, + private vcRef: ViewContainerRef, + private activeRoute: ActivatedRoute, + private routerExtension: RouterExtensions) { } + + ngOnInit() { + //this.routerExtension.navigate(["first"], { relativeTo: this.activeRoute }); + //this.routerExtension.navigate([{ outlets: { playerTab: ["players"], teamTab: ["teams"] } }], { relativeTo: this.activeRoute }); + //this.routerExtension.navigate(['players'], { relativeTo: this.activeRoute }); + } + + canGoBack() { + const canGoBack = this.routerExtension.canGoBack({ relativeTo: this.activeRoute }); + const title = "CanGoBack(ActivatedRoute)"; + this.onShowDialog(title, title + ` ${canGoBack}`); + } + + canGoBackPlayers() { + const canGoBack = this.routerExtension.canGoBack({ outlets: ["playerTab"], relativeTo: this.activeRoute }); + const title = "CanGoBack(Players)"; + this.onShowDialog(title, title + ` ${canGoBack}`); + } + + canGoBackTeams() { + const canGoBack = this.routerExtension.canGoBack({ outlets: ["teamTab"], relativeTo: this.activeRoute }); + const title = "CanGoBack(Teams)"; + this.onShowDialog(title, title + ` ${canGoBack}`); + } + + canGoBackBoth() { + const canGoBack = this.routerExtension.canGoBack({ outlets: ["teamTab", "playerTab"], relativeTo: this.activeRoute }); + const title = "CanGoBack(Both)"; + this.onShowDialog(title, title + ` ${canGoBack}`); + } + + backPlayers() { + this.routerExtension.back({ outlets: ["playerTab"], relativeTo: this.activeRoute }); + } + + backTeams() { + this.routerExtension.back({ outlets: ["teamTab"], relativeTo: this.activeRoute }); + } + + backBoth() { + this.routerExtension.back({ outlets: ["teamTab", "playerTab"], relativeTo: this.activeRoute }); + } + + backActivatedRoute() { + this.routerExtension.back({ relativeTo: this.activeRoute }); + } + + back() { + this.routerExtension.back(); + } + + navigatePlayers() { + this.routerExtension.navigate([{ outlets: { playerTab: ['player', '1'] } }], { relativeTo: this.activeRoute, animated: true, transition: { name: "flip", duration: 2000, curve: "linear" } }); + } + + onShowDialog(title: string, result: string) { + let options: any = { + title: title, + message: result, + okButtonText: "Ok" + } + + confirm(options).then((result: boolean) => { + console.log(result); + }) + } +} diff --git a/e2e/nested-router-tab-view/app/login/login.component.html b/e2e/nested-router-tab-view/app/login/login.component.html new file mode 100644 index 000000000..f022099ed --- /dev/null +++ b/e2e/nested-router-tab-view/app/login/login.component.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/login/login.component.ts b/e2e/nested-router-tab-view/app/login/login.component.ts new file mode 100644 index 000000000..68b5916f3 --- /dev/null +++ b/e2e/nested-router-tab-view/app/login/login.component.ts @@ -0,0 +1,19 @@ +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 { confirm } from "ui/dialogs"; + +import { AppModule } from "../app.module"; + +@Component({ + moduleId: module.id, + selector: "login-page", + templateUrl: "./login.component.html" +}) +export class LoginComponent { + constructor( + private modal: ModalDialogService, + private vcRef: ViewContainerRef, + private routerExtension: RouterExtensions) { } +} diff --git a/e2e/nested-router-tab-view/app/main.aot.ts b/e2e/nested-router-tab-view/app/main.aot.ts new file mode 100644 index 000000000..d5ff77a77 --- /dev/null +++ b/e2e/nested-router-tab-view/app/main.aot.ts @@ -0,0 +1,7 @@ +// this import should be first in order to load some required settings (like globals and reflect-metadata) +import { platformNativeScript } from "nativescript-angular/platform-static"; + +// "./app.module.ngfactory" is a dynamically generated module when compiled with AoT. +import { AppModuleNgFactory } from "./app.module.ngfactory"; + +platformNativeScript().bootstrapModuleFactory(AppModuleNgFactory); diff --git a/e2e/nested-router-tab-view/app/main.ts b/e2e/nested-router-tab-view/app/main.ts new file mode 100644 index 000000000..a84cb8844 --- /dev/null +++ b/e2e/nested-router-tab-view/app/main.ts @@ -0,0 +1,10 @@ +// this import should be first in order to load some required settings (like globals and reflect-metadata) +import { platformNativeScriptDynamic } from "nativescript-angular/platform"; + +import { AppModule } from "./app.module"; + +// A traditional NativeScript application starts by initializing global objects, setting up global CSS rules, creating, and navigating to the main page. +// Angular applications need to take care of their own initialization: modules, components, directives, routes, DI providers. +// A NativeScript Angular app needs to make both paradigms work together, so we provide a wrapper platform object, platformNativeScriptDynamic, +// that sets up a NativeScript application and can bootstrap the Angular framework. +platformNativeScriptDynamic().bootstrapModule(AppModule); diff --git a/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.html b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.html new file mode 100644 index 000000000..13c391cb7 --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.ts b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.ts new file mode 100644 index 000000000..7069b058d --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.ts @@ -0,0 +1,26 @@ +import { Component } from "@angular/core"; +import { View, ShownModallyData } from "ui/core/view" +import { ModalDialogParams } from "nativescript-angular/directives/dialogs"; + +@Component({ + moduleId: module.id, + selector: "modal-nested-page", + templateUrl: "./modal-nested.component.html" +}) +export class NestedModalComponent { + public navigationVisibility: string = "collapse"; + + constructor(private params: ModalDialogParams) { + + console.log("ModalNestedContent.constructor: " + JSON.stringify(params)); + this.navigationVisibility = params.context.navigationVisibility ? "visible" : "collapse"; + } + + close(layoutRoot: View) { + layoutRoot.closeModal(); + } + + onShowingModally(args: ShownModallyData) { + console.log("modal-page showingModally"); + } +} diff --git a/e2e/nested-router-tab-view/app/modal-second/modal-second.component.html b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.html new file mode 100644 index 000000000..9c6b4052e --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/modal-second/modal-second.component.ts b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.ts new file mode 100644 index 000000000..a1b280964 --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.ts @@ -0,0 +1,25 @@ +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { View, EventData } from "ui/core/view" +import { RouterExtensions } from "nativescript-angular/router"; + +@Component({ + moduleId: module.id, + selector: "modal-second-page", + templateUrl: "./modal-second.component.html" +}) +export class ModalSecondComponent { + constructor(private routerExtension: RouterExtensions, private activeRoute: ActivatedRoute) { } + + onLoaded() { + console.log("modal-second loaded"); + } + + goBack() { + this.routerExtension.back({ relativeTo: this.activeRoute }); + } + + close(layoutRoot: View) { + layoutRoot.closeModal(); + } +} diff --git a/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.html b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.html new file mode 100644 index 000000000..f4a99c901 --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.ts b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.ts new file mode 100644 index 000000000..f3be5f96e --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { RouterExtensions } from "nativescript-angular/router"; +import { ModalDialogParams } from "nativescript-angular/directives/dialogs"; + +@Component({ + moduleId: module.id, + selector: "ns-modal-router", + templateUrl: "./modal-router.component.html", +}) + +export class ModalRouterComponent implements OnInit { + private modalRoute: string; + + constructor(private params: ModalDialogParams, private routerExtension: RouterExtensions, private activeRoute: ActivatedRoute) { + this.modalRoute = params.context.modalRoute; + } + + ngOnInit() { + //this.routerExtension.navigate([this.modalRoute], { relativeTo: this.activeRoute }); + this.routerExtension.navigate([{ outlets: { modalOutlet: [ this.modalRoute] } }], { relativeTo: this.activeRoute }); + } +} diff --git a/e2e/nested-router-tab-view/app/modal/modal.component.html b/e2e/nested-router-tab-view/app/modal/modal.component.html new file mode 100644 index 000000000..2077becc8 --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal/modal.component.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/modal/modal.component.ts b/e2e/nested-router-tab-view/app/modal/modal.component.ts new file mode 100644 index 000000000..55ff26525 --- /dev/null +++ b/e2e/nested-router-tab-view/app/modal/modal.component.ts @@ -0,0 +1,86 @@ +import { Component, ViewContainerRef } from "@angular/core"; +import { ModalDialogParams, ModalDialogOptions, ModalDialogService } from "nativescript-angular/directives/dialogs"; +import { RouterExtensions, PageRoute } from "nativescript-angular/router"; +import { ActivatedRoute } from "@angular/router"; +import { View, ShownModallyData, EventData } from "ui/core/view" +import { confirm } from "ui/dialogs"; +import { ModalRouterComponent } from "../modal/modal-router/modal-router.component"; +import { NestedModalComponent } from "../modal-nested/modal-nested.component"; + +@Component({ + moduleId: module.id, + selector: "modal-page", + templateUrl: "./modal.component.html" +}) +export class ModalComponent { + public navigationVisibility: string = "collapse"; + + constructor(private params: ModalDialogParams, + private vcRef: ViewContainerRef, + private routerExtension: RouterExtensions, + private activeRoute: ActivatedRoute, + private modal: ModalDialogService) { + + console.log("\nModalContent.constructor: " + JSON.stringify(params)); + this.navigationVisibility = params.context.navigationVisibility ? "visible" : "collapse"; + } + + close(layoutRoot: View) { + layoutRoot.closeModal(); + } + + ngOnInit() { + } + + ngOnDestroy() { + console.log("ModalContent.ngOnDestroy"); + } + + onShowingModally(args: ShownModallyData) { + console.log("modal-page showingModally"); + } + + showDialogConfirm() { + let options = { + title: "Dialog", + message: "Message", + okButtonText: "Yes", + cancelButtonText: "No" + } + + confirm(options).then((result: boolean) => { + console.log(result); + }) + } + + showNestedModalFrame() { + const options: ModalDialogOptions = { + context: { + navigationVisibility: true, + modalRoute: "nested-frame-modal" + }, + fullscreen: true, + viewContainerRef: this.vcRef + }; + + this.modal.showModal(ModalRouterComponent, options).then((res: string) => { + console.log("nested-modal-frame closed"); + }); + } + + showNestedModal() { + const options: ModalDialogOptions = { + context: { navigationVisibility: false }, + fullscreen: true, + viewContainerRef: this.vcRef + }; + + this.modal.showModal(NestedModalComponent, options).then((res: string) => { + console.log("nested-modal closed"); + }); + } + + onNavigateSecondPage() { + this.routerExtension.navigate(["../modal-second"], { relativeTo: this.activeRoute }); + } +} diff --git a/e2e/nested-router-tab-view/app/package.json b/e2e/nested-router-tab-view/app/package.json new file mode 100644 index 000000000..826903ec1 --- /dev/null +++ b/e2e/nested-router-tab-view/app/package.json @@ -0,0 +1,8 @@ +{ + "android": { + "v8Flags": "--expose_gc" + }, + "main": "main.js", + "name": "tns-template-hello-world-ng", + "version": "3.4.3" +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/player/player-detail.component.html b/e2e/nested-router-tab-view/app/player/player-detail.component.html new file mode 100644 index 000000000..b4039955c --- /dev/null +++ b/e2e/nested-router-tab-view/app/player/player-detail.component.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/e2e/nested-router-tab-view/app/player/player-detail.component.ts b/e2e/nested-router-tab-view/app/player/player-detail.component.ts new file mode 100644 index 000000000..4155a1b54 --- /dev/null +++ b/e2e/nested-router-tab-view/app/player/player-detail.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { RouterExtensions } from "nativescript-angular/router"; +import { DataService, DataItem } from "../data.service"; +import { Subscription } from "rxjs"; + +@Component({ + selector: "ns-player-details", + moduleId: module.id, + templateUrl: "./player-detail.component.html", +}) +export class PlayerDetailComponent implements OnInit { + item: DataItem; + subscription: Subscription; + + constructor( + private data: DataService, + private route: ActivatedRoute, + private routerExtension: RouterExtensions, + private activeRoute: ActivatedRoute, + ) { } + + ngOnInit(): void { + this.subscription = this.route.params.subscribe(params => { + const id = +params["id"]; + this.item = this.data.getPlayer(id); + }) + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + backPlayers() { + this.routerExtension.back({ relativeTo: this.activeRoute }); + } +} diff --git a/e2e/nested-router-tab-view/app/player/players.component.html b/e2e/nested-router-tab-view/app/player/players.component.html new file mode 100644 index 000000000..780282771 --- /dev/null +++ b/e2e/nested-router-tab-view/app/player/players.component.html @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/player/players.component.ts b/e2e/nested-router-tab-view/app/player/players.component.ts new file mode 100644 index 000000000..677168612 --- /dev/null +++ b/e2e/nested-router-tab-view/app/player/players.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit, ViewContainerRef } from "@angular/core"; +import { DataService, DataItem } from "../data.service"; +import { RouterExtensions } from "nativescript-angular/router"; + +import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs"; +import { ModalRouterComponent } from "../modal/modal-router/modal-router.component"; +@Component({ + selector: "ns-players", + moduleId: module.id, + templateUrl: "./players.component.html", +}) +export class PlayerComponent implements OnInit { + items: DataItem[]; + + constructor(private modal: ModalDialogService, private itemService: DataService, private router: RouterExtensions, private vcRef: ViewContainerRef, ) { } + + ngOnInit(): void { + this.items = this.itemService.getPlayers(); + } + + onModalFrame() { + const options: ModalDialogOptions = { + context: { + navigationVisibility: true, + modalRoute: "modal" + }, + fullscreen: true, + viewContainerRef: this.vcRef + }; + + this.modal.showModal(ModalRouterComponent, options).then((res: string) => { + console.log("moda-frame closed"); + }); + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/shared.module.ts b/e2e/nested-router-tab-view/app/shared.module.ts new file mode 100644 index 000000000..03a4b7152 --- /dev/null +++ b/e2e/nested-router-tab-view/app/shared.module.ts @@ -0,0 +1,41 @@ +import { NgModule, NO_ERRORS_SCHEMA, ErrorHandler, NgModuleFactoryLoader } from "@angular/core"; +// import { NativeScriptModule } from "nativescript-angular/nativescript.module"; +// import { AppRoutingModule, COMPONENTS } from "./app.routing"; +// import { AppComponent } from "./app.component"; +import { NativeScriptCommonModule } from "nativescript-angular/common"; +import { NativeScriptRouterModule } from "nativescript-angular/router"; +import { DataService } from "./data.service"; + +import { HomeComponent } from "./home/home.component"; +import { PlayerComponent } from "./player/players.component"; +import { PlayerDetailComponent } from "./player/player-detail.component"; +import { TeamsComponent } from "./team/teams.component"; +import { TeamDetailComponent } from "./team/team-detail.component"; + +@NgModule({ + imports: [ + NativeScriptCommonModule, + NativeScriptRouterModule, + ], + declarations: [ + HomeComponent, + PlayerComponent, + PlayerDetailComponent, + TeamsComponent, + TeamDetailComponent + ], + exports: [ + HomeComponent, + PlayerComponent, + PlayerDetailComponent, + TeamsComponent, + TeamDetailComponent + ], + schemas: [ + NO_ERRORS_SCHEMA + ] +}) +/* +Pass your application module to the bootstrapModule function located in main.ts to start your app +*/ +export class SharedModule { } \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/tabs/tabs.component.html b/e2e/nested-router-tab-view/app/tabs/tabs.component.html new file mode 100644 index 000000000..8bb946b66 --- /dev/null +++ b/e2e/nested-router-tab-view/app/tabs/tabs.component.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/tabs/tabs.component.ts b/e2e/nested-router-tab-view/app/tabs/tabs.component.ts new file mode 100644 index 000000000..10ddc1b12 --- /dev/null +++ b/e2e/nested-router-tab-view/app/tabs/tabs.component.ts @@ -0,0 +1,55 @@ +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 { ActivatedRoute } from "@angular/router"; +import { confirm } from "ui/dialogs"; +import { Page } from "ui/page"; +import { AppModule } from "../app.module"; + +@Component({ + moduleId: module.id, + selector: "tabs-page", + templateUrl: "./tabs.component.html" +}) +export class TabsComponent { + constructor( + private modal: ModalDialogService, + private vcRef: ViewContainerRef, + private routerExtension: RouterExtensions, + private activeRoute: ActivatedRoute, + private page: Page) { + // this.page.actionBarHidden = true; + } + + ngOnInit() { + //this.routerExtension.navigate(["players"], { relativeTo: this.activeRoute }); + this.routerExtension.navigate([{ outlets: { playerTab: ["players"], teamTab: ["teams"] } }], { relativeTo: this.activeRoute }); + } + + canGoBack() { + const canGoBack = this.routerExtension.canGoBack({ relativeTo: this.activeRoute }); + const title = "CanGoBack(ActivatedRoute)"; + this.onShowDialog(title, title + ` ${canGoBack}`); + } + + backActivatedRoute() { + this.routerExtension.back({ relativeTo: this.activeRoute }); + } + + back() { + this.routerExtension.back(); + } + + onShowDialog(title: string, result: string) { + let options: any = { + title: title, + message: result, + okButtonText: "Ok" + } + + confirm(options).then((result: boolean) => { + console.log(result); + }) + } +} diff --git a/e2e/nested-router-tab-view/app/team/team-detail.component.html b/e2e/nested-router-tab-view/app/team/team-detail.component.html new file mode 100644 index 000000000..13f4b87b7 --- /dev/null +++ b/e2e/nested-router-tab-view/app/team/team-detail.component.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/e2e/nested-router-tab-view/app/team/team-detail.component.ts b/e2e/nested-router-tab-view/app/team/team-detail.component.ts new file mode 100644 index 000000000..e4d92b232 --- /dev/null +++ b/e2e/nested-router-tab-view/app/team/team-detail.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { RouterExtensions } from "nativescript-angular/router"; +import { DataService, DataItem } from "../data.service"; +import { Subscription } from "rxjs"; + +@Component({ + selector: "ns-team-details", + moduleId: module.id, + templateUrl: "./team-detail.component.html", +}) +export class TeamDetailComponent implements OnInit { + item: DataItem; + subscription: Subscription; + + constructor( + private data: DataService, + private route: ActivatedRoute, + private routerExtension: RouterExtensions, + private activeRoute: ActivatedRoute, + ) { } + + ngOnInit(): void { + this.subscription = this.route.params.subscribe(params => { + const id = +params["id"]; + this.item = this.data.getTeam(id); + }) + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + backTeams() { + this.routerExtension.back({ relativeTo: this.activeRoute }); + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/app/team/teams.component.html b/e2e/nested-router-tab-view/app/team/teams.component.html new file mode 100644 index 000000000..4126e1d3b --- /dev/null +++ b/e2e/nested-router-tab-view/app/team/teams.component.html @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/e2e/nested-router-tab-view/app/team/teams.component.ts b/e2e/nested-router-tab-view/app/team/teams.component.ts new file mode 100644 index 000000000..119d1d2fe --- /dev/null +++ b/e2e/nested-router-tab-view/app/team/teams.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from "@angular/core"; +import { DataService, DataItem } from "../data.service"; + +@Component({ + selector: "ns-teams", + moduleId: module.id, + templateUrl: "./teams.component.html", +}) +export class TeamsComponent implements OnInit { + items: DataItem[]; + + constructor(private itemService: DataService) { } + + ngOnInit(): void { + this.items = this.itemService.getTeams(); + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts new file mode 100644 index 000000000..5e8e88e74 --- /dev/null +++ b/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts @@ -0,0 +1,122 @@ +import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; +import { Screen } from "./screen" +import { + testPlayerNavigated, + testTeamNavigated, + testPlayerNextNavigated, + testTeamNextNavigated, + testPlayersNavigated, + canGoBack, + testTeamsNavigated +} from "./shared.e2e-spec" + +const pages = ["Go To Home Page", "Go To Lazy Home Page"]; + +describe("home-tabs:", () => { + let driver: AppiumDriver; + let screen: Screen; + + before(async () => { + driver = await createDriver(); + screen = new Screen(driver); + }); + + after(async () => { + await driver.quit(); + console.log("Quit driver!"); + }); + + pages.forEach(page => { + describe(`${page} home-tab:`, () => { + + afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logTestArtifacts(this.currentTest.title); + } + }); + + it("loaded home component and lists", async () => { + await screen.navigateToHomePage(page); + await screen.loadedHome(); + await screen.loadedPlayersList(); + await screen.loadedTeamList(); + }); + + it("should navigate to Tabs without Players/Teams navigation", async () => { + await screen.navigateToTabsPage(); + await screen.loadedTabs(); + await screen.loadedPlayersList(); + await backActivatedRoute(driver); + await screen.loadedHome(); + await screen.loadedPlayersList(); + await screen.loadedTeamList(); + }); + + it("should navigate Player One/Team One then go to Tabs and back", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testTeamNavigated(screen, screen.teamOne); + await screen.navigateToTabsPage(); + await screen.loadedTabs(); + await screen.loadedPlayersList(); + await gotoTeamsTab(driver); + await screen.loadedTeamList(); + await backActivatedRoute(driver); + await screen.loadedHome(); + await screen.loadedPlayerDetails(screen.playerOne); + await screen.loadedTeamDetails(screen.teamOne); + await backBoth(driver); + await screen.loadedPlayersList(); + await screen.loadedTeamList(); + }); + + it("should navigate 2 times in Players go to Tabs and back", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testPlayerNextNavigated(screen, screen.playerTwo); + await screen.navigateToTabsPage(); + await screen.loadedTabs(); + await screen.loadedPlayersList(); + await gotoTeamsTab(driver); + await screen.loadedTeamList(); + await backActivatedRoute(driver); + await screen.loadedHome(); + await screen.loadedPlayerDetails(screen.playerTwo); + await screen.loadedTeamList(); + await backPlayers(driver); + await screen.loadedPlayerDetails(screen.playerOne); + await backPlayers(driver); + await screen.loadedPlayersList(); + await screen.loadedTeamList(); + }); + + it("should navigate back to Login page with back(activatedRoute)", async function () { + await backActivatedRoute(driver); + await screen.loadedLogin; + }); + }); + }); +}); + +async function backActivatedRoute(driver: AppiumDriver) { + const btnBack = await driver.findElementByText("Back(ActivatedRoute)"); + await btnBack.tap(); +} + +async function backPlayers(driver: AppiumDriver) { + const btnBackPlayers = await driver.findElementByText("Back(Players)"); + await btnBackPlayers.tap(); +} + +async function backBoth(driver: AppiumDriver) { + const btnBackBoth = await driver.findElementByText("Back(Both)"); + await btnBackBoth.tap(); +} + +async function gotoPlayersTab(driver: AppiumDriver) { + const btnTabPlayers = await driver.findElementByText("Players Tab"); + await btnTabPlayers.tap(); +} + +async function gotoTeamsTab(driver: AppiumDriver) { + const btnTabTeams = await driver.findElementByText("Teams Tab"); + await btnTabTeams.tap(); +} diff --git a/e2e/nested-router-tab-view/e2e/screen.ts b/e2e/nested-router-tab-view/e2e/screen.ts new file mode 100644 index 000000000..0c9126fd8 --- /dev/null +++ b/e2e/nested-router-tab-view/e2e/screen.ts @@ -0,0 +1,145 @@ +import { AppiumDriver, SearchOptions } from "nativescript-dev-appium"; +import { assert } from "chai"; + +const home = "Home Component"; +const login = "Login Component"; +const tabs = "Tabs Component"; + +const playerList = "Player List"; +const playerDetails = "Player Details"; +const gotoNextPlayer = "next player"; +const gotoPlayers = "players"; + +const teamDetails = "Team Details"; +const teamList = "Team List"; +const gotoNextTeam = "next team"; +const gotoTeams = "teams"; + +const gotoHomePage = "Go To Home Page"; +const gotoTabsPage = "Go To Tabs Page"; +const confirmDialog = "Ok"; + +export class Screen { + + private _driver: AppiumDriver; + + playerOne = "Player One"; + playerTwo = "Player Two"; + teamOne = "Team One"; + teamTwo = "Team Two"; + + canGoBackActivatedRoute = "CanGoBack(ActivatedRoute)"; + canGoBackPlayers = "CanGoBack(Players)"; + canGoBackTeams = "CanGoBack(Teams)"; + canGoBackBoth = "CanGoBack(Both)"; + + constructor(driver: AppiumDriver) { + this._driver = driver; + } + + showDialogConfirm = async (title) => { + const btnShowDialogConfirm = await this._driver.findElementByText(title); + await btnShowDialogConfirm.tap(); + } + + loadedConfirmDialog = async (dialogMessage) => { + const lblDialogMessage = await this._driver.findElementByText(dialogMessage); + assert.isTrue(await lblDialogMessage.isDisplayed()); + console.log(dialogMessage + " shown!"); + } + + closeDialog = async () => { + const btnYesDialog = await this._driver.findElementByText(confirmDialog); + await btnYesDialog.click(); + } + + loadedLogin = async () => { + const lblLogin = await this._driver.findElementByText(login); + assert.isTrue(await lblLogin.isDisplayed()); + console.log(login + " loaded!"); + } + + loadedHome = async () => { + const lblHome = await this._driver.findElementByText(home); + assert.isTrue(await lblHome.isDisplayed()); + console.log(home + " loaded!"); + } + + loadedTabs = async () => { + const lblTabs = await this._driver.findElementByText(tabs); + assert.isTrue(await lblTabs.isDisplayed()); + console.log(tabs + " loaded!"); + } + + navigateToTabsPage = async () => { + const btnNavToTabsPage = await this._driver.findElementByText(gotoTabsPage); + await btnNavToTabsPage.tap(); + } + + loadedPlayersList = async () => { + const lblPlayerList = await this._driver.findElementByText(playerList); + assert.isTrue(await lblPlayerList.isDisplayed()); + console.log(playerList + " loaded!"); + } + + loadedPlayerDetails = async (player) => { + const lblPlayerDetail = await this._driver.findElementByText(playerDetails); + assert.isTrue(await lblPlayerDetail.isDisplayed()); + + const lblPlayer = await this._driver.findElementByText(player + " Details"); + assert.isTrue(await lblPlayer.isDisplayed()); + + console.log(player + " Details" + " loaded!"); + } + + loadedTeamList = async () => { + const lblTeamList = await this._driver.findElementByText(teamList, 10); + assert.isTrue(await lblTeamList.isDisplayed()); + console.log(teamList + " loaded!"); + } + + loadedTeamDetails = async (team) => { + const lblTeamDetail = await this._driver.findElementByText(teamDetails); + assert.isTrue(await lblTeamDetail.isDisplayed()); + + const lblTeam = await this._driver.findElementByText(team + " Details"); + assert.isTrue(await lblTeam.isDisplayed()); + + console.log(team + " Details" + " loaded!"); + } + + navigateToHomePage = async (homePageButton?) => { + const btnNavToHomePage = await this._driver.findElementByText(homePageButton || gotoHomePage); + await btnNavToHomePage.tap(); + } + + navigateToPlayer = async (player: string) => { + const btnNavPlayerPage = await this._driver.findElementByText(player); + await btnNavPlayerPage.tap(); + } + + navigateToNextPlayer = async () => { + const btnNavPlayerNextPage = await this._driver.findElementByText(gotoNextPlayer); + await btnNavPlayerNextPage.tap(); + } + + navigateToPlayers = async () => { + const btnNavPlayersPage = await this._driver.findElementByText(gotoPlayers); + await btnNavPlayersPage.tap(); + } + + navigateToTeam = async (team: string) => { + const btnNavTeamPage = await this._driver.findElementByText(team); + await btnNavTeamPage.tap(); + } + + navigateToNextTeam = async () => { + const btnNavTeamNextPage = await this._driver.findElementByText(gotoNextTeam); + await btnNavTeamNextPage.tap(); + } + + navigateToTeams = async () => { + const btnNavTeamsPage = await this._driver.findElementByText(gotoTeams); + await btnNavTeamsPage.tap(); + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/e2e/setup.ts b/e2e/nested-router-tab-view/e2e/setup.ts new file mode 100644 index 000000000..8b26e66e9 --- /dev/null +++ b/e2e/nested-router-tab-view/e2e/setup.ts @@ -0,0 +1,9 @@ +import { startServer, stopServer } from "nativescript-dev-appium"; + +before("start server", async () => { + await startServer(); +}); + +after("stop server", async () => { + await stopServer(); +}); diff --git a/e2e/nested-router-tab-view/e2e/shared.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/shared.e2e-spec.ts new file mode 100644 index 000000000..1a23b1d35 --- /dev/null +++ b/e2e/nested-router-tab-view/e2e/shared.e2e-spec.ts @@ -0,0 +1,37 @@ +import { Screen } from "./screen" + +export async function canGoBack(screen: Screen, title: string, result: boolean) { + await screen.showDialogConfirm(title); + await screen.loadedConfirmDialog(title + ` ${result}`); + await screen.closeDialog(); +} + +export async function testPlayerNavigated(screen: Screen, player: string) { + await screen.navigateToPlayer(player); + await screen.loadedPlayerDetails(player); +} + +export async function testPlayerNextNavigated(screen: Screen, nextPlayer: string) { + await screen.navigateToNextPlayer(); + await screen.loadedPlayerDetails(nextPlayer); +} + +export async function testPlayersNavigated(screen: Screen) { + await screen.navigateToPlayers(); + await screen.loadedPlayersList(); +} + +export async function testTeamNavigated(screen: Screen, team: string) { + await screen.navigateToTeam(team); + await screen.loadedTeamDetails(team); +} + +export async function testTeamNextNavigated(screen: Screen, nextTeam: string) { + await screen.navigateToNextTeam(); + await screen.loadedTeamDetails(nextTeam); +} + +export async function testTeamsNavigated(screen: Screen) { + await screen.navigateToTeams(); + await screen.loadedTeamList(); +} diff --git a/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts new file mode 100644 index 000000000..5fe6eacac --- /dev/null +++ b/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts @@ -0,0 +1,177 @@ +import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; +import { Screen } from "./screen" +import { + testPlayerNavigated, + testTeamNavigated, + testPlayerNextNavigated, + testTeamNextNavigated, + testPlayersNavigated, + canGoBack +} from "./shared.e2e-spec" + +const pages = ["Go To Home Page", "Go To Lazy Home Page"]; + +describe("split-view:", () => { + let driver: AppiumDriver; + let screen: Screen; + + before(async () => { + driver = await createDriver(); + screen = new Screen(driver); + }); + + after(async () => { + await driver.quit(); + console.log("Quit driver!"); + }); + + pages.forEach(page => { + describe(`${page} split-view:`, () => { + + afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logTestArtifacts(this.currentTest.title); + } + }); + + it("loaded home component and lists", async () => { + await screen.navigateToHomePage(page); + await screen.loadedHome(); + await screen.loadedPlayersList(); + await screen.loadedTeamList(); + await canGoBack(screen, screen.canGoBackActivatedRoute, true); + }); + + it("should navigate Player One/Team One then back separately", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testTeamNavigated(screen, screen.teamOne); + await canGoBack(screen, screen.canGoBackPlayers, true); + await canGoBack(screen, screen.canGoBackTeams, true); + await backPlayers(driver); + await screen.loadedPlayersList(); + await backTeams(driver); + await canGoBack(screen, screen.canGoBackTeams, false); + await screen.loadedTeamList(); + }); + + it("should navigate Player One/Team One then back separately (keep order)", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testTeamNavigated(screen, screen.teamOne); + await backTeams(driver); + await screen.loadedTeamList(); + await backPlayers(driver); + await screen.loadedPlayersList(); + }); + + it("should navigate Player One/Team One then back simultaneously", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testTeamNavigated(screen, screen.teamOne); + await canGoBack(screen, screen.canGoBackBoth, true); + await backBoth(driver); + await canGoBack(screen, screen.canGoBackBoth, false); + await screen.loadedTeamList(); + await screen.loadedPlayersList(); + }); + + it("should navigate Player One/Team One then next Player/Team then back separately", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testTeamNavigated(screen, screen.teamOne); + await testPlayerNextNavigated(screen, screen.playerTwo); + await testTeamNextNavigated(screen, screen.teamTwo); + + await backPlayers(driver); + await screen.loadedPlayerDetails(screen.playerOne); + await backTeams(driver); + await screen.loadedTeamDetails(screen.teamOne); + await backBoth(driver); + await screen.loadedTeamList(); + await screen.loadedPlayersList(); + }); + + it("should navigate Player One/Team One then back", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testTeamNavigated(screen, screen.teamOne); + await back(driver); + await screen.loadedPlayerDetails(screen.playerOne); + await screen.loadedTeamList(); + + await backPlayers(driver); + await screen.loadedPlayersList(); + }); + + it("should navigate Player One then navigate Players list then back", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testPlayerNextNavigated(screen, screen.playerTwo); + await testPlayersNavigated(screen); + await back(driver); + await screen.loadedPlayerDetails(screen.playerTwo); + await back(driver); + await screen.loadedPlayerDetails(screen.playerOne); + await back(driver); + await screen.loadedPlayersList(); + }); + + it("should navigate Player One/Team One then Android back button", async () => { + if (driver.isAndroid) { + await testPlayerNavigated(screen, screen.playerOne); + await testTeamNavigated(screen, screen.teamOne); + await driver.navBack(); + await screen.loadedPlayerDetails(screen.playerOne); + await screen.loadedTeamList(); + + await backPlayers(driver); + await screen.loadedPlayersList(); + } + }); + + it("should navigate Player One then navigate Players list then Android back button", async function () { + if (driver.isAndroid) { + await testPlayerNavigated(screen, screen.playerOne); + await testPlayerNextNavigated(screen, screen.playerTwo); + await testPlayersNavigated(screen); + await driver.navBack(); + await screen.loadedPlayerDetails(screen.playerTwo); + await driver.navBack(); + await screen.loadedPlayersList(); + } + }); + + it("should not navigate back when no back stack available", async function () { + await backTeams(driver); + await screen.loadedTeamList(); + await backPlayers(driver); + await screen.loadedPlayersList(); + }); + + it("should navigate back to Login page with back(activatedRoute)", async function () { + await backActivatedRoute(driver); + await screen.loadedLogin; + }); + }); + }); +}); + +async function backActivatedRoute(driver: AppiumDriver) { + const btnBack = await driver.findElementByText("Back(ActivatedRoute)"); + await btnBack.tap(); +} + +async function back(driver: AppiumDriver) { + const btnBack = await driver.findElementByText("Back()"); + await btnBack.tap(); +} + +async function backPlayers(driver: AppiumDriver) { + const btnBackPlayers = await driver.findElementByText("Back(Players)"); + await btnBackPlayers.tap(); +} + +async function backTeams(driver: AppiumDriver) { + const btnBackTeams = await driver.findElementByText("Back(Teams)"); + await btnBackTeams.tap(); +} + +async function backBoth(driver: AppiumDriver) { + const btnBackBoth = await driver.findElementByText("Back(Both)"); + await btnBackBoth.tap(); +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts new file mode 100644 index 000000000..47b7fb0bb --- /dev/null +++ b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts @@ -0,0 +1,96 @@ +import { AppiumDriver, createDriver } from "nativescript-dev-appium"; +import { Screen } from "./screen" +import { + testPlayerNavigated, + testTeamNavigated, + testPlayerNextNavigated, + testTeamNextNavigated, +} from "./shared.e2e-spec" + +describe("tab-view:", () => { + let driver: AppiumDriver; + let screen: Screen; + + before(async () => { + driver = await createDriver(); + screen = new Screen(driver); + }); + + after(async () => { + await driver.quit(); + console.log("Quit driver!"); + }); + + afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logTestArtifacts(this.currentTest.title); + } + }); + + it("loaded home component and lists", async () => { + await screen.navigateToHomePage(); + await screen.loadedHome(); + await screen.loadedPlayersList(); + await screen.loadedTeamList(); + }); + + it("loaded tabs component, Players List and Teams List pages", async () => { + await screen.navigateToTabsPage(); + await screen.loadedTabs(); + await screen.loadedPlayersList(); + await gotoTeamsTab(driver); + await screen.loadedTeamList(); + await gotoPlayersTab(driver); + await screen.loadedPlayersList(); + }); + + it("should navigate Player One/Team One then back separately", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await gotoTeamsTab(driver); + await testTeamNavigated(screen, screen.teamOne); + await backTeams(driver); + await screen.loadedTeamList(); + await gotoPlayersTab(driver); + await backPlayers(driver); + await screen.loadedPlayersList(); + }); + + it("should navigate Player One/Team One then next Player/Team then back", async () => { + await testPlayerNavigated(screen, screen.playerOne); + await testPlayerNextNavigated(screen, screen.playerTwo); + await gotoTeamsTab(driver); + await testTeamNavigated(screen, screen.teamOne); + await testTeamNextNavigated(screen, screen.teamTwo); + await gotoPlayersTab(driver); + await backPlayers(driver); + await screen.loadedPlayerDetails(screen.playerOne); + await gotoTeamsTab(driver); + await backTeams(driver); + await screen.loadedTeamDetails(screen.teamOne); + await backTeams(driver); + await screen.loadedTeamList(); + await gotoPlayersTab(driver); + await backPlayers(driver); + await screen.loadedPlayersList(); + }); +}); + +async function gotoPlayersTab(driver: AppiumDriver) { + const btnTabPlayers = await driver.findElementByText("Players Tab"); + await btnTabPlayers.tap(); +} + +async function gotoTeamsTab(driver: AppiumDriver) { + const btnTabTeams = await driver.findElementByText("Teams Tab"); + await btnTabTeams.tap(); +} + +async function backTeams(driver: AppiumDriver) { + const btnBackTeams = await driver.findElementByText("Back-Teams"); + await btnBackTeams.tap(); +} + +async function backPlayers(driver: AppiumDriver) { + const btnBackPlayers = await driver.findElementByText("Back-Players"); + await btnBackPlayers.tap(); +} diff --git a/e2e/nested-router-tab-view/e2e/tsconfig.json b/e2e/nested-router-tab-view/e2e/tsconfig.json new file mode 100644 index 000000000..c297b2347 --- /dev/null +++ b/e2e/nested-router-tab-view/e2e/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "importHelpers": false, + "types": [ + "node", + "mocha", + "chai" + ], + "lib": [ + "es2015", + "dom" + ] + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/package.json b/e2e/nested-router-tab-view/package.json new file mode 100644 index 000000000..0c75b2cdb --- /dev/null +++ b/e2e/nested-router-tab-view/package.json @@ -0,0 +1,54 @@ +{ + "description": "NativeScript Application", + "license": "SEE LICENSE IN ", + "readme": "NativeScript Application", + "repository": "", + "nativescript": { + "id": "org.nativescript.nestedroutertabview", + "tns-ios": { + "version": "next" + }, + "tns-android": { + "version": "next" + } + }, + "dependencies": { + "@angular/animations": "~6.1.0", + "@angular/common": "~6.1.0", + "@angular/compiler": "~6.1.0", + "@angular/core": "~6.1.0", + "@angular/forms": "~6.1.0", + "@angular/http": "~6.1.0", + "@angular/platform-browser": "~6.1.0", + "@angular/platform-browser-dynamic": "~6.1.0", + "@angular/router": "~6.1.0", + "nativescript-angular": "file:../../nativescript-angular", + "nativescript-theme-core": "~1.0.4", + "reflect-metadata": "~0.1.8", + "rxjs": "~6.0.0-rc.1", + "tns-core-modules": "next", + "zone.js": "~0.8.2" + }, + "devDependencies": { + "@angular/compiler-cli": "~6.1.0", + "@ngtools/webpack": "~6.2.0", + "@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", + "lazy": "1.0.11", + "mocha": "^5.2.0", + "mocha-junit-reporter": "^1.18.0", + "mocha-multi": "^1.0.1", + "nativescript-dev-appium": "next", + "nativescript-dev-typescript": "~0.7.4", + "nativescript-dev-webpack": "next", + "typescript": "~2.7.2" + }, + "scripts": { + "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json", + "compile-tests": "tsc -p e2e --watch" + } +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/references.d.ts b/e2e/nested-router-tab-view/references.d.ts new file mode 100644 index 000000000..b14f3837d --- /dev/null +++ b/e2e/nested-router-tab-view/references.d.ts @@ -0,0 +1 @@ +/// Needed for autocompletion and compilation. \ No newline at end of file diff --git a/e2e/nested-router-tab-view/tsconfig.json b/e2e/nested-router-tab-view/tsconfig.json new file mode 100644 index 000000000..575276e9f --- /dev/null +++ b/e2e/nested-router-tab-view/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "noEmitHelpers": true, + "noEmitOnError": true, + "sourceMap": true, + "lib": [ + "es6", + "dom", + "es2015.iterable" + ], + "baseUrl": ".", + "paths": { + "*": [ + "./node_modules/tns-core-modules/*", + "./node_modules/*" + ], + "~/*": [ + "app/*" + ] + } + }, + "exclude": [ + "node_modules", + "platforms" + ] +} \ No newline at end of file diff --git a/e2e/nested-router-tab-view/tsconfig.tns.json b/e2e/nested-router-tab-view/tsconfig.tns.json new file mode 100644 index 000000000..95f2ecee0 --- /dev/null +++ b/e2e/nested-router-tab-view/tsconfig.tns.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node" + } +} diff --git a/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts b/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts index d169ff7e3..b52412004 100644 --- a/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts +++ b/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts @@ -15,7 +15,7 @@ describe("TabView with page-router-outlet in each tab", () => { afterEach(async function () { if (this.currentTest.state === "failed") { - await driver.logScreenshot(this.currentTest.title); + await driver.logTestArtifacts(this.currentTest.title); } }); diff --git a/e2e/router/app/app-routing.module.ts b/e2e/router/app/app-routing.module.ts index dcc881a54..c4da4fec9 100644 --- a/e2e/router/app/app-routing.module.ts +++ b/e2e/router/app/app-routing.module.ts @@ -1,5 +1,5 @@ import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; -import { NativeScriptRouterModule } from "nativescript-angular/router"; +import { NativeScriptRouterModule, NSEmptyOutletComponent } from "nativescript-angular/router"; import { FirstComponent } from "./first/first.component" import { SecondComponent } from "./second/second.component" @@ -17,10 +17,17 @@ export const routes = [ component: FirstComponent, }, { - path: "second/:depth", component: SecondComponent, + path: "second/:depth", + component: SecondComponent, children: [ { path: "", component: MasterComponent }, - { path: "detail/:id", component: DetailComponent } + { path: "detail/:id", component: DetailComponent }, + { + path: "lazy-named", + outlet: "lazyNameOutlet", + component: NSEmptyOutletComponent, + loadChildren: "./lazy-named/lazy-named.module#LazyNamedModule", + } ] }, { @@ -50,7 +57,7 @@ export const navigatableComponents = [ ]; @NgModule({ - imports: [NativeScriptRouterModule.forRoot(routes)], + imports: [NativeScriptRouterModule, NativeScriptRouterModule.forRoot(routes)], exports: [NativeScriptRouterModule], }) export class AppRoutingModule { } diff --git a/e2e/router/app/first/first.component.ts b/e2e/router/app/first/first.component.ts index 6c7cdcc7c..9445103e9 100644 --- a/e2e/router/app/first/first.component.ts +++ b/e2e/router/app/first/first.component.ts @@ -14,7 +14,6 @@ import { Page } from "tns-core-modules/ui/page"; - diff --git a/e2e/router/app/lazy-named/lazy-named.module.ts b/e2e/router/app/lazy-named/lazy-named.module.ts new file mode 100644 index 000000000..22cc87913 --- /dev/null +++ b/e2e/router/app/lazy-named/lazy-named.module.ts @@ -0,0 +1,31 @@ +import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; +import { Route } from "@angular/router"; + +import { NativeScriptCommonModule } from "nativescript-angular/common"; +import { NativeScriptRouterModule } from "nativescript-angular/router"; + +import { NestedMasterComponent } from "./nested-master.component" +import { NestedDetailComponent } from "./nested-detail.component" + +const routes: Route[] = [ + { path: "", component: NestedMasterComponent }, + { path: "detail/:id", component: NestedDetailComponent } +]; + +@NgModule({ + schemas: [NO_ERRORS_SCHEMA], + imports: [ + NativeScriptCommonModule, + NativeScriptRouterModule, + NativeScriptRouterModule.forChild(routes) + ], + declarations: [ + NestedMasterComponent, + NestedDetailComponent + ], + exports:[ + NestedMasterComponent, + NestedDetailComponent + ] +}) +export class LazyNamedModule { } \ No newline at end of file diff --git a/e2e/router/app/lazy-named/nested-detail.component.ts b/e2e/router/app/lazy-named/nested-detail.component.ts new file mode 100644 index 000000000..07001a901 --- /dev/null +++ b/e2e/router/app/lazy-named/nested-detail.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit, OnDestroy } from "@angular/core"; +import { ActivatedRoute, Router, Route } from "@angular/router"; +import { Location } from "@angular/common"; +import { Observable } from "rxjs"; +import { map } from "rxjs/operators"; +import { Page } from "tns-core-modules/ui/page"; +import { RouterExtensions } from "nativescript-angular/router"; + +@Component({ + selector: "detail", + template: ` + + + + + ` +}) +export class NestedDetailComponent { + public id$: Observable; + + constructor(private router: Router, private route: ActivatedRoute, private page: Page, private routerExt: RouterExtensions) { + this.page.actionBar.title = "NamedNestedDetail"; + console.log("DetailComponent - constructor()"); + this.id$ = route.params.pipe(map(r => r["id"])); + } + + ngOnInit() { + console.log("DetailComponent - ngOnInit()"); + } + + ngOnDestroy() { + console.log("DetailComponent - ngOnDestroy()"); + } + + goBack(){ + this.routerExt.back(); + } +} diff --git a/e2e/router/app/lazy-named/nested-master.component.ts b/e2e/router/app/lazy-named/nested-master.component.ts new file mode 100644 index 000000000..2d68866f5 --- /dev/null +++ b/e2e/router/app/lazy-named/nested-master.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit, OnDestroy } from "@angular/core"; +import { Page } from "tns-core-modules/ui/page"; +@Component({ + selector: "master", + template: ` + + + + + + ` +}) +export class NestedMasterComponent implements OnInit, OnDestroy { + public details: Array = [1, 2, 3]; + + constructor(private page: Page) { + this.page.actionBar.title = "NamedNestedMaster"; + console.log("MasterComponent - constructor()"); + } + + ngOnInit() { + console.log("MasterComponent - ngOnInit()"); + } + + ngOnDestroy() { + console.log("MasterComponent - ngOnDestroy()"); + } +} \ No newline at end of file diff --git a/e2e/router/app/second/detail.component.ts b/e2e/router/app/second/detail.component.ts index f7827e2d2..ffe2e67ba 100644 --- a/e2e/router/app/second/detail.component.ts +++ b/e2e/router/app/second/detail.component.ts @@ -1,6 +1,5 @@ -import { Component, OnInit, OnDestroy } from "@angular/core"; -import { ActivatedRoute, Router, Route } from "@angular/router"; -import { Location } from "@angular/common"; +import { Component} from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; import { Page } from "tns-core-modules/ui/page"; import { Observable } from "rxjs"; import { map } from "rxjs/operators"; @@ -18,7 +17,8 @@ import { map } from "rxjs/operators"; export class DetailComponent { public id$: Observable; - constructor(private router: Router, private route: ActivatedRoute) { + constructor(private route: ActivatedRoute, private page: Page) { + page.actionBarHidden = true; console.log("DetailComponent - constructor()"); this.id$ = route.params.pipe(map(r => r["id"])); } diff --git a/e2e/router/app/second/master.component.ts b/e2e/router/app/second/master.component.ts index 08605d5e5..fb3169180 100644 --- a/e2e/router/app/second/master.component.ts +++ b/e2e/router/app/second/master.component.ts @@ -1,19 +1,22 @@ import { Component, OnInit, OnDestroy } from "@angular/core"; - +import { Page } from "tns-core-modules/ui/page"; @Component({ selector: "master", template: ` - + - - - + + + + + ` }) export class MasterComponent implements OnInit, OnDestroy { public details: Array = [1, 2, 3]; - constructor() { + constructor(private page: Page) { + this.page.actionBarHidden = true; console.log("MasterComponent - constructor()"); } diff --git a/e2e/router/app/second/second.component.ts b/e2e/router/app/second/second.component.ts index e01066171..08347f286 100644 --- a/e2e/router/app/second/second.component.ts +++ b/e2e/router/app/second/second.component.ts @@ -16,18 +16,24 @@ import { map } from "rxjs/operators"; + - - - - + + + + + + + + + ` }) export class SecondComponent implements OnInit, OnDestroy { public depth$: Observable; public nextDepth$: Observable; - constructor(private routerExt: RouterExtensions, route: ActivatedRoute, page: Page) { + constructor(private routerExt: RouterExtensions, private route: ActivatedRoute, page: Page) { console.log("SecondComponent - constructor() page: " + page); this.depth$ = route.params.pipe(map(r => r["depth"])); this.nextDepth$ = route.params.pipe(map(r => +r["depth"] + 1)); @@ -42,6 +48,10 @@ export class SecondComponent implements OnInit, OnDestroy { } goBack() { - this.routerExt.back(); + this.routerExt.back({ relativeTo: this.route }); + } + + loadNestedNamedOutlet() { + this.routerExt.navigate([{ outlets: { lazyNameOutlet: ["lazy-named"] } }], { relativeTo: this.route }); } } diff --git a/e2e/router/e2e/router.e2e-spec.ts b/e2e/router/e2e/router.e2e-spec.ts index 82dcda30c..97de5f18a 100644 --- a/e2e/router/e2e/router.e2e-spec.ts +++ b/e2e/router/e2e/router.e2e-spec.ts @@ -190,6 +190,53 @@ describe("Nested navigation + page navigation", () => { }); }); +describe("Nested name navigation + page navigation", () => { + let driver: AppiumDriver; + + before(async () => { + driver = await createDriver(); + await driver.resetApp(); + }); + + it("should find First", async () => { + await assureFirstComponent(driver); + }); + + it("should navigate to Second(1)/master", async () => { + await findAndClick(driver, "GO TO SECOND"); + + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); + }); + + it("should load nested named Master", async () => { + await findAndClick(driver, "LOAD NESTED NAMED OUTLET"); + await assureNamedNestedMasterComponent(driver); + }); + + it("should navigate to nested named Master Detail/1", async () => { + const navigationButton = + await driver.findElementByText("DETAIL-NAMED 1", SearchOptions.exact); + navigationButton.click(); + + await assureNamedNestedDetailComponent(driver, 1); + }); + + it("should navigate back to Master and navigate to Detail/2", async () => { + let navigationButton = + await driver.findElementByText("BACK-NESTED", SearchOptions.exact); + navigationButton.click(); + + await assureNamedNestedMasterComponent(driver); + + navigationButton = + await driver.findElementByText("DETAIL-NAMED 2", SearchOptions.exact); + navigationButton.click(); + + await assureNamedNestedDetailComponent(driver, 2); + }); +}); + describe("Shouldn't be able to navigate back on startup", () => { let driver: AppiumDriver; @@ -371,6 +418,16 @@ async function assureComponentlessLazyComponent(driver: AppiumDriver) { await driver.findElementByText("Lazy Componentless Route", SearchOptions.exact); } +async function assureNamedNestedMasterComponent(driver: AppiumDriver) { + await driver.findElementByText("NamedNestedMaster", SearchOptions.exact); +} + +async function assureNamedNestedDetailComponent(driver: AppiumDriver, param: number) { + await driver.findElementByText("NamedNestedDetail", SearchOptions.exact); + await driver.findElementByText(`nested-named-param: ${param}`, SearchOptions.exact); + +} + async function assureSecondComponent(driver: AppiumDriver, param: number) { await driver.findElementByText("SecondComponent", SearchOptions.exact); await driver.findElementByText(`param: ${param}`, SearchOptions.exact); diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts index 4fc954c96..7670320c5 100644 --- a/nativescript-angular/directives/dialogs.ts +++ b/nativescript-angular/directives/dialogs.ts @@ -17,6 +17,7 @@ import { AppHostView } from "../app-host-view"; import { DetachedLoader } from "../common/detached-loader"; import { PageFactory, PAGE_FACTORY } from "../platform-providers"; import { once } from "../common/utils"; +import { Frame } from "tns-core-modules/ui/frame/frame"; export interface ModalDialogOptions { context?: any; @@ -81,7 +82,10 @@ export class ModalDialogService { const componentContainer = moduleRef || viewContainerRef; const resolver = componentContainer.injector.get(ComponentFactoryResolver); - const frame = parentView.page && parentView.page.frame; + let frame = parentView; + if (!(parentView instanceof Frame)) { + frame = parentView.page && parentView.page.frame; + } if (frame) { this.location._beginModalNavigation(frame); @@ -127,10 +131,8 @@ export class ModalDialogService { const closeCallback = once((...args) => { doneCallback.apply(undefined, args); if (componentView) { - this.location._beginCloseModalNavigation(); componentView.closeModal(); - this.location.back(); - this.location._finishCloseModalNavigation(); + this.location._closeModalNavigation(); detachedLoaderRef.instance.detectChanges(); detachedLoaderRef.destroy(); } diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts index e6d2ff380..0a3cf45c4 100644 --- a/nativescript-angular/platform-providers.ts +++ b/nativescript-angular/platform-providers.ts @@ -66,80 +66,8 @@ 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 { 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-empty-outlet.component.ts b/nativescript-angular/router/ns-empty-outlet.component.ts new file mode 100644 index 000000000..eea9a74fd --- /dev/null +++ b/nativescript-angular/router/ns-empty-outlet.component.ts @@ -0,0 +1,15 @@ +import { Component } from "@angular/core"; +import { Page } from "tns-core-modules/ui/page"; +@Component({ + // tslint:disable-next-line:component-selector + selector: "ns-empty-outlet", + moduleId: module.id, + template: "" +}) +export class NSEmptyOutletComponent { + constructor(private page: Page) { + if (this.page) { + this.page.actionBarHidden = true; + } + } +} diff --git a/nativescript-angular/router/ns-location-strategy.ts b/nativescript-angular/router/ns-location-strategy.ts index e266921c4..74f1a263b 100644 --- a/nativescript-angular/router/ns-location-strategy.ts +++ b/nativescript-angular/router/ns-location-strategy.ts @@ -6,6 +6,42 @@ import { NavigationTransition, Frame } from "tns-core-modules/ui/frame"; import { isPresent } from "../lang-facade"; import { FrameService } from "../platform-providers"; +export class Outlet { + showingModal: boolean; + modalNavigationDepth: number; + parent: Outlet; + isPageNavigationBack: boolean; + + // More than one key available when using NSEmptyOutletComponent component + // in module that lazy loads children (loadChildren) and has outlet name. + outletKeys: Array; + pathByOutlets: string; + states: Array = []; + frame: Frame; + + // Used in reuse-strategy by its children to determine if they should be detached too. + shouldDetach: boolean = true; + constructor(outletKey: string, pathByOutlets: string, modalNavigationDepth?: number) { + this.outletKeys = [outletKey]; + this.isPageNavigationBack = false; + this.showingModal = false; + this.modalNavigationDepth = modalNavigationDepth || 0; + this.pathByOutlets = pathByOutlets; + } + + peekState(): LocationState { + if (this.states.length > 0) { + return this.states[this.states.length - 1]; + } + return null; + } + + containsTopState(stateUrl: string): boolean { + const lastState = this.peekState(); + return lastState && lastState.segmentGroup.toString() === stateUrl; + } +} + export interface NavigationOptions { clearHistory?: boolean; animated?: boolean; @@ -18,27 +54,21 @@ const defaultNavOptions: NavigationOptions = { }; export interface LocationState { - state: any; - title: string; - queryParams: string; segmentGroup: UrlSegmentGroup; isRootSegmentGroup: boolean; isPageNavigation: boolean; - isModalNavigation: boolean; } @Injectable() export class NSLocationStrategy extends LocationStrategy { - private statesByOutlet: { [key: string]: Array } = {}; - private currentUrlTree: UrlTree; - private currentOutlet: string; - private popStateCallbacks = new Array<(_: any) => any>(); + private outlets: Array = []; + private currentOutlet: Outlet; - private _isPageNavigationBack = false; + private popStateCallbacks = new Array<(_: any) => any>(); private _currentNavigationOptions: NavigationOptions; + private currentUrlTree: UrlTree; - public _isModalClosing = false; - public _isModalNavigation = false; + public _modalNavigationDepth = 0; constructor(private frameService: FrameService) { super(); @@ -52,19 +82,20 @@ export class NSLocationStrategy extends LocationStrategy { return "/"; } - const state = this.peekState(this.currentOutlet); + const state = this.currentOutlet && this.currentOutlet.peekState(); if (!state) { return "/"; } let tree = this.currentUrlTree; + let changedOutlet = this.getSegmentGroup(this.currentOutlet.pathByOutlets); // Handle case where the user declares a component at path "/". // The url serializer doesn't parse this url as having a primary outlet. if (state.isRootSegmentGroup) { tree.root = state.segmentGroup; - } else { - tree.root.children[this.currentOutlet] = state.segmentGroup; + } else if (changedOutlet) { + this.updateSegmentGroup(tree.root, changedOutlet, state.segmentGroup); } const urlSerializer = new DefaultUrlSerializer(); @@ -92,72 +123,63 @@ export class NSLocationStrategy extends LocationStrategy { pushStateInternal(state: any, title: string, url: string, queryParams: string): void { const urlSerializer = new DefaultUrlSerializer(); - const stateUrlTree: UrlTree = urlSerializer.parse(url); - const rootOutlets = stateUrlTree.root.children; + this.currentUrlTree = urlSerializer.parse(url); + const urlTreeRoot = this.currentUrlTree.root; + + if (!Object.keys(urlTreeRoot.children).length) { + // Handle case where the user declares a component at path "/". + // The url serializer doesn't parse this url as having a primary outlet. + const rootOutlet = this.createOutlet("primary", null, null); + this.currentOutlet = rootOutlet; + return; + } - this.currentUrlTree = stateUrlTree; + const queue = []; + let currentTree = urlTreeRoot; + + while (currentTree) { + Object.keys(currentTree.children).forEach(outletName => { + const currentSegmentGroup = currentTree.children[outletName]; + currentSegmentGroup.outlet = outletName; + currentSegmentGroup.root = urlTreeRoot; + + let outletKey = this.getSegmentGroupFullPath(currentTree) + outletName; + let outlet = this.findOutletByKey(outletKey); + const parentOutletName = currentTree.outlet || ""; + const parentOutletKey = this.getSegmentGroupFullPath(currentTree.parent) + parentOutletName; + const parentOutlet = this.findOutletByKey(parentOutletKey); + + const containsLastState = outlet && outlet.containsTopState(currentSegmentGroup.toString()); + if (!outlet) { + // tslint:disable-next-line:max-line-length + outlet = this.createOutlet(outletKey, currentSegmentGroup, parentOutlet, this._modalNavigationDepth); + this.currentOutlet = outlet; + } else if (this._modalNavigationDepth > 0 && outlet.showingModal && !containsLastState) { + // Navigation inside modal view. + this.upsertModalOutlet(outlet, currentSegmentGroup); + } else { + outlet.parent = parentOutlet; - // Handle case where the user declares a component at path "/". - // The url serializer doesn't parse this url as having a primary outlet. - if (!Object.keys(rootOutlets).length) { - const outletStates = this.statesByOutlet["primary"] = this.statesByOutlet["primary"] || []; - const isNewPage = outletStates.length === 0; - outletStates.push({ - state: state, - title: title, - queryParams: queryParams, - segmentGroup: stateUrlTree.root, - isRootSegmentGroup: true, - isPageNavigation: isNewPage, - isModalNavigation: false + if (this.updateStates(outlet, currentSegmentGroup)) { + this.currentOutlet = outlet; // If states updated + } + } + + queue.push(currentSegmentGroup); }); - this.currentOutlet = "primary"; - } - - Object.keys(rootOutlets).forEach(outletName => { - const outletStates = this.statesByOutlet[outletName] = this.statesByOutlet[outletName] || []; - const isNewPage = outletStates.length === 0; - const lastState = this.peekState(outletName); - - if (!lastState || lastState.segmentGroup.toString() !== rootOutlets[outletName].toString()) { - outletStates.push({ - state: state, - title: title, - queryParams: queryParams, - segmentGroup: rootOutlets[outletName], - isRootSegmentGroup: false, - isPageNavigation: isNewPage, - isModalNavigation: false - }); - - this.currentOutlet = outletName; - } - }); + + currentTree = queue.shift(); + } } replaceState(state: any, title: string, url: string, queryParams: string): void { - const states = this.statesByOutlet[this.currentOutlet]; + const states = this.currentOutlet && this.currentOutlet.states; + if (states && states.length > 0) { if (isLogEnabled()) { routerLog("NSLocationStrategy.replaceState changing existing state: " + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`); } - - const tree = this.currentUrlTree; - if (url !== tree.toString()) { - const urlSerializer = new DefaultUrlSerializer(); - const stateUrlTree: UrlTree = urlSerializer.parse(url); - const rootOutlets = stateUrlTree.root.children; - - Object.keys(rootOutlets).forEach(outletName => { - const topState = this.peekState(outletName); - - topState.segmentGroup = rootOutlets[outletName]; - topState.state = state; - topState.title = title; - topState.queryParams = queryParams; - }); - } } else { if (isLogEnabled()) { routerLog("NSLocationStrategy.replaceState pushing new state: " + @@ -171,42 +193,11 @@ export class NSLocationStrategy extends LocationStrategy { throw new Error("NSLocationStrategy.forward() - not implemented"); } - back(): void { - if (this._isModalClosing) { - const states = this.statesByOutlet[this.currentOutlet]; - // We are closing modal view - // clear the stack until we get to the page that opened the modal view - let state; - let modalStatesCleared = false; - let count = 1; - - while (!modalStatesCleared) { - state = this.peekState(this.currentOutlet); - - if (!state) { - modalStatesCleared = true; - this.callPopState(null, true); - continue; - } - - if (!state.isModalNavigation) { - states.pop(); - count++; - } else { - modalStatesCleared = true; - state.isModalNavigation = false; - } - } + back(outlet?: Outlet): void { + this.currentOutlet = outlet || this.currentOutlet; - if (isLogEnabled()) { - routerLog(`NSLocationStrategy.back() while closing modal. States popped: ${count}`); - } - - if (state) { - this.callPopState(state, true); - } - } else if (this._isPageNavigationBack) { - const states = this.statesByOutlet[this.currentOutlet]; + if (this.currentOutlet.isPageNavigationBack) { + const states = this.currentOutlet.states; // We are navigating to the previous page // clear the stack until we get to a page navigation state let state = states.pop(); @@ -222,15 +213,19 @@ export class NSLocationStrategy extends LocationStrategy { } this.callPopState(state, true); } else { - let state = this.peekState(this.currentOutlet); - if (state.isPageNavigation) { + let state = this.currentOutlet.peekState(); + if (state && state.isPageNavigation) { // This was a page navigation - so navigate through frame. if (isLogEnabled()) { routerLog("NSLocationStrategy.back() while not navigating back but top" + " state is page - will call frame.goBack()"); } - const frame = this.frameService.getFrame(); - frame.goBack(); + + if (!outlet) { + const topmostFrame = this.frameService.getFrame(); + this.currentOutlet = this.getOutletByFrame(topmostFrame); + } + this.currentOutlet.frame.goBack(); } else { // Nested navigation - just pop the state if (isLogEnabled()) { @@ -238,14 +233,14 @@ export class NSLocationStrategy extends LocationStrategy { " state is not page - just pop"); } - this.callPopState(this.statesByOutlet[this.currentOutlet].pop(), true); + this.callPopState(this.currentOutlet.states.pop(), true); } } - } - canGoBack() { - return this.statesByOutlet[this.currentOutlet].length > 1; + canGoBack(outlet?: Outlet) { + outlet = outlet || this.currentOutlet; + return outlet.states.length > 1; } onPopState(fn: (_: any) => any): void { @@ -262,18 +257,17 @@ export class NSLocationStrategy extends LocationStrategy { return ""; } - private callPopState(state: LocationState, pop: boolean = true) { + private callPopState(state: LocationState, pop: boolean = true, outlet?: Outlet) { + outlet = outlet || this.currentOutlet; const urlSerializer = new DefaultUrlSerializer(); - const rootOutlet = this.currentUrlTree.root.children[this.currentOutlet]; + let changedOutlet = this.getSegmentGroup(outlet.pathByOutlets); - if (state && rootOutlet) { - this.currentUrlTree.root.children[this.currentOutlet] = state.segmentGroup; - } else { + if (state && changedOutlet) { + this.updateSegmentGroup(this.currentUrlTree.root, changedOutlet, state.segmentGroup); + } else if (changedOutlet) { // 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]; + this.updateSegmentGroup(this.currentUrlTree.root, changedOutlet, null); } const url = urlSerializer.serialize(this.currentUrlTree); @@ -283,25 +277,16 @@ export class NSLocationStrategy extends LocationStrategy { } } - private peekState(name: string) { - const states = this.statesByOutlet[name] || []; - if (states.length > 0) { - return states[states.length - 1]; - } - return null; - } - public toString() { let result = []; - Object.keys(this.statesByOutlet).forEach(outletName => { - const outletStates = this.statesByOutlet[outletName]; + this.outlets.forEach(outlet => { + const outletStates = outlet.states; const outletLog = outletStates // tslint:disable-next-line:max-line-length - .map((v, i) => `${outletName}.${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}].[${v.isModalNavigation ? "MODAL" : "BASE"}] "${v.segmentGroup.toString()}"`) + .map((v, i) => `${outlet.outletKeys}.${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}].[${outlet.modalNavigationDepth ? "MODAL" : "BASE"}] "${v.segmentGroup.toString()}"`) .reverse(); - result = result.concat(outletLog); }); @@ -309,107 +294,70 @@ export class NSLocationStrategy extends LocationStrategy { } // Methods for syncing with page navigation in PageRouterOutlet - public _beginBackPageNavigation(name: string, frame: Frame) { - if (this._isPageNavigationBack) { + public _beginBackPageNavigation(frame: Frame) { + const outlet: Outlet = this.getOutletByFrame(frame); + + if (!outlet || outlet.isPageNavigationBack) { if (isLogEnabled()) { routerError("Attempted to call startGoBack while going back."); } return; } + if (isLogEnabled()) { routerLog("NSLocationStrategy.startGoBack()"); } - this._isPageNavigationBack = true; + outlet.isPageNavigationBack = true; - let { cachedFrame } = this.frameService.findFrame(frame); - - if (cachedFrame) { - this.currentOutlet = cachedFrame.rootOutlet; - } else if (!this.frameService.containsOutlet(name)) { - this.currentOutlet = name; - } + this.currentOutlet = outlet; } - public _finishBackPageNavigation() { - if (!this._isPageNavigationBack) { + public _finishBackPageNavigation(frame: Frame) { + const outlet: Outlet = this.getOutletByFrame(frame); + + if (!outlet || !outlet.isPageNavigationBack) { if (isLogEnabled()) { routerError("Attempted to call endGoBack while not going back."); } return; } + if (isLogEnabled()) { routerLog("NSLocationStrategy.finishBackPageNavigation()"); } - this._isPageNavigationBack = false; - } - - public _isPageNavigatingBack() { - return this._isPageNavigationBack; + outlet.isPageNavigationBack = false; } public _beginModalNavigation(frame: Frame): void { if (isLogEnabled()) { - routerLog("NSLocationStrategy._beginModalNavigation()"); + routerLog("NSLocationStrategy._beginModalNavigation()"); } - let { cachedFrameRootOutlet } = this.frameService.findFrame(frame); - - const lastState = this.peekState(cachedFrameRootOutlet || this.currentOutlet); - - if (lastState) { - lastState.isModalNavigation = true; - } - - this._isModalNavigation = true; - } + this.currentOutlet = this.getOutletByFrame(frame); + this.currentOutlet.showingModal = true; + this._modalNavigationDepth++; + } - public _beginCloseModalNavigation(): void { - if (this._isModalClosing) { - if (isLogEnabled()) { - routerError("Attempted to call startCloseModal while closing modal."); - } - return; - } + public _closeModalNavigation() { if (isLogEnabled()) { - routerLog("NSLocationStrategy.startCloseModal()"); + routerLog("NSLocationStrategy.closeModalNavigation()"); } - this._isModalClosing = true; - } + this._modalNavigationDepth--; - public _finishCloseModalNavigation() { - if (!this._isModalClosing) { - if (isLogEnabled()) { - routerError("Attempted to call startCloseModal while not closing modal."); - } - return; - } + this.currentOutlet = this.findOutletByModal(this._modalNavigationDepth, true) || this.currentOutlet; + this.currentOutlet.showingModal = false; - if (isLogEnabled()) { - routerLog("NSLocationStrategy.finishCloseModalNavigation()"); - } - this._isModalNavigation = false; - this._isModalClosing = false; + this.callPopState(this.currentOutlet.peekState(), false); } - public _beginPageNavigation(name: string, frame: Frame): NavigationOptions { + public _beginPageNavigation(frame: Frame): NavigationOptions { if (isLogEnabled()) { routerLog("NSLocationStrategy._beginPageNavigation()"); } - 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); - } + this.currentOutlet = this.getOutletByFrame(frame); + const lastState = this.currentOutlet.peekState(); - const lastState = this.peekState(this.currentOutlet); if (lastState) { lastState.isPageNavigation = true; } @@ -419,7 +367,7 @@ export class NSLocationStrategy extends LocationStrategy { if (isLogEnabled()) { routerLog("NSLocationStrategy._beginPageNavigation clearing states history"); } - this.statesByOutlet[this.currentOutlet] = [lastState]; + this.currentOutlet.states = [lastState]; } this._currentNavigationOptions = undefined; @@ -432,13 +380,254 @@ export class NSLocationStrategy extends LocationStrategy { animated: isPresent(options.animated) ? options.animated : true, transition: options.transition }; + if (isLogEnabled()) { routerLog("NSLocationStrategy._setNavigationOptions(" + - `${JSON.stringify(this._currentNavigationOptions)})`); + `${JSON.stringify(this._currentNavigationOptions)})`); } } - public _getStates(): { [key: string]: Array } { - return this.statesByOutlet; + public _getOutlets(): Array { + return this.outlets; + } + + updateOutletFrame(outlet: Outlet, frame: Frame) { + outlet.frame = frame; + this.currentOutlet = outlet; + } + + clearOutlet(frame: Frame) { + this.outlets = this.outlets.filter(currentOutlet => { + // Remove current outlet, if it's named p-r-o, from the url tree. + const isEqualToCurrent = currentOutlet.pathByOutlets === this.currentOutlet.pathByOutlets; + const isModalOutlet = currentOutlet.modalNavigationDepth > 0; + + if (currentOutlet.frame === frame && isModalOutlet && !isEqualToCurrent) { + this.callPopState(null, true, currentOutlet); + } + return currentOutlet.frame !== frame; + }); + } + + getSegmentGroupFullPath(segmentGroup: UrlSegmentGroup): string { + let fullPath = ""; + + while (segmentGroup) { + const url = segmentGroup.toString(); + + if (fullPath) { + fullPath = (url ? url + "/" : "") + fullPath; + } else { + fullPath = url; + } + + segmentGroup = segmentGroup.parent; + } + + return fullPath; + } + + getRouteFullPath(currentRoute: any): string { + const outletName = currentRoute.outlet; + let fullPath; + + currentRoute = currentRoute.parent; + while (currentRoute) { + const urls = (currentRoute.url.value || currentRoute.url); + let url = urls; + + if (Array.isArray(urls)) { + url = url.join("/"); + } + + fullPath = fullPath ? (url ? url + "/" : url) + fullPath : url; + currentRoute = currentRoute.parent; + } + + return fullPath ? fullPath + outletName : outletName; + } + + + getPathByOutlets(urlSegmentGroup: any): string { + if (!urlSegmentGroup) { + return ""; + } + + let pathToOutlet; + let lastPath = urlSegmentGroup.outlet || "primary"; + let parent = urlSegmentGroup.parent; + + while (parent && urlSegmentGroup.root !== parent) { + if (parent && parent.outlet !== lastPath) { + if (lastPath === "primary") { + lastPath = parent.outlet; + } else { + lastPath = parent.outlet; + pathToOutlet = lastPath + "-" + (pathToOutlet || urlSegmentGroup.outlet); + } + } + + parent = parent.parent; + } + + return pathToOutlet || lastPath; + } + + findOutletByModal(modalNavigation: number, isShowingModal?: boolean): Outlet { + return this.outlets.find((outlet) => { + let isEqual = outlet.modalNavigationDepth === modalNavigation; + return isShowingModal ? isEqual && outlet.showingModal : isEqual; + }); + } + + findOutletByOutletPath(pathByOutlets: string): Outlet { + return this.outlets.find((outlet) => outlet.pathByOutlets === pathByOutlets); + } + + findOutletByKey(outletKey: string): Outlet { + return this.outlets.find((outlet) => outlet.outletKeys.indexOf(outletKey) > -1); + } + + private getOutletByFrame(frame: Frame): Outlet { + let outlet; + + for (let index = 0; index < this.outlets.length; index++) { + const currentOutlet = this.outlets[index]; + + if (currentOutlet.frame === frame) { + outlet = currentOutlet; + break; + } + } + + return outlet; + } + + private updateStates(outlet: Outlet, currentSegmentGroup: UrlSegmentGroup): boolean { + const isNewPage = outlet.states.length === 0; + const lastState = outlet.states[outlet.states.length - 1]; + const equalStateUrls = outlet.containsTopState(currentSegmentGroup.toString()); + + const locationState: LocationState = { + segmentGroup: currentSegmentGroup, + isRootSegmentGroup: false, + isPageNavigation: isNewPage + }; + + if (!lastState || !equalStateUrls) { + outlet.states.push(locationState); + + if (this._modalNavigationDepth === 0 && !outlet.showingModal) { + this.updateParentsStates(outlet, currentSegmentGroup.parent); + } + + return true; + } + + return false; + } + + private updateParentsStates(outlet: Outlet, newSegmentGroup: UrlSegmentGroup) { + let parentOutlet = outlet.parent; + + // Update parents lastState segmentGroups + while (parentOutlet && newSegmentGroup) { + const state = parentOutlet.peekState(); + + if (state) { + state.segmentGroup = newSegmentGroup; + newSegmentGroup = newSegmentGroup.parent; + parentOutlet = parentOutlet.parent; + } + } + } + + private createOutlet(outletKey: string, segmentGroup: any, parent: Outlet, modalNavigation?: number): Outlet { + let isRootSegmentGroup: boolean = false; + + if (!segmentGroup) { + // Handle case where the user declares a component at path "/". + // The url serializer doesn't parse this url as having a primary outlet. + segmentGroup = this.currentUrlTree && this.currentUrlTree.root; + isRootSegmentGroup = true; + } + + const pathByOutlets = this.getPathByOutlets(segmentGroup); + const newOutlet = new Outlet(outletKey, pathByOutlets, modalNavigation); + + const locationState: LocationState = { + segmentGroup: segmentGroup, + isRootSegmentGroup: isRootSegmentGroup, + isPageNavigation: true // It is a new OutletNode. + }; + + newOutlet.states = [locationState]; + newOutlet.parent = parent; + this.outlets.push(newOutlet); + + return newOutlet; + } + + private getSegmentGroup(pathByOutlets: string): UrlSegmentGroup { + const pathList = pathByOutlets.split("-"); + let segmentGroup = this.currentUrlTree.root; + + for (let index = 0; index < pathList.length; index++) { + const currentPath = pathList[index]; + const childrenCount = Object.keys(segmentGroup.children).length; + + if (childrenCount && segmentGroup.children[currentPath]) { + segmentGroup = segmentGroup.children[currentPath]; + } else { + // If no child outlet found with the given name - forget about all previously found outlets. + // example: seaching for 'primary-second-primary' shouldn't return 'primary-second' + // if no 'primary' child available on 'second'. + segmentGroup = null; + break; + } + } + + return segmentGroup; + } + + // Traversal and replacement of segmentGroup. + private updateSegmentGroup(rootNode: any, oldSegmentGroup: UrlSegmentGroup, newSegmentGroup: UrlSegmentGroup) { + const queue = []; + let currentTree = rootNode; + + while (currentTree) { + Object.keys(currentTree.children).forEach(outletName => { + if (currentTree.children[outletName] === oldSegmentGroup) { + if (newSegmentGroup) { + currentTree.children[outletName] = newSegmentGroup; + } else { + delete currentTree.children[outletName]; + } + } + queue.push(currentTree.children[outletName]); + }); + + currentTree = queue.shift(); + } + } + + private upsertModalOutlet(parentOutlet: Outlet, segmentedGroup: UrlSegmentGroup) { + let currentModalOutlet = this.findOutletByModal(this._modalNavigationDepth); + + // We want to treat every p-r-o as a standalone Outlet. + if (!currentModalOutlet) { + if (this._modalNavigationDepth > 1) { + // The parent of the current Outlet should be the previous opened modal (if any). + parentOutlet = this.findOutletByModal(this._modalNavigationDepth - 1); + } + + // No currentModalOutlet available when opening 'primary' p-r-o. + const outletName = "primary"; + const outletKey = parentOutlet.peekState().segmentGroup.toString() + outletName; + currentModalOutlet = this.createOutlet(outletKey, segmentedGroup, parentOutlet, this._modalNavigationDepth); + this.currentOutlet = currentModalOutlet; + } else if (this.updateStates(currentModalOutlet, segmentedGroup)) { + this.currentOutlet = currentModalOutlet; // If states updated + } } } diff --git a/nativescript-angular/router/ns-route-reuse-strategy.ts b/nativescript-angular/router/ns-route-reuse-strategy.ts index b1f90b791..0fa0f3e03 100644 --- a/nativescript-angular/router/ns-route-reuse-strategy.ts +++ b/nativescript-angular/router/ns-route-reuse-strategy.ts @@ -99,10 +99,20 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { shouldDetach(route: ActivatedRouteSnapshot): boolean { route = findTopActivatedRouteNodeForOutlet(route); + const outletKey = this.location.getRouteFullPath(route); + const outlet = this.location.findOutletByKey(outletKey); const key = getSnapshotKey(route); const isPageActivated = route[pageRouterActivatedSymbol]; - const isBack = this.location._isPageNavigatingBack(); - const shouldDetach = !isBack && isPageActivated; + const isBack = outlet ? outlet.isPageNavigationBack : false; + let shouldDetach = outlet && !isBack && isPageActivated; + + if (outlet) { + if (outlet.parent && !outlet.parent.shouldDetach) { + shouldDetach = false; + } + + outlet.shouldDetach = shouldDetach; + } if (isLogEnabled()) { log(`shouldDetach isBack: ${isBack} key: ${key} result: ${shouldDetach}`); @@ -114,19 +124,25 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { shouldAttach(route: ActivatedRouteSnapshot): boolean { route = findTopActivatedRouteNodeForOutlet(route); - const cache = this.cacheByOutlet[route.outlet]; + const outletKey = this.location.getRouteFullPath(route); + const outlet = this.location.findOutletByKey(outletKey); + const cache = this.cacheByOutlet[outletKey]; if (!cache) { return false; } const key = getSnapshotKey(route); - const isBack = this.location._isPageNavigatingBack(); + const isBack = outlet ? outlet.isPageNavigationBack : false; const shouldAttach = isBack && cache.peek().key === key; if (isLogEnabled()) { log(`shouldAttach isBack: ${isBack} key: ${key} result: ${shouldAttach}`); } + if (outlet) { + outlet.shouldDetach = true; + } + return shouldAttach; } @@ -138,13 +154,15 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { log(`store key: ${key}, state: ${state}`); } - const cache = this.cacheByOutlet[route.outlet] = this.cacheByOutlet[route.outlet] || new DetachedStateCache(); + const outletKey = this.location.getRouteFullPath(route); + + // tslint:disable-next-line:max-line-length + const cache = this.cacheByOutlet[outletKey] = this.cacheByOutlet[outletKey] || new DetachedStateCache(); if (state) { let isModal = false; - if (this.location._isModalNavigation) { + if (this.location._modalNavigationDepth > 0) { isModal = true; - this.location._isModalNavigation = false; } cache.push({ key, state, isModal }); @@ -152,6 +170,10 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { const topItem = cache.peek(); if (topItem.key === key) { cache.pop(); + + if (!cache.length) { + delete this.cacheByOutlet[outletKey]; + } } else { throw new Error("Trying to pop from DetachedStateCache but keys don't match. " + `expected: ${topItem.key} actual: ${key}`); @@ -162,13 +184,15 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null { route = findTopActivatedRouteNodeForOutlet(route); - const cache = this.cacheByOutlet[route.outlet]; + const outletKey = this.location.getRouteFullPath(route); + const outlet = this.location.findOutletByKey(outletKey); + const cache = this.cacheByOutlet[outletKey]; if (!cache) { return null; } const key = getSnapshotKey(route); - const isBack = this.location._isPageNavigatingBack(); + const isBack = outlet ? outlet.isPageNavigationBack : false; const cachedItem = cache.peek(); let state = null; @@ -199,16 +223,16 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { return shouldReuse; } - clearCache(outletName: string) { - const cache = this.cacheByOutlet[outletName]; + clearCache(outletKey: string) { + const cache = this.cacheByOutlet[outletKey]; if (cache) { cache.clear(); } } - clearModalCache(outletName: string) { - const cache = this.cacheByOutlet[outletName]; + clearModalCache(outletKey: string) { + const cache = this.cacheByOutlet[outletKey]; if (cache) { cache.clearModalCache(); diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index bccb43f70..79ca5098f 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -20,12 +20,11 @@ import { profile } from "tns-core-modules/profiling"; import { BehaviorSubject } from "rxjs"; import { DEVICE, PAGE_FACTORY, PageFactory } from "../platform-providers"; -import { routerLog as log, isLogEnabled } from "../trace"; +import { routerLog as log, routerError as error, isLogEnabled } from "../trace"; import { DetachedLoader } from "../common/detached-loader"; import { ViewUtil } from "../view-util"; -import { NSLocationStrategy } from "./ns-location-strategy"; +import { NSLocationStrategy, Outlet } from "./ns-location-strategy"; import { NSRouteReuseStrategy } from "./ns-route-reuse-strategy"; -import { FrameService } from "../platform-providers"; export class PageRoute { activatedRoute: BehaviorSubject; @@ -108,7 +107,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire private _activatedRoute: ActivatedRoute | null = null; private detachedLoaderFactory: ComponentFactory; + private outlet: Outlet; private name: string; + private isEmptyOutlet: boolean; private viewUtil: ViewUtil; private frame: Frame; @@ -149,6 +150,7 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef, @Attribute("name") name: string, + @Attribute("isEmptyOutlet") isEmptyOutlet: boolean, private locationStrategy: NSLocationStrategy, private componentFactoryResolver: ComponentFactoryResolver, private resolver: ComponentFactoryResolver, @@ -156,9 +158,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire @Inject(DEVICE) device: Device, @Inject(PAGE_FACTORY) private pageFactory: PageFactory, private routeReuseStrategy: NSRouteReuseStrategy, - elRef: ElementRef, - private frameService: FrameService + elRef: ElementRef ) { + this.isEmptyOutlet = isEmptyOutlet; this.frame = elRef.nativeElement; if (isLogEnabled()) { log(`PageRouterOutlet.constructor frame: ${this.frame}`); @@ -174,17 +176,23 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire ngOnDestroy(): void { // Clear accumulated modal view page cache when page-router-outlet // destroyed on modal view closing - this.routeReuseStrategy.clearModalCache(this.name); this.parentContexts.onChildOutletDestroyed(this.name); - this.frameService.removeFrame(this.frame); + + if (this.outlet) { + this.outlet.outletKeys.forEach(key => { + this.routeReuseStrategy.clearModalCache(key); + }); + this.locationStrategy.clearOutlet(this.frame); + } else { + log("NSLocationStrategy.ngOnDestroy: no outlet available for page-router-outlet"); + } } deactivate(): void { - if (!this.locationStrategy._isPageNavigatingBack()) { + if (!this.outlet.isPageNavigationBack) { if (isLogEnabled()) { log("Currently not in page back navigation - component should be detached instead of deactivated."); } - return; } @@ -236,10 +244,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire this.activated = ref; this._activatedRoute = activatedRoute; - this.markActivatedRoute(activatedRoute); - this.locationStrategy._finishBackPageNavigation(); + this.locationStrategy._finishBackPageNavigation(this.frame); } /** @@ -251,11 +258,21 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null): void { - if (this.locationStrategy._isPageNavigatingBack()) { + this.outlet = this.outlet || this.getOutlet(activatedRoute.snapshot); + if (!this.outlet) { + if (isLogEnabled()) { + error("No outlet found relative to activated route"); + } + return; + } + + this.locationStrategy.updateOutletFrame(this.outlet, this.frame); + + if (this.outlet && this.outlet.isPageNavigationBack) { if (isLogEnabled()) { log("Currently in page back navigation - component should be reattached instead of activated."); } - this.locationStrategy._finishBackPageNavigation(); + this.locationStrategy._finishBackPageNavigation(this.frame); } if (isLogEnabled()) { @@ -316,17 +333,19 @@ 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.frame); + this.locationStrategy._beginBackPageNavigation(this.frame); this.locationStrategy.back(); } })); - const navOptions = this.locationStrategy._beginPageNavigation(this.name, this.frame); + const navOptions = this.locationStrategy._beginPageNavigation(this.frame); // Clear refCache if navigation with clearHistory if (navOptions.clearHistory) { const clearCallback = () => setTimeout(() => { - this.routeReuseStrategy.clearCache(this.name); + if (this.outlet) { + this.routeReuseStrategy.clearCache(this.outlet.outletKeys[0]); + } page.off(Page.navigatedToEvent, clearCallback); }); @@ -341,11 +360,31 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire }); } + // Find and mark the top activated route as an activated one. + // In ns-location-strategy we are reusing components only if their corresponing routes + // are marked as activated from this method. private markActivatedRoute(activatedRoute: ActivatedRoute) { - const nodeToMark = findTopActivatedRouteNodeForOutlet(activatedRoute.snapshot); - nodeToMark[pageRouterActivatedSymbol] = true; - if (isLogEnabled()) { - log(`Activated route marked as page: ${routeToString(nodeToMark)}`); + const queue = []; + queue.push(activatedRoute.snapshot); + let currentRoute = queue.shift(); + + while (currentRoute) { + currentRoute.children.forEach(childRoute => { + queue.push(childRoute); + }); + + const nodeToMark = findTopActivatedRouteNodeForOutlet(currentRoute); + let outletKeyForRoute = this.locationStrategy.getRouteFullPath(nodeToMark); + let outlet = this.locationStrategy.findOutletByKey(outletKeyForRoute); + + if (outlet && outlet.frame) { + nodeToMark[pageRouterActivatedSymbol] = true; + if (isLogEnabled()) { + log("Activated route marked as page: " + routeToString(nodeToMark)); + } + } + + currentRoute = queue.shift(); } } @@ -359,4 +398,29 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire loadedResolver.resolveComponentFactory(component) : this.componentFactoryResolver.resolveComponentFactory(component); } + + private getOutlet(activatedRouteSnapshot: ActivatedRouteSnapshot): Outlet { + const topActivatedRoute = findTopActivatedRouteNodeForOutlet(activatedRouteSnapshot); + const modalNavigation = this.locationStrategy._modalNavigationDepth; + const outletKey = this.locationStrategy.getRouteFullPath(topActivatedRoute); + let outlet; + + if (modalNavigation > 0) { // Modal with 'primary' p-r-o + outlet = this.locationStrategy.findOutletByModal(modalNavigation); + } else { + outlet = this.locationStrategy.findOutletByKey(outletKey); + } + + // Named lazy loaded outlet. + if (!outlet && this.isEmptyOutlet) { + const parentOutletKey = this.locationStrategy.getRouteFullPath(topActivatedRoute.parent); + outlet = this.locationStrategy.findOutletByKey(parentOutletKey); + + if (outlet) { + outlet.outletKeys.push(outletKey); + } + } + + return outlet; + } } diff --git a/nativescript-angular/router/router-extensions.ts b/nativescript-angular/router/router-extensions.ts index 9f12dcb67..0943f8acb 100644 --- a/nativescript-angular/router/router-extensions.ts +++ b/nativescript-angular/router/router-extensions.ts @@ -1,10 +1,17 @@ import { Injectable } from "@angular/core"; -import { Router, UrlTree, NavigationExtras } from "@angular/router"; -import { NSLocationStrategy, NavigationOptions } from "./ns-location-strategy"; +import { Router, UrlTree, NavigationExtras, ActivatedRoute } from "@angular/router"; +import { NSLocationStrategy, NavigationOptions, Outlet } from "./ns-location-strategy"; import { FrameService } from "../platform-providers"; +import { routerError } from "../trace"; +import { findTopActivatedRouteNodeForOutlet } from "./page-router-outlet"; export type ExtendedNavigationExtras = NavigationExtras & NavigationOptions; +export interface BackNavigationOptions { + outlets?: Array; + relativeTo?: ActivatedRoute | null; +} + @Injectable() export class RouterExtensions { @@ -28,12 +35,33 @@ export class RouterExtensions { return this.router.navigateByUrl(url); } - public back() { - this.locationStrategy.back(); + public back(backNavigationOptions?: BackNavigationOptions) { + if (backNavigationOptions) { + this.backOutlets(backNavigationOptions); + } else { + this.locationStrategy.back(); + } } - public canGoBack() { - return this.locationStrategy.canGoBack(); + public canGoBack(backNavigationOptions?: BackNavigationOptions) { + let canGoBack = true; + if (backNavigationOptions) { + const { outletsToBack, outlets } = this.findOutletsToBack(backNavigationOptions); + + if (outletsToBack.length !== outlets.length) { + routerError("No outlet found relative to activated route"); + } else { + outletsToBack.forEach(outletToBack => { + if (!this.locationStrategy.canGoBack(outletToBack)) { + canGoBack = false; + } + }); + } + } else { + canGoBack = this.locationStrategy.canGoBack(); + } + + return canGoBack; } public backToPreviousPage() { @@ -43,4 +71,51 @@ export class RouterExtensions { public canGoBackToPreviousPage(): boolean { return this.frameService.getFrame().canGoBack(); } + + private backOutlets(options: BackNavigationOptions) { + const { outletsToBack, outlets } = this.findOutletsToBack(options); + + if (outletsToBack.length !== outlets.length) { + routerError("No outlet found relative to activated route"); + } else { + outletsToBack.forEach(outletToBack => { + if (outletToBack.isPageNavigationBack) { + routerError("Attempted to call startGoBack while going back:"); + } else { + this.locationStrategy.back(outletToBack); + } + }); + } + } + + // tslint:disable-next-line:max-line-length + private findOutletsToBack(options?: BackNavigationOptions): { outletsToBack: Array, outlets: Array } { + const outletsToBack: Array = []; + const rootRoute: ActivatedRoute = this.router.routerState.root; + let outlets = options.outlets; + let relativeRoute = options.relativeTo; + + if (!outlets && relativeRoute) { + outlets = [relativeRoute.outlet]; + relativeRoute = relativeRoute.parent || rootRoute; + } else if (!relativeRoute) { + relativeRoute = rootRoute; + } + + for (let index = 0; index < relativeRoute.children.length; index++) { + const currentRoute = relativeRoute.children[index]; + + if (outlets.some(currentOutlet => currentOutlet === currentRoute.outlet)) { + const currentRouteSnapshop = findTopActivatedRouteNodeForOutlet(currentRoute.snapshot); + const outletKey = this.locationStrategy.getRouteFullPath(currentRouteSnapshop); + let outlet = this.locationStrategy.findOutletByKey(outletKey); + + if (outlet) { + outletsToBack.push(outlet); + } + } + } + + return { outletsToBack: outletsToBack, outlets: outlets }; + } } diff --git a/nativescript-angular/router/router.module.ts b/nativescript-angular/router/router.module.ts index d1dad1335..385b4fe1f 100644 --- a/nativescript-angular/router/router.module.ts +++ b/nativescript-angular/router/router.module.ts @@ -10,14 +10,17 @@ import { NSRouteReuseStrategy } from "./ns-route-reuse-strategy"; import { RouterExtensions } from "./router-extensions"; import { NativeScriptCommonModule } from "../common"; import { FrameService } from "../platform-providers"; +import { NSEmptyOutletComponent } from "./ns-empty-outlet.component"; export { PageRoute } from "./page-router-outlet"; export { RouterExtensions } from "./router-extensions"; export { NSModuleFactoryLoader } from "./ns-module-factory-loader"; +export { NSEmptyOutletComponent } from "./ns-empty-outlet.component"; + export type LocationState = LocationState; @NgModule({ - declarations: [NSRouterLink, NSRouterLinkActive, PageRouterOutlet], + declarations: [NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent], providers: [ { provide: NSLocationStrategy, @@ -32,7 +35,7 @@ export type LocationState = LocationState; { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy }, ], imports: [RouterModule, NativeScriptCommonModule], - exports: [RouterModule, NSRouterLink, NSRouterLinkActive, PageRouterOutlet], + exports: [RouterModule, NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent], schemas: [NO_ERRORS_SCHEMA], }) export class NativeScriptRouterModule { diff --git a/tests/app/tests/modal-dialog.ts b/tests/app/tests/modal-dialog.ts index 3c1e7a85f..babe84507 100644 --- a/tests/app/tests/modal-dialog.ts +++ b/tests/app/tests/modal-dialog.ts @@ -9,7 +9,7 @@ import { device, isIOS } from "tns-core-modules/platform"; import { ComponentFixture, async } from "@angular/core/testing"; import { nsTestBedRender, nsTestBedAfterEach, nsTestBedBeforeEach } from "nativescript-angular/testing"; -import { NSLocationStrategy } from "nativescript-angular/router/ns-location-strategy"; +import { NSLocationStrategy, Outlet } from "nativescript-angular/router/ns-location-strategy"; import { FrameService } from "nativescript-angular"; import { FakeFrameService } from "./ns-location-strategy"; const CLOSE_WAIT = isIOS ? 1000 : 0; @@ -47,7 +47,11 @@ export class FailComponent { ` }) export class SuccessComponent { - constructor(public service: ModalDialogService, public vcRef: ViewContainerRef) { + constructor( + public service: ModalDialogService, + public vcRef: ViewContainerRef, + public locationStrategy: NSLocationStrategy, + public fakeFrameService: FrameService) { } } @@ -88,6 +92,16 @@ describe("modal-dialog", () => { nsTestBedRender(SuccessComponent) .then((fixture: ComponentFixture) => { const service = fixture.componentRef.instance.service; + const locStrategy = fixture.componentRef.instance.locationStrategy; + const outlet = new Outlet("primary", "primary", 0); + + let parentView = fixture.componentRef.instance.vcRef.element.nativeElement; + parentView = parentView.page && parentView.page.frame; + outlet.frame = parentView; + locStrategy._getOutlets().push(outlet); + + locStrategy.pushState(null, "test", "/test", null); + const comp = fixture.componentRef.instance; return service.showModal(ModalComponent, { viewContainerRef: comp.vcRef }); }) @@ -100,6 +114,16 @@ describe("modal-dialog", () => { nsTestBedRender(SuccessComponent) .then((fixture: ComponentFixture) => { const service = fixture.componentRef.instance.service; + const locStrategy = fixture.componentRef.instance.locationStrategy; + const outlet = new Outlet("primary", "primary", 0); + + let parentView = fixture.componentRef.instance.vcRef.element.nativeElement; + parentView = parentView.page && parentView.page.frame; + outlet.frame = parentView; + locStrategy._getOutlets().push(outlet); + + locStrategy.pushState(null, "test", "/test", null); + const comp = fixture.componentRef.instance; return service.showModal(ModalComponent, { viewContainerRef: comp.vcRef, diff --git a/tests/app/tests/ns-location-strategy.ts b/tests/app/tests/ns-location-strategy.ts index 726f5776c..c0ca05fbc 100644 --- a/tests/app/tests/ns-location-strategy.ts +++ b/tests/app/tests/ns-location-strategy.ts @@ -1,7 +1,7 @@ // make sure you import mocha-config before @angular/core import { assert } from "./test-config"; -import { DefaultUrlSerializer, UrlSegmentGroup, UrlTree } from "@angular/router"; -import { NSLocationStrategy, LocationState } from "nativescript-angular/router/ns-location-strategy"; +import { DefaultUrlSerializer, UrlTree } from "@angular/router"; +import { NSLocationStrategy, LocationState, Outlet } from "nativescript-angular/router/ns-location-strategy"; import { Frame, BackstackEntry, NavigationEntry } from "tns-core-modules/ui/frame"; import { Page } from "tns-core-modules/ui/page"; import { View } from "tns-core-modules/ui/core/view"; @@ -24,6 +24,7 @@ export class FakeFrame extends View implements Frame { currentPage: Page; currentEntry: NavigationEntry; animated: boolean; + actionBarVisibility: "always" | "auto" | "never"; transition: any; _currentEntry: any; @@ -43,48 +44,50 @@ export class FakeFrame extends View implements Frame { } public navigationQueueIsEmpty(): boolean { - throw new Error("I am a FakeFrame"); + throw new Error("navigationQueueIsEmpty:I am a FakeFrame"); } public get navigationBarHeight(): number { - throw new Error("I am a FakeFrame"); + throw new Error("navigationBarHeight:I am a FakeFrame"); } public _processNavigationQueue(page: Page) { - throw new Error("I am a FakeFrame"); + throw new Error("_processNavigationQueue:I am a FakeFrame"); } public _updateActionBar(page?: Page) { - throw new Error("I am a FakeFrame"); + throw new Error("_updateActionBar:I am a FakeFrame"); } public _getNavBarVisible(page: Page): boolean { - throw new Error("I am a FakeFrame"); + throw new Error("_getNavBarVisible:I am a FakeFrame"); } public isCurrent(entry: BackstackEntry): boolean { - throw new Error("I am a FakeFrame"); + throw new Error("isCurrent:I am a FakeFrame"); } setCurrent(entry: BackstackEntry, isBack: boolean): void { - throw new Error("I am a FakeFrame"); + throw new Error("setCurrent:I am a FakeFrame"); } _findEntryForTag(fragmentTag: string): BackstackEntry { - throw new Error("I am a FakeFrame"); + throw new Error("_findEntryForTag:I am a FakeFrame"); } _updateBackstack(entry: BackstackEntry, isBack: boolean): void { - throw new Error("I am a FakeFrame"); + throw new Error("_updateBackstack:I am a FakeFrame"); } _pushInFrameStack() { - throw new Error("I am a FakeFrame"); + throw new Error("_pushInFrameStack:I am a FakeFrame"); } _removeFromFrameStack() { - throw new Error("I am a FakeFrame"); + throw new Error("_removeFromFrameStack:I am a FakeFrame"); } } -function initStrategy(initUrl: string, back?: () => void): NSLocationStrategy { - const strategy = new NSLocationStrategy(new FakeFrameService(back)); +// tslint:disable-next-line:max-line-length +function initStrategy(initUrl: string, back?: () => void): { strategy: NSLocationStrategy, frameService: FrameService } { + const frameService = new FakeFrameService(back); + const strategy = new NSLocationStrategy(frameService); strategy.pushState(null, null, initUrl, null); // load initial state - return strategy; + return { strategy: strategy, frameService: frameService }; } function assertStatesEqual(actual: Array, expected: Array) { @@ -95,6 +98,7 @@ function assertStatesEqual(actual: Array, expected: Array, expected: Array { @@ -155,33 +159,33 @@ describe("NSLocationStrategy", () => { }); it("pushState changes path", () => { - const strategy = initStrategy("/"); + const { strategy } = initStrategy("/"); strategy.pushState(null, "test", "/test", null); assert.equal(strategy.path(), "/test"); }); it("pushState changes path with named outlets", () => { - const strategy = initStrategy("/(test1:test1//test2:test2)"); + const { strategy } = initStrategy("/(test1:test1//test2:test2)"); strategy.pushState(null, "test", "/(test1:test12//test2:test2)", null); assert.equal(strategy.path(), "/(test1:test12//test2:test2)"); }); it("canGoBack() return false initially", () => { - const strategy = initStrategy("/"); + const { strategy } = initStrategy("/"); assert.isFalse(strategy.canGoBack(), "canGoBack() should return false if there are no navigations"); }); it("canGoBack() return false initially with named outlets", () => { - const strategy = initStrategy("/(test1:test1//test2:test2)"); + const { strategy } = initStrategy("/(test1:test1//test2:test2)"); assert.isFalse(strategy.canGoBack(), "canGoBack() should return false if there are no navigations"); }); it("canGoBack() return true after navigation", () => { - const strategy = initStrategy("/"); + const { strategy } = initStrategy("/"); strategy.pushState(null, "test", "/test", null); @@ -189,7 +193,7 @@ describe("NSLocationStrategy", () => { }); it("canGoBack() return true after navigation with named outlets", () => { - const strategy = initStrategy("/(test1:test1//test2:test2)"); + const { strategy } = initStrategy("/(test1:test1//test2:test2)"); strategy.pushState(null, "test", "/(test1:test12//test2:test2)", null); @@ -197,7 +201,7 @@ describe("NSLocationStrategy", () => { }); it("back() calls onPopState", () => { - const strategy = initStrategy("/"); + const { strategy } = initStrategy("/"); let popCount = 0; strategy.onPopState(() => { popCount++; @@ -213,7 +217,7 @@ describe("NSLocationStrategy", () => { }); it("back() calls onPopState with named outlets", () => { - const strategy = initStrategy("/(test1:test1//test2:test2)"); + const { strategy } = initStrategy("/(test1:test1//test2:test2)"); let popCount = 0; strategy.onPopState(() => { popCount++; @@ -229,7 +233,7 @@ describe("NSLocationStrategy", () => { }); it("replaceState() replaces state - doesn't call onPopState", () => { - const strategy = initStrategy("/"); + const { strategy } = initStrategy("/"); let popCount = 0; strategy.onPopState(() => { popCount++; @@ -239,13 +243,15 @@ describe("NSLocationStrategy", () => { assert.equal(strategy.path(), "/test"); strategy.replaceState(null, "test2", "/test2", null); - assert.equal(strategy.path(), "/test2"); + // Currently replaceState does nothing since this shouldn't affect any functionality on {N} side. + // replaceState should be relevant only in Web. + // assert.equal(strategy.path(), "/test2"); assert.equal(popCount, 0); // no onPopState when replacing }); it("replaceState() replaces state - doesn't call onPopState with named outlets", () => { - const strategy = initStrategy("/(test1:test1//test2:test2)"); + const { strategy } = initStrategy("/(test1:test1//test2:test2)"); let popCount = 0; strategy.onPopState(() => { popCount++; @@ -255,58 +261,78 @@ describe("NSLocationStrategy", () => { assert.equal(strategy.path(), "/(test1:test12//test2:test2)"); strategy.replaceState(null, "test2", "/(test1:test13//test2:test2)", null); - assert.equal(strategy.path(), "/(test1:test13//test2:test2)"); + // Currently replaceState does nothing since this shouldn't affect any functionality on {N} side. + // replaceState should be relevant only in Web. + // assert.equal(strategy.path(), "/(test1:test13//test2:test2)"); assert.equal(popCount, 0); // no onPopState when replacing }); it("pushState() with page navigation", () => { - const strategy = initStrategy("/"); - const expectedStates: Array = [createState("/", "primary", true, false, true)]; + const { strategy } = initStrategy("/"); + const outletName = "primary"; + const expectedStates: Array = [createState("/", outletName, true, true)]; + const frame = new FakeFrame(); - simulatePageNavigation(strategy, "/page", "primary"); - expectedStates.push(createState("/page", "primary", true)); + simulatePageNavigation(strategy, "/page", frame, outletName); + expectedStates.push(createState("/page", outletName, true)); strategy.pushState(null, null, "/internal", null); - expectedStates.push(createState("/internal", "primary")); + expectedStates.push(createState("/internal", outletName)); + + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); - assertStatesEqual(strategy._getStates()["primary"], expectedStates); + assertStatesEqual(outlet.states, expectedStates); }); it("pushState() with page navigation with named outlets", () => { - const strategy = initStrategy("/(test1:test1//test2:test2)"); + const { strategy } = initStrategy("/(test1:test1//test2:test2)"); + const frame = new FakeFrame(); + const frame2 = new FakeFrame(); + const outletName = "test1"; + const outletName2 = "test2"; const expectedStatesTest1: Array = [ - createState("/(test1:test1//test2:test2)", "test1", true) + createState("/(test1:test1//test2:test2)", outletName, true) ]; const expectedStatesTest2: Array = [ - createState("/(test1:test1//test2:test2)", "test2", true) + createState("/(test1:test1//test2:test2)", outletName2, true) ]; - simulatePageNavigation(strategy, "/(test1:page//test2:test2)", "test1"); - expectedStatesTest1.push(createState("/(test1:page//test2:test2)", "test1", true)); + simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame, outletName); + simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame2, outletName2); + expectedStatesTest1.push(createState("/(test1:page//test2:test2)", outletName, true)); strategy.pushState(null, null, "/(test1:internal//test2:test2)", null); - expectedStatesTest1.push(createState("/(test1:internal//test2:test2)", "test1")); + expectedStatesTest1.push(createState("/(test1:internal//test2:test2)", outletName)); - assertStatesEqual(strategy._getStates()["test1"], expectedStatesTest1); - assertStatesEqual(strategy._getStates()["test2"], expectedStatesTest2); + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); + const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2); + + assertStatesEqual(outlet.states, expectedStatesTest1); + assertStatesEqual(outlet2.states, expectedStatesTest2); }); it("back() when on page-state calls frame.goBack() if no page navigation in progress", () => { let frameBackCount = 0; - const strategy = initStrategy("/", () => { + const outletName = "primary"; + + const { strategy, frameService } = initStrategy("/", () => { frameBackCount++; }); + + const currentFrame = frameService.getFrame(); + let popCount = 0; strategy.onPopState(() => { popCount++; }); - simulatePageNavigation(strategy, "/page", "primary"); + simulatePageNavigation(strategy, "/page", currentFrame, outletName); + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); assert.equal(frameBackCount, 0); assert.equal(popCount, 0); - assert.equal(strategy._getStates()["primary"].length, 2); + assert.equal(outlet.states.length, 2); // Act strategy.back(); @@ -314,12 +340,15 @@ describe("NSLocationStrategy", () => { // Assert assert.equal(frameBackCount, 1); assert.equal(popCount, 0); - assert.equal(strategy._getStates()["primary"].length, 2); + assert.equal(outlet.states.length, 2); }); it("back() when on page-state calls frame.goBack() if no page navigation in progress with named outlets", () => { let frameBackCount = 0; - const strategy = initStrategy("/(test1:test1//test2:test2)", () => { + const frame = new FakeFrame(); + const outletName = "test1"; + const outletName2 = "test2"; + const { strategy, frameService } = initStrategy("/(test1:test1//test2:test2)", () => { frameBackCount++; }); let popCount = 0; @@ -327,12 +356,16 @@ describe("NSLocationStrategy", () => { popCount++; }); - simulatePageNavigation(strategy, "/(test1:page//test2:test2)", "test1"); + const currentFrame = frameService.getFrame(); + simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame, outletName); + simulatePageNavigation(strategy, "/(test1:page//test2:test2)", currentFrame, outletName2); + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); + const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2); assert.equal(frameBackCount, 0); assert.equal(popCount, 0); - assert.equal(strategy._getStates()["test1"].length, 2); - assert.equal(strategy._getStates()["test2"].length, 1); + assert.equal(outlet.states.length, 2); + assert.equal(outlet2.states.length, 1); // Act strategy.back(); @@ -340,38 +373,47 @@ describe("NSLocationStrategy", () => { // Assert assert.equal(frameBackCount, 1); assert.equal(popCount, 0); - assert.equal(strategy._getStates()["test1"].length, 2); - assert.equal(strategy._getStates()["test2"].length, 1); + assert.equal(outlet.states.length, 2); + assert.equal(outlet2.states.length, 1); }); it("back() when on page-state navigates back if page navigation is in progress", () => { let frameBackCount = 0; - const strategy = initStrategy("/", () => { + const outletName = "primary"; + const { strategy, frameService } = initStrategy("/", () => { frameBackCount++; }); + + const currentFrame = frameService.getFrame(); let popCount = 0; strategy.onPopState(() => { popCount++; }); - simulatePageNavigation(strategy, "/page", "primary"); + simulatePageNavigation(strategy, "/page", currentFrame, outletName); + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); assert.equal(frameBackCount, 0); assert.equal(popCount, 0); - assert.equal(strategy._getStates()["primary"].length, 2); + assert.equal(outlet.states.length, 2); // Act - simulatePageBack(strategy, "primary"); + simulatePageBack(strategy, currentFrame); // Assert assert.equal(frameBackCount, 0); assert.equal(popCount, 1); - assert.equal(strategy._getStates()["primary"].length, 1); + assert.equal(outlet.states.length, 1); }); it("back() when on page-state navigates back if page navigation is in progress with named outlets", () => { let frameBackCount = 0; - const strategy = initStrategy("/(test1:test1//test2:test2)", () => { + const frame = new FakeFrame(); + const frame2 = new FakeFrame(); + const outletName = "test1"; + const outletName2 = "test2"; + + const { strategy, frameService } = initStrategy("/(test1:test1//test2:test2)", () => { frameBackCount++; }); let popCount = 0; @@ -379,49 +421,61 @@ describe("NSLocationStrategy", () => { popCount++; }); - simulatePageNavigation(strategy, "/(test1:page//test2:test2)", "test1"); + simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame, outletName); + simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame2, outletName2); + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); + const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2); assert.equal(frameBackCount, 0); assert.equal(popCount, 0); - assert.equal(strategy._getStates()["test1"].length, 2); - assert.equal(strategy._getStates()["test2"].length, 1); + assert.equal(outlet.states.length, 2); + assert.equal(outlet2.states.length, 1); // Act - simulatePageBack(strategy, "test1"); + simulatePageBack(strategy, frame); // Assert assert.equal(frameBackCount, 0); assert.equal(popCount, 1); - assert.equal(strategy._getStates()["test1"].length, 1); - assert.equal(strategy._getStates()["test2"].length, 1); + assert.equal(outlet.states.length, 1); + assert.equal(outlet2.states.length, 1); }); it("pushState() with clearHistory clears history", () => { - const strategy = initStrategy("/"); - + const { strategy } = initStrategy("/"); + const frame = new FakeFrame(); + const outletName = "primary"; // Act strategy._setNavigationOptions({ clearHistory: true }); - simulatePageNavigation(strategy, "/cleared", "primary"); - + simulatePageNavigation(strategy, "/cleared", frame, outletName); + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); // Assert - assertStatesEqual(strategy._getStates()["primary"], [createState("/cleared", "primary", true)]); + assertStatesEqual(outlet.states, [createState("/cleared", outletName, true)]); }); it("pushState() with clearHistory clears history with named outlets", () => { - const strategy = initStrategy("/(test1:test1//test2:test2)"); + const { strategy } = initStrategy("/(test1:test1//test2:test2)"); + const frame = new FakeFrame(); + const outletName = "test1"; + const frame2 = new FakeFrame(); + const outletName2 = "test2"; // Act strategy._setNavigationOptions({ clearHistory: true }); - simulatePageNavigation(strategy, "/(test1:cleared//test2:test2)", "test1"); + simulatePageNavigation(strategy, "/(test1:cleared//test2:test2)", frame, outletName); + simulatePageNavigation(strategy, "/(test1:cleared//test2:test2)", frame2, outletName2); const expectedStatesTest1: Array = [ - createState("/(test1:cleared//test2:test2)", "test1", true) + createState("/(test1:cleared//test2:test2)", outletName, true) ]; const expectedStatesTest2: Array = [ - createState("/(test1:cleared//test2:test2)", "test2", true) + createState("/(test1:cleared//test2:test2)", outletName2, true) ]; + const outlet: Outlet = strategy.findOutletByOutletPath(outletName); + const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2); + // Assert - assertStatesEqual(strategy._getStates()["test1"], expectedStatesTest1); - assertStatesEqual(strategy._getStates()["test2"], expectedStatesTest2); + assertStatesEqual(outlet.states, expectedStatesTest1); + assertStatesEqual(outlet2.states, expectedStatesTest2); }); });